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