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