Msi.pm (c9b362f6) | Msi.pm (9f91b7e3) |
---|---|
1#************************************************************** 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance --- 9 unchanged lines hidden (view full) --- 18# under the License. 19# 20#************************************************************** 21 22package installer::patch::Msi; 23 24use installer::patch::MsiTable; 25use installer::patch::Tools; | 1#************************************************************** 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance --- 9 unchanged lines hidden (view full) --- 18# under the License. 19# 20#************************************************************** 21 22package installer::patch::Msi; 23 24use installer::patch::MsiTable; 25use installer::patch::Tools; |
26use installer::patch::InstallationSet; 27 28use File::Basename; 29use File::Copy; 30 |
|
26use strict; 27 28 29=head1 NAME 30 31 package installer::patch::Msi - Class represents a single MSI file and gives access to its tables. 32 33=cut 34 | 31use strict; 32 33 34=head1 NAME 35 36 package installer::patch::Msi - Class represents a single MSI file and gives access to its tables. 37 38=cut 39 |
40sub FindAndCreate($$$$$) 41{ 42 my ($class, $version, $is_current_version, $language, $product_name) = @_; |
|
35 | 43 |
44 my $condensed_version = $version; 45 $condensed_version =~ s/\.//g; |
|
36 | 46 |
37=head2 new($class, $version, $language, $product_name) | 47 # When $version is the current version we have to search the msi at a different place. 48 my $path; 49 my $filename; 50 my $is_current = 0; 51 $path = installer::patch::InstallationSet::GetUnpackedExePath( 52 $version, 53 $is_current_version, 54 $language, 55 "msi", 56 $product_name); |
38 | 57 |
58 # Find the msi in the path.ls . 59 $filename = File::Spec->catfile($path, "openoffice".$condensed_version.".msi"); 60 $is_current = $is_current_version; 61 62 return $class->new($filename, $version, $is_current, $language, $product_name); 63} 64 65 66 67 68 69 70=head2 new($class, $filename, $version, $is_current_version, $language, $product_name) 71 |
|
39 Create a new object of the Msi class. The values of $version, $language, and $product_name define 40 where to look for the msi file. 41 42 If construction fails then IsValid() will return false. 43 44=cut | 72 Create a new object of the Msi class. The values of $version, $language, and $product_name define 73 where to look for the msi file. 74 75 If construction fails then IsValid() will return false. 76 77=cut |
45sub new ($$$$) | 78sub new ($$$$$$) |
46{ | 79{ |
47 my ($class, $version, $language, $product_name) = @_; | 80 my ($class, $filename, $version, $is_current_version, $language, $product_name) = @_; |
48 | 81 |
49 my $path = installer::patch::InstallationSet::GetUnpackedMsiPath( 50 $version, 51 $language, 52 "msi", 53 $product_name); 54 55 # Find the msi in the path. 56 my $filename = undef; 57 if ( -d $path) 58 { 59 my @msi_files = glob(File::Spec->catfile($path, "*.msi")); 60 if (scalar @msi_files != 1) 61 { 62 printf STDERR ("there are %d msi files in %s, should be 1", scalar @msi_files, $filename); 63 $filename = ""; 64 } 65 else 66 { 67 $filename = $msi_files[0]; 68 } 69 } 70 else 71 { 72 installer::logger::PrintError("can not access path '%s' to find msi\n", $path); 73 return undef; 74 } 75 | |
76 if ( ! -f $filename) 77 { | 82 if ( ! -f $filename) 83 { |
78 installer::logger::PrintError("can not access MSI file at '%s'\n", $filename); | 84 installer::logger::PrintError("can not find the .msi file for version %s and language %s at '%s'\n", 85 $version, 86 $language, 87 $filename); |
79 return undef; 80 } | 88 return undef; 89 } |
81 | 90 |
82 my $self = { 83 'filename' => $filename, | 91 my $self = { 92 'filename' => $filename, |
84 'path' => $path, | 93 'path' => dirname($filename), |
85 'version' => $version, | 94 'version' => $version, |
95 'is_current_version' => $is_current_version, |
|
86 'language' => $language, 87 'package_format' => "msi", 88 'product_name' => $product_name, 89 'tmpdir' => File::Temp->newdir(CLEANUP => 1), 90 'is_valid' => -f $filename 91 }; 92 bless($self, $class); 93 --- 8 unchanged lines hidden (view full) --- 102 my ($self) = @_; 103 104 return $self->{'is_valid'}; 105} 106 107 108 109 | 96 'language' => $language, 97 'package_format' => "msi", 98 'product_name' => $product_name, 99 'tmpdir' => File::Temp->newdir(CLEANUP => 1), 100 'is_valid' => -f $filename 101 }; 102 bless($self, $class); 103 --- 8 unchanged lines hidden (view full) --- 112 my ($self) = @_; 113 114 return $self->{'is_valid'}; 115} 116 117 118 119 |
120=head2 Commit($self) 121 122 Write all modified tables back into the databse. 123 124=cut 125sub Commit ($) 126{ 127 my $self = shift; 128 129 my @tables_to_update = (); 130 foreach my $table (values %{$self->{'tables'}}) 131 { 132 push @tables_to_update,$table if ($table->IsModified()); 133 } 134 135 if (scalar @tables_to_update > 0) 136 { 137 $installer::logger::Info->printf("writing modified tables to database:\n"); 138 foreach my $table (@tables_to_update) 139 { 140 $installer::logger::Info->printf(" %s\n", $table->GetName()); 141 $self->PutTable($table); 142 } 143 144 foreach my $table (@tables_to_update) 145 { 146 $table->UpdateTimestamp(); 147 $table->MarkAsUnmodified(); 148 } 149 } 150} 151 152 153 154 |
|
110=head2 GetTable($seld, $table_name) 111 112 Return an MsiTable object for $table_name. Table objects are kept 113 alive for the life time of the Msi object. Therefore the second 114 call for the same table is very cheap. 115 116=cut 117sub GetTable ($$) --- 6 unchanged lines hidden (view full) --- 124 my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt"); 125 if ( ! -f $table_filename 126 || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'})) 127 { 128 # Extract table from database to text file on disk. 129 my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name; 130 my $command = join(" ", 131 "msidb.exe", | 155=head2 GetTable($seld, $table_name) 156 157 Return an MsiTable object for $table_name. Table objects are kept 158 alive for the life time of the Msi object. Therefore the second 159 call for the same table is very cheap. 160 161=cut 162sub GetTable ($$) --- 6 unchanged lines hidden (view full) --- 169 my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt"); 170 if ( ! -f $table_filename 171 || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'})) 172 { 173 # Extract table from database to text file on disk. 174 my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name; 175 my $command = join(" ", 176 "msidb.exe", |
132 "-d", installer::patch::Tools::CygpathToWindows($self->{'filename'}), 133 "-f", installer::patch::Tools::CygpathToWindows($self->{'tmpdir'}), | 177 "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}), 178 "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}), |
134 "-e", $table_name); 135 my $result = qx($command); 136 print $result; 137 } 138 139 # Read table into memory. 140 $table = new installer::patch::MsiTable($table_filename, $table_name); 141 $self->{'tables'}->{$table_name} = $table; 142 } 143 144 return $table; 145} 146 147 148 149 | 179 "-e", $table_name); 180 my $result = qx($command); 181 print $result; 182 } 183 184 # Read table into memory. 185 $table = new installer::patch::MsiTable($table_filename, $table_name); 186 $self->{'tables'}->{$table_name} = $table; 187 } 188 189 return $table; 190} 191 192 193 194 |
195=head2 PutTable($self, $table) 196 197 Write the given table back to the databse. 198 199=cut 200sub PutTable ($$) 201{ 202 my ($self, $table) = @_; 203 204 # Create text file from the current table content. 205 $table->WriteFile(); 206 207 my $table_name = $table->GetName(); 208 209 # Store table from text file into database. 210 my $table_filename = $table->{'filename'}; 211 212 if (length($table_name) > 8) 213 { 214 # The file name of the table data must not be longer than 8 characters (not counting the extension). 215 # The name passed as argument to the -i option may be longer. 216 my $truncated_table_name = substr($table_name,0,8); 217 my $table_truncated_filename = File::Spec->catfile( 218 dirname($table_filename), 219 $truncated_table_name.".idt"); 220 File::Copy::copy($table_filename, $table_truncated_filename) || die("can not create table file with short name"); 221 } 222 223 my $command = join(" ", 224 "msidb.exe", 225 "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}), 226 "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}), 227 "-i", $table_name); 228 my $result = system($command); 229 230 if ($result != 0) 231 { 232 installer::logger::PrintError("writing table '%s' back to database failed", $table_name); 233 # For error messages see http://msdn.microsoft.com/en-us/library/windows/desktop/aa372835%28v=vs.85%29.aspx 234 } 235} 236 237 238 239 |
|
150=head2 EnsureAYoungerThanB ($filename_a, $filename_b) 151 152 Internal function (not a method) that compares to files according 153 to their last modification times (mtime). 154 155=cut 156sub EnsureAYoungerThanB ($$) 157{ --- 63 unchanged lines hidden (view full) --- 221 else 222 { 223 my ($long,$short) = installer::patch::Msi::SplitLongShortName($name); 224 return ($long,$short,$long,$short); 225 } 226} 227 228 | 240=head2 EnsureAYoungerThanB ($filename_a, $filename_b) 241 242 Internal function (not a method) that compares to files according 243 to their last modification times (mtime). 244 245=cut 246sub EnsureAYoungerThanB ($$) 247{ --- 63 unchanged lines hidden (view full) --- 311 else 312 { 313 my ($long,$short) = installer::patch::Msi::SplitLongShortName($name); 314 return ($long,$short,$long,$short); 315 } 316} 317 318 |
319=head2 GetDirectoryMap($self) |
|
229 | 320 |
321 Return a map that maps directory unique names (column 'Directory' in table 'Directory') 322 to hashes that contains short and long source and target names. |
|
230 | 323 |
231=head2 GetFileToDirectoryMap ($) 232 233 Return a map (hash) that maps the unique name (column 'File' in 234 the 'File' table) to its directory names. Each value is a 235 reference to an array of two elements: the source path and the 236 target path. 237 238 The map is kept alive for the lifetime of the Msi object. All 239 calls but the first are cheap. 240 | |
241=cut | 324=cut |
242sub GetFileToDirectoryMap ($) | 325sub GetDirectoryMap ($) |
243{ 244 my ($self) = @_; 245 | 326{ 327 my ($self) = @_; 328 |
246 if (defined $self->{'FileToDirectoryMap'}) | 329 if (defined $self->{'DirectoryMap'}) |
247 { | 330 { |
248 return $self->{'FileToDirectoryMap'}; | 331 return $self->{'DirectoryMap'}; |
249 } 250 | 332 } 333 |
251 my $file_table = $self->GetTable("File"); | |
252 my $directory_table = $self->GetTable("Directory"); | 334 my $directory_table = $self->GetTable("Directory"); |
253 my $component_table = $self->GetTable("Component"); 254 $installer::logger::Info->printf("got access to tables File, Directory, Component\n"); 255 | |
256 my %dir_map = (); 257 foreach my $row (@{$directory_table->GetAllRows()}) 258 { | 335 my %dir_map = (); 336 foreach my $row (@{$directory_table->GetAllRows()}) 337 { |
259 my ($target_name, undef, $source_name, undef) | 338 my ($target_long_name, $target_short_name, $source_long_name, $source_short_name) |
260 = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir")); | 339 = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir")); |
261 $dir_map{$row->GetValue("Directory")} = { | 340 my $unique_name = $row->GetValue("Directory"); 341 $dir_map{$unique_name} = 342 { 343 'unique_name' => $unique_name, |
262 'parent' => $row->GetValue("Directory_Parent"), | 344 'parent' => $row->GetValue("Directory_Parent"), |
263 'source_name' => $source_name, 264 'target_name' => $target_name}; | 345 'default_dir' => $row->GetValue("DefaultDir"), 346 'source_long_name' => $source_long_name, 347 'source_short_name' => $source_short_name, 348 'target_long_name' => $target_long_name, 349 'target_short_name' => $target_short_name 350 }; |
265 } 266 267 # Set up full names for all directories. 268 my @todo = map {$_} (keys %dir_map); | 351 } 352 353 # Set up full names for all directories. 354 my @todo = map {$_} (keys %dir_map); |
269 my $process_count = 0; 270 my $push_count = 0; | |
271 while (scalar @todo > 0) 272 { | 355 while (scalar @todo > 0) 356 { |
273 ++$process_count; 274 | |
275 my $key = shift @todo; 276 my $item = $dir_map{$key}; 277 next if defined $item->{'full_source_name'}; 278 279 if ($item->{'parent'} eq "") 280 { 281 # Directory has no parent => full names are the same as the name. | 357 my $key = shift @todo; 358 my $item = $dir_map{$key}; 359 next if defined $item->{'full_source_name'}; 360 361 if ($item->{'parent'} eq "") 362 { 363 # Directory has no parent => full names are the same as the name. |
282 $item->{'full_source_name'} = $item->{'source_name'}; 283 $item->{'full_target_name'} = $item->{'target_name'}; | 364 $item->{'full_source_long_name'} = $item->{'source_long_name'}; 365 $item->{'full_source_short_name'} = $item->{'source_short_name'}; 366 $item->{'full_target_long_name'} = $item->{'target_long_name'}; 367 $item->{'full_target_short_name'} = $item->{'target_short_name'}; |
284 } 285 else 286 { 287 my $parent = $dir_map{$item->{'parent'}}; | 368 } 369 else 370 { 371 my $parent = $dir_map{$item->{'parent'}}; |
288 if ( defined $parent->{'full_source_name'}) | 372 if ( defined $parent->{'full_source_long_name'}) |
289 { 290 # Parent aleady has full names => we can create the full name of the current item. | 373 { 374 # Parent aleady has full names => we can create the full name of the current item. |
291 $item->{'full_source_name'} = $parent->{'full_source_name'} . "/" . $item->{'source_name'}; 292 $item->{'full_target_name'} = $parent->{'full_target_name'} . "/" . $item->{'target_name'}; | 375 $item->{'full_source_long_name'} 376 = $parent->{'full_source_long_name'} . "/" . $item->{'source_long_name'}; 377 $item->{'full_source_short_name'} 378 = $parent->{'full_source_short_name'} . "/" . $item->{'source_short_name'}; 379 $item->{'full_target_long_name'} 380 = $parent->{'full_target_long_name'} . "/" . $item->{'target_long_name'}; 381 $item->{'full_target_short_name'} 382 = $parent->{'full_target_short_name'} . "/" . $item->{'target_short_name'}; |
293 } 294 else 295 { 296 # Parent has to be processed before the current item can be processed. 297 # Push both to the head of the list. 298 unshift @todo, $key; 299 unshift @todo, $item->{'parent'}; | 383 } 384 else 385 { 386 # Parent has to be processed before the current item can be processed. 387 # Push both to the head of the list. 388 unshift @todo, $key; 389 unshift @todo, $item->{'parent'}; |
300 301 ++$push_count; | |
302 } 303 } 304 } 305 | 390 } 391 } 392 } 393 |
306 foreach my $key (keys %dir_map) | 394 # Postprocess the path names for cleanup. 395 foreach my $item (values %dir_map) |
307 { | 396 { |
308 $dir_map{$key}->{'full_source_name'} =~ s/\/(\.\/)+/\//g; 309 $dir_map{$key}->{'full_source_name'} =~ s/^SourceDir\///; 310 $dir_map{$key}->{'full_target_name'} =~ s/\/(\.\/)+/\//g; 311 $dir_map{$key}->{'full_target_name'} =~ s/^SourceDir\///; | 397 foreach my $id ( 398 'full_source_long_name', 399 'full_source_short_name', 400 'full_target_long_name', 401 'full_target_short_name') 402 { 403 $item->{$id} =~ s/\/(\.\/)+/\//g; 404 $item->{$id} =~ s/^SourceDir\///; 405 $item->{$id} =~ s/^\.$//; 406 } |
312 } | 407 } |
313 $installer::logger::Info->printf("for %d directories there where %d processing steps and %d pushes\n", 314 $directory_table->GetRowCount(), 315 $process_count, 316 $push_count); | |
317 | 408 |
409 $self->{'DirectoryMap'} = \%dir_map; 410 return $self->{'DirectoryMap'}; 411} 412 413 414 415 416=head2 GetFileMap ($) 417 418 Return a map (hash) that maps the unique name (column 'File' in 419 the 'File' table) to data that is associated with that file, like 420 the directory or component. 421 422 The map is kept alive for the lifetime of the Msi object. All 423 calls but the first are cheap. 424 425=cut 426sub GetFileMap ($) 427{ 428 my ($self) = @_; 429 430 if (defined $self->{'FileMap'}) 431 { 432 return $self->{'FileMap'}; 433 } 434 435 my $file_table = $self->GetTable("File"); 436 my $component_table = $self->GetTable("Component"); 437 my $dir_map = $self->GetDirectoryMap(); 438 |
|
318 # Setup a map from component names to directory items. | 439 # Setup a map from component names to directory items. |
319 my %component_to_directory_map = map {$_->GetValue('Component') => $_->GetValue('Directory_')} @{$component_table->GetAllRows()}; | 440 my %component_to_directory_map = 441 map 442 {$_->GetValue('Component') => $_->GetValue('Directory_')} 443 @{$component_table->GetAllRows()}; |
320 321 # Finally, create the map from files to directories. | 444 445 # Finally, create the map from files to directories. |
322 my $map = {}; | 446 my $file_map = {}; |
323 my $file_component_index = $file_table->GetColumnIndex("Component_"); 324 my $file_file_index = $file_table->GetColumnIndex("File"); 325 foreach my $file_row (@{$file_table->GetAllRows()}) 326 { 327 my $component_name = $file_row->GetValue($file_component_index); 328 my $directory_name = $component_to_directory_map{$component_name}; | 447 my $file_component_index = $file_table->GetColumnIndex("Component_"); 448 my $file_file_index = $file_table->GetColumnIndex("File"); 449 foreach my $file_row (@{$file_table->GetAllRows()}) 450 { 451 my $component_name = $file_row->GetValue($file_component_index); 452 my $directory_name = $component_to_directory_map{$component_name}; |
329 my $dir_item = $dir_map{$directory_name}; | |
330 my $unique_name = $file_row->GetValue($file_file_index); | 453 my $unique_name = $file_row->GetValue($file_file_index); |
331 $map->{$unique_name} = [$dir_item->{'full_source_name'},$dir_item->{'full_target_name'}]; | 454 $file_map->{$unique_name} = { 455 'directory' => $dir_map->{$directory_name}, 456 'component_name' => $component_name 457 }; |
332 } 333 | 458 } 459 |
334 $installer::logger::Info->printf("got full paths for %d files\n", 335 $file_table->GetRowCount()); 336 337 $self->{'FileToDirectoryMap'} = $map; 338 return $map; | 460 $self->{'FileMap'} = $file_map; 461 return $file_map; |
339} 340 341 3421; | 462} 463 464 4651; |