xref: /AOO41X/main/solenv/bin/modules/installer/patch/Msi.pm (revision 9f91b7e30577a5efd4c1ce7f0c95f4b60745abda)
1c9b362f6SAndre Fischer#**************************************************************
2c9b362f6SAndre Fischer#
3c9b362f6SAndre Fischer#  Licensed to the Apache Software Foundation (ASF) under one
4c9b362f6SAndre Fischer#  or more contributor license agreements.  See the NOTICE file
5c9b362f6SAndre Fischer#  distributed with this work for additional information
6c9b362f6SAndre Fischer#  regarding copyright ownership.  The ASF licenses this file
7c9b362f6SAndre Fischer#  to you under the Apache License, Version 2.0 (the
8c9b362f6SAndre Fischer#  "License"); you may not use this file except in compliance
9c9b362f6SAndre Fischer#  with the License.  You may obtain a copy of the License at
10c9b362f6SAndre Fischer#
11c9b362f6SAndre Fischer#    http://www.apache.org/licenses/LICENSE-2.0
12c9b362f6SAndre Fischer#
13c9b362f6SAndre Fischer#  Unless required by applicable law or agreed to in writing,
14c9b362f6SAndre Fischer#  software distributed under the License is distributed on an
15c9b362f6SAndre Fischer#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16c9b362f6SAndre Fischer#  KIND, either express or implied.  See the License for the
17c9b362f6SAndre Fischer#  specific language governing permissions and limitations
18c9b362f6SAndre Fischer#  under the License.
19c9b362f6SAndre Fischer#
20c9b362f6SAndre Fischer#**************************************************************
21c9b362f6SAndre Fischer
22c9b362f6SAndre Fischerpackage installer::patch::Msi;
23c9b362f6SAndre Fischer
24c9b362f6SAndre Fischeruse installer::patch::MsiTable;
25c9b362f6SAndre 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
31c9b362f6SAndre Fischeruse strict;
32c9b362f6SAndre Fischer
33c9b362f6SAndre Fischer
34c9b362f6SAndre Fischer=head1 NAME
35c9b362f6SAndre Fischer
36c9b362f6SAndre Fischer    package installer::patch::Msi - Class represents a single MSI file and gives access to its tables.
37c9b362f6SAndre Fischer
38c9b362f6SAndre Fischer=cut
39c9b362f6SAndre 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}
64c9b362f6SAndre Fischer
65c9b362f6SAndre 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)
71c9b362f6SAndre Fischer
72c9b362f6SAndre Fischer    Create a new object of the Msi class.  The values of $version, $language, and $product_name define
73c9b362f6SAndre Fischer    where to look for the msi file.
74c9b362f6SAndre Fischer
75c9b362f6SAndre Fischer    If construction fails then IsValid() will return false.
76c9b362f6SAndre Fischer
77c9b362f6SAndre Fischer=cut
78*9f91b7e3SAndre Fischersub new ($$$$$$)
79c9b362f6SAndre Fischer{
80*9f91b7e3SAndre Fischer    my ($class, $filename, $version, $is_current_version, $language, $product_name) = @_;
81c9b362f6SAndre Fischer
82c9b362f6SAndre Fischer    if ( ! -f $filename)
83c9b362f6SAndre 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);
88c9b362f6SAndre Fischer        return undef;
89c9b362f6SAndre Fischer    }
90c9b362f6SAndre Fischer
91c9b362f6SAndre Fischer    my $self = {
92c9b362f6SAndre Fischer        'filename' => $filename,
93*9f91b7e3SAndre Fischer        'path' => dirname($filename),
94c9b362f6SAndre Fischer        'version' => $version,
95*9f91b7e3SAndre Fischer        'is_current_version' => $is_current_version,
96c9b362f6SAndre Fischer        'language' => $language,
97c9b362f6SAndre Fischer        'package_format' => "msi",
98c9b362f6SAndre Fischer        'product_name' => $product_name,
99c9b362f6SAndre Fischer        'tmpdir' => File::Temp->newdir(CLEANUP => 1),
100c9b362f6SAndre Fischer        'is_valid' => -f $filename
101c9b362f6SAndre Fischer    };
102c9b362f6SAndre Fischer    bless($self, $class);
103c9b362f6SAndre Fischer
104c9b362f6SAndre Fischer    return $self;
105c9b362f6SAndre Fischer}
106c9b362f6SAndre Fischer
107c9b362f6SAndre Fischer
108c9b362f6SAndre Fischer
109c9b362f6SAndre Fischer
110c9b362f6SAndre Fischersub IsValid ($)
111c9b362f6SAndre Fischer{
112c9b362f6SAndre Fischer    my ($self) = @_;
113c9b362f6SAndre Fischer
114c9b362f6SAndre Fischer    return $self->{'is_valid'};
115c9b362f6SAndre Fischer}
116c9b362f6SAndre Fischer
117c9b362f6SAndre Fischer
118c9b362f6SAndre Fischer
119c9b362f6SAndre 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
155c9b362f6SAndre Fischer=head2 GetTable($seld, $table_name)
156c9b362f6SAndre Fischer
157c9b362f6SAndre Fischer    Return an MsiTable object for $table_name.  Table objects are kept
158c9b362f6SAndre Fischer    alive for the life time of the Msi object.  Therefore the second
159c9b362f6SAndre Fischer    call for the same table is very cheap.
160c9b362f6SAndre Fischer
161c9b362f6SAndre Fischer=cut
162c9b362f6SAndre Fischersub GetTable ($$)
163c9b362f6SAndre Fischer{
164c9b362f6SAndre Fischer    my ($self, $table_name) = @_;
165c9b362f6SAndre Fischer
166c9b362f6SAndre Fischer    my $table = $self->{'tables'}->{$table_name};
167c9b362f6SAndre Fischer    if ( ! defined $table)
168c9b362f6SAndre Fischer    {
169c9b362f6SAndre Fischer        my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt");
170c9b362f6SAndre Fischer        if ( ! -f $table_filename
171c9b362f6SAndre Fischer            || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'}))
172c9b362f6SAndre Fischer        {
173c9b362f6SAndre Fischer            # Extract table from database to text file on disk.
174c9b362f6SAndre Fischer            my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name;
175c9b362f6SAndre Fischer            my $command = join(" ",
176c9b362f6SAndre Fischer                "msidb.exe",
177*9f91b7e3SAndre Fischer                "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
178*9f91b7e3SAndre Fischer                "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
179c9b362f6SAndre Fischer                "-e", $table_name);
180c9b362f6SAndre Fischer            my $result = qx($command);
181c9b362f6SAndre Fischer            print $result;
182c9b362f6SAndre Fischer        }
183c9b362f6SAndre Fischer
184c9b362f6SAndre Fischer        # Read table into memory.
185c9b362f6SAndre Fischer        $table = new installer::patch::MsiTable($table_filename, $table_name);
186c9b362f6SAndre Fischer        $self->{'tables'}->{$table_name} = $table;
187c9b362f6SAndre Fischer    }
188c9b362f6SAndre Fischer
189c9b362f6SAndre Fischer    return $table;
190c9b362f6SAndre Fischer}
191c9b362f6SAndre Fischer
192c9b362f6SAndre Fischer
193c9b362f6SAndre Fischer
194c9b362f6SAndre 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
240c9b362f6SAndre Fischer=head2 EnsureAYoungerThanB ($filename_a, $filename_b)
241c9b362f6SAndre Fischer
242c9b362f6SAndre Fischer    Internal function (not a method) that compares to files according
243c9b362f6SAndre Fischer    to their last modification times (mtime).
244c9b362f6SAndre Fischer
245c9b362f6SAndre Fischer=cut
246c9b362f6SAndre Fischersub EnsureAYoungerThanB ($$)
247c9b362f6SAndre Fischer{
248c9b362f6SAndre Fischer    my ($filename_a, $filename_b) = @_;
249c9b362f6SAndre Fischer
250c9b362f6SAndre Fischer    die("file $filename_a does not exist") unless -f $filename_a;
251c9b362f6SAndre Fischer    die("file $filename_b does not exist") unless -f $filename_b;
252c9b362f6SAndre Fischer
253c9b362f6SAndre Fischer    my @stat_a = stat($filename_a);
254c9b362f6SAndre Fischer    my @stat_b = stat($filename_b);
255c9b362f6SAndre Fischer
256c9b362f6SAndre Fischer    if ($stat_a[9] <= $stat_b[9])
257c9b362f6SAndre Fischer    {
258c9b362f6SAndre Fischer        return 0;
259c9b362f6SAndre Fischer    }
260c9b362f6SAndre Fischer    else
261c9b362f6SAndre Fischer    {
262c9b362f6SAndre Fischer        return 1;
263c9b362f6SAndre Fischer    }
264c9b362f6SAndre Fischer}
265c9b362f6SAndre Fischer
266c9b362f6SAndre Fischer
267c9b362f6SAndre Fischer
268c9b362f6SAndre Fischer
269c9b362f6SAndre Fischer=head2 SplitLongShortName($name)
270c9b362f6SAndre Fischer
271c9b362f6SAndre Fischer    Split $name (typically from the 'FileName' column in the 'File'
272c9b362f6SAndre Fischer    table or 'DefaultDir' column in the 'Directory' table) at the '|'
273c9b362f6SAndre Fischer    into short (8.3) and long names.  If there is no '|' in $name then
274c9b362f6SAndre Fischer    $name is returned as both short and long name.
275c9b362f6SAndre Fischer
276c9b362f6SAndre Fischer    Returns long and short name (in this order) as array.
277c9b362f6SAndre Fischer
278c9b362f6SAndre Fischer=cut
279c9b362f6SAndre Fischersub SplitLongShortName ($)
280c9b362f6SAndre Fischer{
281c9b362f6SAndre Fischer    my ($name) = @_;
282c9b362f6SAndre Fischer
283c9b362f6SAndre Fischer    if ($name =~ /^([^\|]*)\|(.*)$/)
284c9b362f6SAndre Fischer    {
285c9b362f6SAndre Fischer        return ($2,$1);
286c9b362f6SAndre Fischer    }
287c9b362f6SAndre Fischer    else
288c9b362f6SAndre Fischer    {
289c9b362f6SAndre Fischer        return ($name,$name);
290c9b362f6SAndre Fischer    }
291c9b362f6SAndre Fischer}
292c9b362f6SAndre Fischer
293c9b362f6SAndre Fischer
294c9b362f6SAndre Fischer
295c9b362f6SAndre Fischer=head2 SplitTargetSourceLongShortName ($name)
296c9b362f6SAndre Fischer
297c9b362f6SAndre Fischer    Split $name first at the ':' into target and source parts and each
298c9b362f6SAndre Fischer    of those at the '|'s into long and short parts.  Names that follow
299c9b362f6SAndre Fischer    this pattern come from the 'DefaultDir' column in the 'Directory'
300c9b362f6SAndre Fischer    table.
301c9b362f6SAndre Fischer
302c9b362f6SAndre Fischer=cut
303c9b362f6SAndre Fischersub SplitTargetSourceLongShortName ($)
304c9b362f6SAndre Fischer{
305c9b362f6SAndre Fischer    my ($name) = @_;
306c9b362f6SAndre Fischer
307c9b362f6SAndre Fischer    if ($name =~ /^([^:]*):(.*)$/)
308c9b362f6SAndre Fischer    {
309c9b362f6SAndre Fischer        return (installer::patch::Msi::SplitLongShortName($1), installer::patch::Msi::SplitLongShortName($2));
310c9b362f6SAndre Fischer    }
311c9b362f6SAndre Fischer    else
312c9b362f6SAndre Fischer    {
313c9b362f6SAndre Fischer        my ($long,$short) = installer::patch::Msi::SplitLongShortName($name);
314c9b362f6SAndre Fischer        return ($long,$short,$long,$short);
315c9b362f6SAndre Fischer    }
316c9b362f6SAndre Fischer}
317c9b362f6SAndre Fischer
318c9b362f6SAndre Fischer
319*9f91b7e3SAndre Fischer=head2 GetDirectoryMap($self)
320c9b362f6SAndre 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.
323c9b362f6SAndre Fischer
324c9b362f6SAndre Fischer=cut
325*9f91b7e3SAndre Fischersub GetDirectoryMap ($)
326c9b362f6SAndre Fischer{
327c9b362f6SAndre Fischer    my ($self) = @_;
328c9b362f6SAndre Fischer
329*9f91b7e3SAndre Fischer    if (defined $self->{'DirectoryMap'})
330c9b362f6SAndre Fischer    {
331*9f91b7e3SAndre Fischer        return $self->{'DirectoryMap'};
332c9b362f6SAndre Fischer    }
333c9b362f6SAndre Fischer
334c9b362f6SAndre Fischer    my $directory_table = $self->GetTable("Directory");
335c9b362f6SAndre Fischer    my %dir_map = ();
336c9b362f6SAndre Fischer    foreach my $row (@{$directory_table->GetAllRows()})
337c9b362f6SAndre Fischer    {
338*9f91b7e3SAndre Fischer        my ($target_long_name, $target_short_name, $source_long_name, $source_short_name)
339c9b362f6SAndre 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,
344c9b362f6SAndre 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        };
351c9b362f6SAndre Fischer    }
352c9b362f6SAndre Fischer
353c9b362f6SAndre Fischer    # Set up full names for all directories.
354c9b362f6SAndre Fischer    my @todo = map {$_} (keys %dir_map);
355c9b362f6SAndre Fischer    while (scalar @todo > 0)
356c9b362f6SAndre Fischer    {
357c9b362f6SAndre Fischer        my $key = shift @todo;
358c9b362f6SAndre Fischer        my $item = $dir_map{$key};
359c9b362f6SAndre Fischer        next if defined $item->{'full_source_name'};
360c9b362f6SAndre Fischer
361c9b362f6SAndre Fischer        if ($item->{'parent'} eq "")
362c9b362f6SAndre Fischer        {
363c9b362f6SAndre 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'};
368c9b362f6SAndre Fischer        }
369c9b362f6SAndre Fischer        else
370c9b362f6SAndre Fischer        {
371c9b362f6SAndre Fischer            my $parent = $dir_map{$item->{'parent'}};
372*9f91b7e3SAndre Fischer            if ( defined $parent->{'full_source_long_name'})
373c9b362f6SAndre Fischer            {
374c9b362f6SAndre 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'};
383c9b362f6SAndre Fischer            }
384c9b362f6SAndre Fischer            else
385c9b362f6SAndre Fischer            {
386c9b362f6SAndre Fischer                # Parent has to be processed before the current item can be processed.
387c9b362f6SAndre Fischer                # Push both to the head of the list.
388c9b362f6SAndre Fischer                unshift @todo, $key;
389c9b362f6SAndre Fischer                unshift @todo, $item->{'parent'};
390c9b362f6SAndre Fischer            }
391c9b362f6SAndre Fischer        }
392c9b362f6SAndre Fischer    }
393c9b362f6SAndre Fischer
394*9f91b7e3SAndre Fischer    # Postprocess the path names for cleanup.
395*9f91b7e3SAndre Fischer    foreach my $item (values %dir_map)
396c9b362f6SAndre 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/^\.$//;
406c9b362f6SAndre 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();
438c9b362f6SAndre Fischer
439c9b362f6SAndre 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()};
444c9b362f6SAndre Fischer
445c9b362f6SAndre Fischer    # Finally, create the map from files to directories.
446*9f91b7e3SAndre Fischer    my $file_map = {};
447c9b362f6SAndre Fischer    my $file_component_index = $file_table->GetColumnIndex("Component_");
448c9b362f6SAndre Fischer    my $file_file_index = $file_table->GetColumnIndex("File");
449c9b362f6SAndre Fischer    foreach my $file_row (@{$file_table->GetAllRows()})
450c9b362f6SAndre Fischer    {
451c9b362f6SAndre Fischer        my $component_name = $file_row->GetValue($file_component_index);
452c9b362f6SAndre Fischer        my $directory_name = $component_to_directory_map{$component_name};
453c9b362f6SAndre 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        };
458c9b362f6SAndre Fischer    }
459c9b362f6SAndre Fischer
460*9f91b7e3SAndre Fischer    $self->{'FileMap'} = $file_map;
461*9f91b7e3SAndre Fischer    return $file_map;
462c9b362f6SAndre Fischer}
463c9b362f6SAndre Fischer
464c9b362f6SAndre Fischer
465c9b362f6SAndre Fischer1;
466