1*b1cdbd2cSJim Jagielski#************************************************************** 2*b1cdbd2cSJim Jagielski# 3*b1cdbd2cSJim Jagielski# Licensed to the Apache Software Foundation (ASF) under one 4*b1cdbd2cSJim Jagielski# or more contributor license agreements. See the NOTICE file 5*b1cdbd2cSJim Jagielski# distributed with this work for additional information 6*b1cdbd2cSJim Jagielski# regarding copyright ownership. The ASF licenses this file 7*b1cdbd2cSJim Jagielski# to you under the Apache License, Version 2.0 (the 8*b1cdbd2cSJim Jagielski# "License"); you may not use this file except in compliance 9*b1cdbd2cSJim Jagielski# with the License. You may obtain a copy of the License at 10*b1cdbd2cSJim Jagielski# 11*b1cdbd2cSJim Jagielski# http://www.apache.org/licenses/LICENSE-2.0 12*b1cdbd2cSJim Jagielski# 13*b1cdbd2cSJim Jagielski# Unless required by applicable law or agreed to in writing, 14*b1cdbd2cSJim Jagielski# software distributed under the License is distributed on an 15*b1cdbd2cSJim Jagielski# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*b1cdbd2cSJim Jagielski# KIND, either express or implied. See the License for the 17*b1cdbd2cSJim Jagielski# specific language governing permissions and limitations 18*b1cdbd2cSJim Jagielski# under the License. 19*b1cdbd2cSJim Jagielski# 20*b1cdbd2cSJim Jagielski#************************************************************** 21*b1cdbd2cSJim Jagielski 22*b1cdbd2cSJim Jagielskipackage installer::patch::Msi; 23*b1cdbd2cSJim Jagielski 24*b1cdbd2cSJim Jagielskiuse installer::patch::MsiTable; 25*b1cdbd2cSJim Jagielskiuse installer::patch::Tools; 26*b1cdbd2cSJim Jagielskiuse installer::patch::InstallationSet; 27*b1cdbd2cSJim Jagielski 28*b1cdbd2cSJim Jagielskiuse File::Basename; 29*b1cdbd2cSJim Jagielskiuse File::Copy; 30*b1cdbd2cSJim Jagielski 31*b1cdbd2cSJim Jagielskiuse strict; 32*b1cdbd2cSJim Jagielski 33*b1cdbd2cSJim Jagielski 34*b1cdbd2cSJim Jagielski=head1 NAME 35*b1cdbd2cSJim Jagielski 36*b1cdbd2cSJim Jagielski package installer::patch::Msi - Class represents a single MSI file and gives access to its tables. 37*b1cdbd2cSJim Jagielski 38*b1cdbd2cSJim Jagielski=cut 39*b1cdbd2cSJim Jagielski 40*b1cdbd2cSJim Jagielskisub FindAndCreate($$$$$) 41*b1cdbd2cSJim Jagielski{ 42*b1cdbd2cSJim Jagielski my ($class, $version, $is_current_version, $language, $product_name) = @_; 43*b1cdbd2cSJim Jagielski 44*b1cdbd2cSJim Jagielski my $condensed_version = $version; 45*b1cdbd2cSJim Jagielski $condensed_version =~ s/\.//g; 46*b1cdbd2cSJim Jagielski 47*b1cdbd2cSJim Jagielski # When $version is the current version we have to search the msi at a different place. 48*b1cdbd2cSJim Jagielski my $path; 49*b1cdbd2cSJim Jagielski my $filename; 50*b1cdbd2cSJim Jagielski my $is_current = 0; 51*b1cdbd2cSJim Jagielski $path = installer::patch::InstallationSet::GetUnpackedExePath( 52*b1cdbd2cSJim Jagielski $version, 53*b1cdbd2cSJim Jagielski $is_current_version, 54*b1cdbd2cSJim Jagielski installer::languages::get_normalized_language($language), 55*b1cdbd2cSJim Jagielski "msi", 56*b1cdbd2cSJim Jagielski $product_name); 57*b1cdbd2cSJim Jagielski 58*b1cdbd2cSJim Jagielski # Find the msi in the path.ls . 59*b1cdbd2cSJim Jagielski $filename = File::Spec->catfile($path, "openoffice".$condensed_version.".msi"); 60*b1cdbd2cSJim Jagielski $is_current = $is_current_version; 61*b1cdbd2cSJim Jagielski 62*b1cdbd2cSJim Jagielski return $class->new($filename, $version, $is_current, $language, $product_name); 63*b1cdbd2cSJim Jagielski} 64*b1cdbd2cSJim Jagielski 65*b1cdbd2cSJim Jagielski 66*b1cdbd2cSJim Jagielski 67*b1cdbd2cSJim Jagielski 68*b1cdbd2cSJim Jagielski 69*b1cdbd2cSJim Jagielski 70*b1cdbd2cSJim Jagielski=head2 new($class, $filename, $version, $is_current_version, $language, $product_name) 71*b1cdbd2cSJim Jagielski 72*b1cdbd2cSJim Jagielski Create a new object of the Msi class. The values of $version, $language, and $product_name define 73*b1cdbd2cSJim Jagielski where to look for the msi file. 74*b1cdbd2cSJim Jagielski 75*b1cdbd2cSJim Jagielski If construction fails then IsValid() will return false. 76*b1cdbd2cSJim Jagielski 77*b1cdbd2cSJim Jagielski=cut 78*b1cdbd2cSJim Jagielski 79*b1cdbd2cSJim Jagielskisub new ($$;$$$$) 80*b1cdbd2cSJim Jagielski{ 81*b1cdbd2cSJim Jagielski my ($class, $filename, $version, $is_current_version, $language, $product_name) = @_; 82*b1cdbd2cSJim Jagielski 83*b1cdbd2cSJim Jagielski if ( ! -f $filename) 84*b1cdbd2cSJim Jagielski { 85*b1cdbd2cSJim Jagielski installer::logger::PrintError("can not find the .msi file for version %s and language %s at '%s'\n", 86*b1cdbd2cSJim Jagielski $version, 87*b1cdbd2cSJim Jagielski $language, 88*b1cdbd2cSJim Jagielski $filename); 89*b1cdbd2cSJim Jagielski return undef; 90*b1cdbd2cSJim Jagielski } 91*b1cdbd2cSJim Jagielski 92*b1cdbd2cSJim Jagielski my $self = { 93*b1cdbd2cSJim Jagielski 'filename' => $filename, 94*b1cdbd2cSJim Jagielski 'path' => dirname($filename), 95*b1cdbd2cSJim Jagielski 'version' => $version, 96*b1cdbd2cSJim Jagielski 'is_current_version' => $is_current_version, 97*b1cdbd2cSJim Jagielski 'language' => $language, 98*b1cdbd2cSJim Jagielski 'package_format' => "msi", 99*b1cdbd2cSJim Jagielski 'product_name' => $product_name, 100*b1cdbd2cSJim Jagielski 'tmpdir' => File::Temp->newdir(CLEANUP => 1), 101*b1cdbd2cSJim Jagielski 'is_valid' => -f $filename 102*b1cdbd2cSJim Jagielski }; 103*b1cdbd2cSJim Jagielski bless($self, $class); 104*b1cdbd2cSJim Jagielski 105*b1cdbd2cSJim Jagielski # Fill in some missing values from the 'Properties' table. 106*b1cdbd2cSJim Jagielski if ( ! (defined $version && defined $language && defined $product_name)) 107*b1cdbd2cSJim Jagielski { 108*b1cdbd2cSJim Jagielski my $property_table = $self->GetTable("Property"); 109*b1cdbd2cSJim Jagielski 110*b1cdbd2cSJim Jagielski $self->{'version'} = $property_table->GetValue("Property", "DEFINEDVERSION", "Value") 111*b1cdbd2cSJim Jagielski unless defined $self->{'version'}; 112*b1cdbd2cSJim Jagielski $self->{'product_name'} = $property_table->GetValue("Property", "DEFINEDPRODUCT", "Value") 113*b1cdbd2cSJim Jagielski unless defined $self->{'product_name'}; 114*b1cdbd2cSJim Jagielski 115*b1cdbd2cSJim Jagielski my $language = $property_table->GetValue("Property", "ProductLanguage", "Value"); 116*b1cdbd2cSJim Jagielski # TODO: Convert numerical language id to language name. 117*b1cdbd2cSJim Jagielski $self->{'language'} = $language 118*b1cdbd2cSJim Jagielski unless defined $self->{'language'}; 119*b1cdbd2cSJim Jagielski } 120*b1cdbd2cSJim Jagielski 121*b1cdbd2cSJim Jagielski return $self; 122*b1cdbd2cSJim Jagielski} 123*b1cdbd2cSJim Jagielski 124*b1cdbd2cSJim Jagielski 125*b1cdbd2cSJim Jagielski 126*b1cdbd2cSJim Jagielski 127*b1cdbd2cSJim Jagielskisub IsValid ($) 128*b1cdbd2cSJim Jagielski{ 129*b1cdbd2cSJim Jagielski my ($self) = @_; 130*b1cdbd2cSJim Jagielski 131*b1cdbd2cSJim Jagielski return $self->{'is_valid'}; 132*b1cdbd2cSJim Jagielski} 133*b1cdbd2cSJim Jagielski 134*b1cdbd2cSJim Jagielski 135*b1cdbd2cSJim Jagielski 136*b1cdbd2cSJim Jagielski 137*b1cdbd2cSJim Jagielski=head2 Commit($self) 138*b1cdbd2cSJim Jagielski 139*b1cdbd2cSJim Jagielski Write all modified tables back into the database. 140*b1cdbd2cSJim Jagielski 141*b1cdbd2cSJim Jagielski=cut 142*b1cdbd2cSJim Jagielski 143*b1cdbd2cSJim Jagielskisub Commit ($) 144*b1cdbd2cSJim Jagielski{ 145*b1cdbd2cSJim Jagielski my $self = shift; 146*b1cdbd2cSJim Jagielski 147*b1cdbd2cSJim Jagielski my @tables_to_update = (); 148*b1cdbd2cSJim Jagielski foreach my $table (values %{$self->{'tables'}}) 149*b1cdbd2cSJim Jagielski { 150*b1cdbd2cSJim Jagielski push @tables_to_update,$table if ($table->IsModified()); 151*b1cdbd2cSJim Jagielski } 152*b1cdbd2cSJim Jagielski 153*b1cdbd2cSJim Jagielski if (scalar @tables_to_update > 0) 154*b1cdbd2cSJim Jagielski { 155*b1cdbd2cSJim Jagielski $installer::logger::Info->printf("writing modified tables to database:\n"); 156*b1cdbd2cSJim Jagielski foreach my $table (@tables_to_update) 157*b1cdbd2cSJim Jagielski { 158*b1cdbd2cSJim Jagielski $installer::logger::Info->printf(" %s\n", $table->GetName()); 159*b1cdbd2cSJim Jagielski $self->PutTable($table); 160*b1cdbd2cSJim Jagielski } 161*b1cdbd2cSJim Jagielski 162*b1cdbd2cSJim Jagielski foreach my $table (@tables_to_update) 163*b1cdbd2cSJim Jagielski { 164*b1cdbd2cSJim Jagielski $table->UpdateTimestamp(); 165*b1cdbd2cSJim Jagielski $table->MarkAsUnmodified(); 166*b1cdbd2cSJim Jagielski } 167*b1cdbd2cSJim Jagielski } 168*b1cdbd2cSJim Jagielski} 169*b1cdbd2cSJim Jagielski 170*b1cdbd2cSJim Jagielski 171*b1cdbd2cSJim Jagielski 172*b1cdbd2cSJim Jagielski 173*b1cdbd2cSJim Jagielski=head2 GetTable($seld, $table_name) 174*b1cdbd2cSJim Jagielski 175*b1cdbd2cSJim Jagielski Return an MsiTable object for $table_name. Table objects are kept 176*b1cdbd2cSJim Jagielski alive for the life time of the Msi object. Therefore the second 177*b1cdbd2cSJim Jagielski call for the same table is very cheap. 178*b1cdbd2cSJim Jagielski 179*b1cdbd2cSJim Jagielski=cut 180*b1cdbd2cSJim Jagielski 181*b1cdbd2cSJim Jagielskisub GetTable ($$) 182*b1cdbd2cSJim Jagielski{ 183*b1cdbd2cSJim Jagielski my ($self, $table_name) = @_; 184*b1cdbd2cSJim Jagielski 185*b1cdbd2cSJim Jagielski my $table = $self->{'tables'}->{$table_name}; 186*b1cdbd2cSJim Jagielski if ( ! defined $table) 187*b1cdbd2cSJim Jagielski { 188*b1cdbd2cSJim Jagielski my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt"); 189*b1cdbd2cSJim Jagielski if ( ! -f $table_filename 190*b1cdbd2cSJim Jagielski || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'})) 191*b1cdbd2cSJim Jagielski { 192*b1cdbd2cSJim Jagielski # Extract table from database to text file on disk. 193*b1cdbd2cSJim Jagielski my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name; 194*b1cdbd2cSJim Jagielski my $command = join(" ", 195*b1cdbd2cSJim Jagielski "msidb.exe", 196*b1cdbd2cSJim Jagielski "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}), 197*b1cdbd2cSJim Jagielski "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}), 198*b1cdbd2cSJim Jagielski "-e", $table_name); 199*b1cdbd2cSJim Jagielski my $result = qx($command); 200*b1cdbd2cSJim Jagielski } 201*b1cdbd2cSJim Jagielski 202*b1cdbd2cSJim Jagielski # Read table into memory. 203*b1cdbd2cSJim Jagielski $table = new installer::patch::MsiTable($table_filename, $table_name); 204*b1cdbd2cSJim Jagielski $self->{'tables'}->{$table_name} = $table; 205*b1cdbd2cSJim Jagielski } 206*b1cdbd2cSJim Jagielski 207*b1cdbd2cSJim Jagielski return $table; 208*b1cdbd2cSJim Jagielski} 209*b1cdbd2cSJim Jagielski 210*b1cdbd2cSJim Jagielski 211*b1cdbd2cSJim Jagielski 212*b1cdbd2cSJim Jagielski 213*b1cdbd2cSJim Jagielski=head2 PutTable($self, $table) 214*b1cdbd2cSJim Jagielski 215*b1cdbd2cSJim Jagielski Write the given table back to the database. 216*b1cdbd2cSJim Jagielski 217*b1cdbd2cSJim Jagielski=cut 218*b1cdbd2cSJim Jagielski 219*b1cdbd2cSJim Jagielskisub PutTable ($$) 220*b1cdbd2cSJim Jagielski{ 221*b1cdbd2cSJim Jagielski my ($self, $table) = @_; 222*b1cdbd2cSJim Jagielski 223*b1cdbd2cSJim Jagielski # Create text file from the current table content. 224*b1cdbd2cSJim Jagielski $table->WriteFile(); 225*b1cdbd2cSJim Jagielski 226*b1cdbd2cSJim Jagielski my $table_name = $table->GetName(); 227*b1cdbd2cSJim Jagielski 228*b1cdbd2cSJim Jagielski # Store table from text file into database. 229*b1cdbd2cSJim Jagielski my $table_filename = $table->{'filename'}; 230*b1cdbd2cSJim Jagielski 231*b1cdbd2cSJim Jagielski if (length($table_name) > 8) 232*b1cdbd2cSJim Jagielski { 233*b1cdbd2cSJim Jagielski # The file name of the table data must not be longer than 8 characters (not counting the extension). 234*b1cdbd2cSJim Jagielski # The name passed as argument to the -i option may be longer. 235*b1cdbd2cSJim Jagielski my $truncated_table_name = substr($table_name,0,8); 236*b1cdbd2cSJim Jagielski my $table_truncated_filename = File::Spec->catfile( 237*b1cdbd2cSJim Jagielski dirname($table_filename), 238*b1cdbd2cSJim Jagielski $truncated_table_name.".idt"); 239*b1cdbd2cSJim Jagielski File::Copy::copy($table_filename, $table_truncated_filename) || die("can not create table file with short name"); 240*b1cdbd2cSJim Jagielski } 241*b1cdbd2cSJim Jagielski 242*b1cdbd2cSJim Jagielski my $command = join(" ", 243*b1cdbd2cSJim Jagielski "msidb.exe", 244*b1cdbd2cSJim Jagielski "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}), 245*b1cdbd2cSJim Jagielski "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}), 246*b1cdbd2cSJim Jagielski "-i", $table_name); 247*b1cdbd2cSJim Jagielski my $result = system($command); 248*b1cdbd2cSJim Jagielski 249*b1cdbd2cSJim Jagielski if ($result != 0) 250*b1cdbd2cSJim Jagielski { 251*b1cdbd2cSJim Jagielski installer::logger::PrintError("writing table '%s' back to database failed", $table_name); 252*b1cdbd2cSJim Jagielski # For error messages see http://msdn.microsoft.com/en-us/library/windows/desktop/aa372835%28v=vs.85%29.aspx 253*b1cdbd2cSJim Jagielski } 254*b1cdbd2cSJim Jagielski} 255*b1cdbd2cSJim Jagielski 256*b1cdbd2cSJim Jagielski 257*b1cdbd2cSJim Jagielski 258*b1cdbd2cSJim Jagielski 259*b1cdbd2cSJim Jagielski=head2 EnsureAYoungerThanB ($filename_a, $filename_b) 260*b1cdbd2cSJim Jagielski 261*b1cdbd2cSJim Jagielski Internal function (not a method) that compares to files according 262*b1cdbd2cSJim Jagielski to their last modification times (mtime). 263*b1cdbd2cSJim Jagielski 264*b1cdbd2cSJim Jagielski=cut 265*b1cdbd2cSJim Jagielski 266*b1cdbd2cSJim Jagielskisub EnsureAYoungerThanB ($$) 267*b1cdbd2cSJim Jagielski{ 268*b1cdbd2cSJim Jagielski my ($filename_a, $filename_b) = @_; 269*b1cdbd2cSJim Jagielski 270*b1cdbd2cSJim Jagielski die("file $filename_a does not exist") unless -f $filename_a; 271*b1cdbd2cSJim Jagielski die("file $filename_b does not exist") unless -f $filename_b; 272*b1cdbd2cSJim Jagielski 273*b1cdbd2cSJim Jagielski my @stat_a = stat($filename_a); 274*b1cdbd2cSJim Jagielski my @stat_b = stat($filename_b); 275*b1cdbd2cSJim Jagielski 276*b1cdbd2cSJim Jagielski if ($stat_a[9] <= $stat_b[9]) 277*b1cdbd2cSJim Jagielski { 278*b1cdbd2cSJim Jagielski return 0; 279*b1cdbd2cSJim Jagielski } 280*b1cdbd2cSJim Jagielski else 281*b1cdbd2cSJim Jagielski { 282*b1cdbd2cSJim Jagielski return 1; 283*b1cdbd2cSJim Jagielski } 284*b1cdbd2cSJim Jagielski} 285*b1cdbd2cSJim Jagielski 286*b1cdbd2cSJim Jagielski 287*b1cdbd2cSJim Jagielski 288*b1cdbd2cSJim Jagielski 289*b1cdbd2cSJim Jagielski=head2 SplitLongShortName($name) 290*b1cdbd2cSJim Jagielski 291*b1cdbd2cSJim Jagielski Split $name (typically from the 'FileName' column in the 'File' 292*b1cdbd2cSJim Jagielski table or 'DefaultDir' column in the 'Directory' table) at the '|' 293*b1cdbd2cSJim Jagielski into short (8.3) and long names. If there is no '|' in $name then 294*b1cdbd2cSJim Jagielski $name is returned as both short and long name. 295*b1cdbd2cSJim Jagielski 296*b1cdbd2cSJim Jagielski Returns long and short name (in this order) as array. 297*b1cdbd2cSJim Jagielski 298*b1cdbd2cSJim Jagielski=cut 299*b1cdbd2cSJim Jagielski 300*b1cdbd2cSJim Jagielskisub SplitLongShortName ($) 301*b1cdbd2cSJim Jagielski{ 302*b1cdbd2cSJim Jagielski my ($name) = @_; 303*b1cdbd2cSJim Jagielski 304*b1cdbd2cSJim Jagielski if ($name =~ /^([^\|]*)\|(.*)$/) 305*b1cdbd2cSJim Jagielski { 306*b1cdbd2cSJim Jagielski return ($2,$1); 307*b1cdbd2cSJim Jagielski } 308*b1cdbd2cSJim Jagielski else 309*b1cdbd2cSJim Jagielski { 310*b1cdbd2cSJim Jagielski return ($name,$name); 311*b1cdbd2cSJim Jagielski } 312*b1cdbd2cSJim Jagielski} 313*b1cdbd2cSJim Jagielski 314*b1cdbd2cSJim Jagielski 315*b1cdbd2cSJim Jagielski 316*b1cdbd2cSJim Jagielski=head2 SplitTargetSourceLongShortName ($name) 317*b1cdbd2cSJim Jagielski 318*b1cdbd2cSJim Jagielski Split $name first at the ':' into target and source parts and each 319*b1cdbd2cSJim Jagielski of those at the '|'s into long and short parts. Names that follow 320*b1cdbd2cSJim Jagielski this pattern come from the 'DefaultDir' column in the 'Directory' 321*b1cdbd2cSJim Jagielski table. 322*b1cdbd2cSJim Jagielski 323*b1cdbd2cSJim Jagielski=cut 324*b1cdbd2cSJim Jagielski 325*b1cdbd2cSJim Jagielskisub SplitTargetSourceLongShortName ($) 326*b1cdbd2cSJim Jagielski{ 327*b1cdbd2cSJim Jagielski my ($name) = @_; 328*b1cdbd2cSJim Jagielski 329*b1cdbd2cSJim Jagielski if ($name =~ /^([^:]*):(.*)$/) 330*b1cdbd2cSJim Jagielski { 331*b1cdbd2cSJim Jagielski return (installer::patch::Msi::SplitLongShortName($1), installer::patch::Msi::SplitLongShortName($2)); 332*b1cdbd2cSJim Jagielski } 333*b1cdbd2cSJim Jagielski else 334*b1cdbd2cSJim Jagielski { 335*b1cdbd2cSJim Jagielski my ($long,$short) = installer::patch::Msi::SplitLongShortName($name); 336*b1cdbd2cSJim Jagielski return ($long,$short,$long,$short); 337*b1cdbd2cSJim Jagielski } 338*b1cdbd2cSJim Jagielski} 339*b1cdbd2cSJim Jagielski 340*b1cdbd2cSJim Jagielski 341*b1cdbd2cSJim Jagielski 342*b1cdbd2cSJim Jagielski 343*b1cdbd2cSJim Jagielskisub SetupFullNames ($$); 344*b1cdbd2cSJim Jagielskisub SetupFullNames ($$) 345*b1cdbd2cSJim Jagielski{ 346*b1cdbd2cSJim Jagielski my ($item, $directory_map) = @_; 347*b1cdbd2cSJim Jagielski 348*b1cdbd2cSJim Jagielski # Don't process any item twice. 349*b1cdbd2cSJim Jagielski return if defined $item->{'full_source_name'}; 350*b1cdbd2cSJim Jagielski 351*b1cdbd2cSJim Jagielski my $parent = $item->{'parent'}; 352*b1cdbd2cSJim Jagielski if (defined $parent) 353*b1cdbd2cSJim Jagielski { 354*b1cdbd2cSJim Jagielski # Process the parent first. 355*b1cdbd2cSJim Jagielski if ( ! defined $parent->{'full_source_long_name'}) 356*b1cdbd2cSJim Jagielski { 357*b1cdbd2cSJim Jagielski SetupFullNames($parent, $directory_map); 358*b1cdbd2cSJim Jagielski } 359*b1cdbd2cSJim Jagielski 360*b1cdbd2cSJim Jagielski # Prepend the full names of the parent to our names. 361*b1cdbd2cSJim Jagielski $item->{'full_source_long_name'} 362*b1cdbd2cSJim Jagielski = $parent->{'full_source_long_name'} . "/" . $item->{'source_long_name'}; 363*b1cdbd2cSJim Jagielski $item->{'full_source_short_name'} 364*b1cdbd2cSJim Jagielski = $parent->{'full_source_short_name'} . "/" . $item->{'source_short_name'}; 365*b1cdbd2cSJim Jagielski $item->{'full_target_long_name'} 366*b1cdbd2cSJim Jagielski = $parent->{'full_target_long_name'} . "/" . $item->{'target_long_name'}; 367*b1cdbd2cSJim Jagielski $item->{'full_target_short_name'} 368*b1cdbd2cSJim Jagielski = $parent->{'full_target_short_name'} . "/" . $item->{'target_short_name'}; 369*b1cdbd2cSJim Jagielski } 370*b1cdbd2cSJim Jagielski else 371*b1cdbd2cSJim Jagielski { 372*b1cdbd2cSJim Jagielski # Directory has no parent => full names are the same as the name. 373*b1cdbd2cSJim Jagielski $item->{'full_source_long_name'} = $item->{'source_long_name'}; 374*b1cdbd2cSJim Jagielski $item->{'full_source_short_name'} = $item->{'source_short_name'}; 375*b1cdbd2cSJim Jagielski $item->{'full_target_long_name'} = $item->{'target_long_name'}; 376*b1cdbd2cSJim Jagielski $item->{'full_target_short_name'} = $item->{'target_short_name'}; 377*b1cdbd2cSJim Jagielski } 378*b1cdbd2cSJim Jagielski} 379*b1cdbd2cSJim Jagielski 380*b1cdbd2cSJim Jagielski 381*b1cdbd2cSJim Jagielski 382*b1cdbd2cSJim Jagielski 383*b1cdbd2cSJim Jagielski=head2 GetDirectoryMap($self) 384*b1cdbd2cSJim Jagielski 385*b1cdbd2cSJim Jagielski Return a map that maps directory unique names (column 'Directory' in table 'Directory') 386*b1cdbd2cSJim Jagielski to hashes that contains short and long source and target names. 387*b1cdbd2cSJim Jagielski 388*b1cdbd2cSJim Jagielski=cut 389*b1cdbd2cSJim Jagielski 390*b1cdbd2cSJim Jagielskisub GetDirectoryMap ($) 391*b1cdbd2cSJim Jagielski{ 392*b1cdbd2cSJim Jagielski my ($self) = @_; 393*b1cdbd2cSJim Jagielski 394*b1cdbd2cSJim Jagielski if (defined $self->{'DirectoryMap'}) 395*b1cdbd2cSJim Jagielski { 396*b1cdbd2cSJim Jagielski return $self->{'DirectoryMap'}; 397*b1cdbd2cSJim Jagielski } 398*b1cdbd2cSJim Jagielski 399*b1cdbd2cSJim Jagielski # Initialize the directory map. 400*b1cdbd2cSJim Jagielski my $directory_table = $self->GetTable("Directory"); 401*b1cdbd2cSJim Jagielski my $directory_map = (); 402*b1cdbd2cSJim Jagielski foreach my $row (@{$directory_table->GetAllRows()}) 403*b1cdbd2cSJim Jagielski { 404*b1cdbd2cSJim Jagielski my ($target_long_name, $target_short_name, $source_long_name, $source_short_name) 405*b1cdbd2cSJim Jagielski = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir")); 406*b1cdbd2cSJim Jagielski my $unique_name = $row->GetValue("Directory"); 407*b1cdbd2cSJim Jagielski $directory_map->{$unique_name} = 408*b1cdbd2cSJim Jagielski { 409*b1cdbd2cSJim Jagielski 'unique_name' => $unique_name, 410*b1cdbd2cSJim Jagielski 'parent_name' => $row->GetValue("Directory_Parent"), 411*b1cdbd2cSJim Jagielski 'default_dir' => $row->GetValue("DefaultDir"), 412*b1cdbd2cSJim Jagielski 'source_long_name' => $source_long_name, 413*b1cdbd2cSJim Jagielski 'source_short_name' => $source_short_name, 414*b1cdbd2cSJim Jagielski 'target_long_name' => $target_long_name, 415*b1cdbd2cSJim Jagielski 'target_short_name' => $target_short_name 416*b1cdbd2cSJim Jagielski }; 417*b1cdbd2cSJim Jagielski } 418*b1cdbd2cSJim Jagielski 419*b1cdbd2cSJim Jagielski # Add references to parent directories. 420*b1cdbd2cSJim Jagielski foreach my $item (values %$directory_map) 421*b1cdbd2cSJim Jagielski { 422*b1cdbd2cSJim Jagielski $item->{'parent'} = $directory_map->{$item->{'parent_name'}}; 423*b1cdbd2cSJim Jagielski } 424*b1cdbd2cSJim Jagielski 425*b1cdbd2cSJim Jagielski # Set up full names for all directories. 426*b1cdbd2cSJim Jagielski foreach my $item (values %$directory_map) 427*b1cdbd2cSJim Jagielski { 428*b1cdbd2cSJim Jagielski SetupFullNames($item, $directory_map); 429*b1cdbd2cSJim Jagielski } 430*b1cdbd2cSJim Jagielski 431*b1cdbd2cSJim Jagielski # Cleanup the names. 432*b1cdbd2cSJim Jagielski foreach my $item (values %$directory_map) 433*b1cdbd2cSJim Jagielski { 434*b1cdbd2cSJim Jagielski foreach my $id ( 435*b1cdbd2cSJim Jagielski 'full_source_long_name', 436*b1cdbd2cSJim Jagielski 'full_source_short_name', 437*b1cdbd2cSJim Jagielski 'full_target_long_name', 438*b1cdbd2cSJim Jagielski 'full_target_short_name') 439*b1cdbd2cSJim Jagielski { 440*b1cdbd2cSJim Jagielski $item->{$id} =~ s/\/(\.\/)+/\//g; 441*b1cdbd2cSJim Jagielski $item->{$id} =~ s/^SourceDir\///; 442*b1cdbd2cSJim Jagielski $item->{$id} =~ s/^\.$//; 443*b1cdbd2cSJim Jagielski } 444*b1cdbd2cSJim Jagielski } 445*b1cdbd2cSJim Jagielski 446*b1cdbd2cSJim Jagielski $self->{'DirectoryMap'} = $directory_map; 447*b1cdbd2cSJim Jagielski return $self->{'DirectoryMap'}; 448*b1cdbd2cSJim Jagielski} 449*b1cdbd2cSJim Jagielski 450*b1cdbd2cSJim Jagielski 451*b1cdbd2cSJim Jagielski 452*b1cdbd2cSJim Jagielski 453*b1cdbd2cSJim Jagielski=head2 GetFileMap ($) 454*b1cdbd2cSJim Jagielski 455*b1cdbd2cSJim Jagielski Return a map (hash) that maps the unique name (column 'File' in 456*b1cdbd2cSJim Jagielski the 'File' table) to data that is associated with that file, like 457*b1cdbd2cSJim Jagielski the directory or component. 458*b1cdbd2cSJim Jagielski 459*b1cdbd2cSJim Jagielski The map is kept alive for the lifetime of the Msi object. All 460*b1cdbd2cSJim Jagielski calls but the first are cheap. 461*b1cdbd2cSJim Jagielski 462*b1cdbd2cSJim Jagielski=cut 463*b1cdbd2cSJim Jagielski 464*b1cdbd2cSJim Jagielskisub GetFileMap ($) 465*b1cdbd2cSJim Jagielski{ 466*b1cdbd2cSJim Jagielski my ($self) = @_; 467*b1cdbd2cSJim Jagielski 468*b1cdbd2cSJim Jagielski if (defined $self->{'FileMap'}) 469*b1cdbd2cSJim Jagielski { 470*b1cdbd2cSJim Jagielski return $self->{'FileMap'}; 471*b1cdbd2cSJim Jagielski } 472*b1cdbd2cSJim Jagielski 473*b1cdbd2cSJim Jagielski my $file_table = $self->GetTable("File"); 474*b1cdbd2cSJim Jagielski my $component_table = $self->GetTable("Component"); 475*b1cdbd2cSJim Jagielski my $dir_map = $self->GetDirectoryMap(); 476*b1cdbd2cSJim Jagielski 477*b1cdbd2cSJim Jagielski # Setup a map from component names to directory items. 478*b1cdbd2cSJim Jagielski my %component_to_directory_map = 479*b1cdbd2cSJim Jagielski map 480*b1cdbd2cSJim Jagielski {$_->GetValue('Component') => $_->GetValue('Directory_')} 481*b1cdbd2cSJim Jagielski @{$component_table->GetAllRows()}; 482*b1cdbd2cSJim Jagielski 483*b1cdbd2cSJim Jagielski # Finally, create the map from files to directories. 484*b1cdbd2cSJim Jagielski my $file_map = {}; 485*b1cdbd2cSJim Jagielski my $file_component_index = $file_table->GetColumnIndex("Component_"); 486*b1cdbd2cSJim Jagielski my $file_file_index = $file_table->GetColumnIndex("File"); 487*b1cdbd2cSJim Jagielski my $file_filename_index = $file_table->GetColumnIndex("FileName"); 488*b1cdbd2cSJim Jagielski foreach my $file_row (@{$file_table->GetAllRows()}) 489*b1cdbd2cSJim Jagielski { 490*b1cdbd2cSJim Jagielski my $component_name = $file_row->GetValue($file_component_index); 491*b1cdbd2cSJim Jagielski my $directory_name = $component_to_directory_map{$component_name}; 492*b1cdbd2cSJim Jagielski my $unique_name = $file_row->GetValue($file_file_index); 493*b1cdbd2cSJim Jagielski my $file_name = $file_row->GetValue($file_filename_index); 494*b1cdbd2cSJim Jagielski my ($long_name, $short_name) = SplitLongShortName($file_name); 495*b1cdbd2cSJim Jagielski $file_map->{$unique_name} = { 496*b1cdbd2cSJim Jagielski 'directory' => $dir_map->{$directory_name}, 497*b1cdbd2cSJim Jagielski 'component_name' => $component_name, 498*b1cdbd2cSJim Jagielski 'file_name' => $file_name, 499*b1cdbd2cSJim Jagielski 'long_name' => $long_name, 500*b1cdbd2cSJim Jagielski 'short_name' => $short_name 501*b1cdbd2cSJim Jagielski }; 502*b1cdbd2cSJim Jagielski } 503*b1cdbd2cSJim Jagielski 504*b1cdbd2cSJim Jagielski $self->{'FileMap'} = $file_map; 505*b1cdbd2cSJim Jagielski return $file_map; 506*b1cdbd2cSJim Jagielski} 507*b1cdbd2cSJim Jagielski 508*b1cdbd2cSJim Jagielski 509*b1cdbd2cSJim Jagielski1; 510