xref: /AOO41X/main/solenv/bin/modules/installer/patch/Msi.pm (revision 60b96b8d2c40ffaa603fbcdb78c4a5b0e1606ae3)
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;
269f91b7e3SAndre Fischeruse installer::patch::InstallationSet;
279f91b7e3SAndre Fischer
289f91b7e3SAndre Fischeruse File::Basename;
299f91b7e3SAndre Fischeruse File::Copy;
309f91b7e3SAndre 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
409f91b7e3SAndre Fischersub FindAndCreate($$$$$)
419f91b7e3SAndre Fischer{
429f91b7e3SAndre Fischer    my ($class, $version, $is_current_version, $language, $product_name) = @_;
439f91b7e3SAndre Fischer
449f91b7e3SAndre Fischer    my $condensed_version = $version;
459f91b7e3SAndre Fischer    $condensed_version =~ s/\.//g;
469f91b7e3SAndre Fischer
479f91b7e3SAndre Fischer    # When $version is the current version we have to search the msi at a different place.
489f91b7e3SAndre Fischer    my $path;
499f91b7e3SAndre Fischer    my $filename;
509f91b7e3SAndre Fischer    my $is_current = 0;
519f91b7e3SAndre Fischer    $path = installer::patch::InstallationSet::GetUnpackedExePath(
529f91b7e3SAndre Fischer        $version,
539f91b7e3SAndre Fischer        $is_current_version,
54*60b96b8dSAndre Fischer        installer::languages::get_normalized_language($language),
559f91b7e3SAndre Fischer        "msi",
569f91b7e3SAndre Fischer        $product_name);
579f91b7e3SAndre Fischer
589f91b7e3SAndre Fischer    # Find the msi in the path.ls .
599f91b7e3SAndre Fischer    $filename = File::Spec->catfile($path, "openoffice".$condensed_version.".msi");
609f91b7e3SAndre Fischer    $is_current = $is_current_version;
619f91b7e3SAndre Fischer
629f91b7e3SAndre Fischer    return $class->new($filename, $version, $is_current, $language, $product_name);
639f91b7e3SAndre Fischer}
64c9b362f6SAndre Fischer
65c9b362f6SAndre Fischer
669f91b7e3SAndre Fischer
679f91b7e3SAndre Fischer
689f91b7e3SAndre Fischer
699f91b7e3SAndre Fischer
709f91b7e3SAndre 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*60b96b8dSAndre Fischer
799f91b7e3SAndre Fischersub new ($$$$$$)
80c9b362f6SAndre Fischer{
819f91b7e3SAndre Fischer    my ($class, $filename, $version, $is_current_version, $language, $product_name) = @_;
82c9b362f6SAndre Fischer
83c9b362f6SAndre Fischer    if ( ! -f $filename)
84c9b362f6SAndre Fischer    {
859f91b7e3SAndre Fischer        installer::logger::PrintError("can not find the .msi file for version %s and language %s at '%s'\n",
869f91b7e3SAndre Fischer            $version,
879f91b7e3SAndre Fischer            $language,
889f91b7e3SAndre Fischer            $filename);
89c9b362f6SAndre Fischer        return undef;
90c9b362f6SAndre Fischer    }
91c9b362f6SAndre Fischer
92c9b362f6SAndre Fischer    my $self = {
93c9b362f6SAndre Fischer        'filename' => $filename,
949f91b7e3SAndre Fischer        'path' => dirname($filename),
95c9b362f6SAndre Fischer        'version' => $version,
969f91b7e3SAndre Fischer        'is_current_version' => $is_current_version,
97c9b362f6SAndre Fischer        'language' => $language,
98c9b362f6SAndre Fischer        'package_format' => "msi",
99c9b362f6SAndre Fischer        'product_name' => $product_name,
100c9b362f6SAndre Fischer        'tmpdir' => File::Temp->newdir(CLEANUP => 1),
101c9b362f6SAndre Fischer        'is_valid' => -f $filename
102c9b362f6SAndre Fischer    };
103c9b362f6SAndre Fischer    bless($self, $class);
104c9b362f6SAndre Fischer
105c9b362f6SAndre Fischer    return $self;
106c9b362f6SAndre Fischer}
107c9b362f6SAndre Fischer
108c9b362f6SAndre Fischer
109c9b362f6SAndre Fischer
110c9b362f6SAndre Fischer
111c9b362f6SAndre Fischersub IsValid ($)
112c9b362f6SAndre Fischer{
113c9b362f6SAndre Fischer    my ($self) = @_;
114c9b362f6SAndre Fischer
115c9b362f6SAndre Fischer    return $self->{'is_valid'};
116c9b362f6SAndre Fischer}
117c9b362f6SAndre Fischer
118c9b362f6SAndre Fischer
119c9b362f6SAndre Fischer
120c9b362f6SAndre Fischer
1219f91b7e3SAndre Fischer=head2 Commit($self)
1229f91b7e3SAndre Fischer
1239f91b7e3SAndre Fischer    Write all modified tables back into the databse.
1249f91b7e3SAndre Fischer
1259f91b7e3SAndre Fischer=cut
126*60b96b8dSAndre Fischer
1279f91b7e3SAndre Fischersub Commit ($)
1289f91b7e3SAndre Fischer{
1299f91b7e3SAndre Fischer    my $self = shift;
1309f91b7e3SAndre Fischer
1319f91b7e3SAndre Fischer    my @tables_to_update = ();
1329f91b7e3SAndre Fischer    foreach my $table (values %{$self->{'tables'}})
1339f91b7e3SAndre Fischer    {
1349f91b7e3SAndre Fischer        push @tables_to_update,$table if ($table->IsModified());
1359f91b7e3SAndre Fischer    }
1369f91b7e3SAndre Fischer
1379f91b7e3SAndre Fischer    if (scalar @tables_to_update > 0)
1389f91b7e3SAndre Fischer    {
1399f91b7e3SAndre Fischer        $installer::logger::Info->printf("writing modified tables to database:\n");
1409f91b7e3SAndre Fischer        foreach my $table (@tables_to_update)
1419f91b7e3SAndre Fischer        {
1429f91b7e3SAndre Fischer            $installer::logger::Info->printf("    %s\n", $table->GetName());
1439f91b7e3SAndre Fischer            $self->PutTable($table);
1449f91b7e3SAndre Fischer        }
1459f91b7e3SAndre Fischer
1469f91b7e3SAndre Fischer        foreach my $table (@tables_to_update)
1479f91b7e3SAndre Fischer        {
1489f91b7e3SAndre Fischer            $table->UpdateTimestamp();
1499f91b7e3SAndre Fischer            $table->MarkAsUnmodified();
1509f91b7e3SAndre Fischer        }
1519f91b7e3SAndre Fischer    }
1529f91b7e3SAndre Fischer}
1539f91b7e3SAndre Fischer
1549f91b7e3SAndre Fischer
1559f91b7e3SAndre Fischer
1569f91b7e3SAndre Fischer
157c9b362f6SAndre Fischer=head2 GetTable($seld, $table_name)
158c9b362f6SAndre Fischer
159c9b362f6SAndre Fischer    Return an MsiTable object for $table_name.  Table objects are kept
160c9b362f6SAndre Fischer    alive for the life time of the Msi object.  Therefore the second
161c9b362f6SAndre Fischer    call for the same table is very cheap.
162c9b362f6SAndre Fischer
163c9b362f6SAndre Fischer=cut
164*60b96b8dSAndre Fischer
165c9b362f6SAndre Fischersub GetTable ($$)
166c9b362f6SAndre Fischer{
167c9b362f6SAndre Fischer    my ($self, $table_name) = @_;
168c9b362f6SAndre Fischer
169c9b362f6SAndre Fischer    my $table = $self->{'tables'}->{$table_name};
170c9b362f6SAndre Fischer    if ( ! defined $table)
171c9b362f6SAndre Fischer    {
172c9b362f6SAndre Fischer        my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt");
173c9b362f6SAndre Fischer        if ( ! -f $table_filename
174c9b362f6SAndre Fischer            || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'}))
175c9b362f6SAndre Fischer        {
176c9b362f6SAndre Fischer            # Extract table from database to text file on disk.
177c9b362f6SAndre Fischer            my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name;
178c9b362f6SAndre Fischer            my $command = join(" ",
179c9b362f6SAndre Fischer                "msidb.exe",
1809f91b7e3SAndre Fischer                "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
1819f91b7e3SAndre Fischer                "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
182c9b362f6SAndre Fischer                "-e", $table_name);
183c9b362f6SAndre Fischer            my $result = qx($command);
184c9b362f6SAndre Fischer            print $result;
185c9b362f6SAndre Fischer        }
186c9b362f6SAndre Fischer
187c9b362f6SAndre Fischer        # Read table into memory.
188c9b362f6SAndre Fischer        $table = new installer::patch::MsiTable($table_filename, $table_name);
189c9b362f6SAndre Fischer        $self->{'tables'}->{$table_name} = $table;
190c9b362f6SAndre Fischer    }
191c9b362f6SAndre Fischer
192c9b362f6SAndre Fischer    return $table;
193c9b362f6SAndre Fischer}
194c9b362f6SAndre Fischer
195c9b362f6SAndre Fischer
196c9b362f6SAndre Fischer
197c9b362f6SAndre Fischer
1989f91b7e3SAndre Fischer=head2 PutTable($self, $table)
1999f91b7e3SAndre Fischer
2009f91b7e3SAndre Fischer    Write the given table back to the databse.
2019f91b7e3SAndre Fischer
2029f91b7e3SAndre Fischer=cut
203*60b96b8dSAndre Fischer
2049f91b7e3SAndre Fischersub PutTable ($$)
2059f91b7e3SAndre Fischer{
2069f91b7e3SAndre Fischer    my ($self, $table) = @_;
2079f91b7e3SAndre Fischer
2089f91b7e3SAndre Fischer    # Create text file from the current table content.
2099f91b7e3SAndre Fischer    $table->WriteFile();
2109f91b7e3SAndre Fischer
2119f91b7e3SAndre Fischer    my $table_name = $table->GetName();
2129f91b7e3SAndre Fischer
2139f91b7e3SAndre Fischer    # Store table from text file into database.
2149f91b7e3SAndre Fischer    my $table_filename = $table->{'filename'};
2159f91b7e3SAndre Fischer
2169f91b7e3SAndre Fischer    if (length($table_name) > 8)
2179f91b7e3SAndre Fischer    {
2189f91b7e3SAndre Fischer        # The file name of the table data must not be longer than 8 characters (not counting the extension).
2199f91b7e3SAndre Fischer        # The name passed as argument to the -i option may be longer.
2209f91b7e3SAndre Fischer        my $truncated_table_name = substr($table_name,0,8);
2219f91b7e3SAndre Fischer        my $table_truncated_filename = File::Spec->catfile(
2229f91b7e3SAndre Fischer            dirname($table_filename),
2239f91b7e3SAndre Fischer            $truncated_table_name.".idt");
2249f91b7e3SAndre Fischer        File::Copy::copy($table_filename, $table_truncated_filename) || die("can not create table file with short name");
2259f91b7e3SAndre Fischer    }
2269f91b7e3SAndre Fischer
2279f91b7e3SAndre Fischer    my $command = join(" ",
2289f91b7e3SAndre Fischer        "msidb.exe",
2299f91b7e3SAndre Fischer        "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
2309f91b7e3SAndre Fischer        "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
2319f91b7e3SAndre Fischer        "-i", $table_name);
2329f91b7e3SAndre Fischer    my $result = system($command);
2339f91b7e3SAndre Fischer
2349f91b7e3SAndre Fischer    if ($result != 0)
2359f91b7e3SAndre Fischer    {
2369f91b7e3SAndre Fischer        installer::logger::PrintError("writing table '%s' back to database failed", $table_name);
2379f91b7e3SAndre Fischer        # For error messages see http://msdn.microsoft.com/en-us/library/windows/desktop/aa372835%28v=vs.85%29.aspx
2389f91b7e3SAndre Fischer    }
2399f91b7e3SAndre Fischer}
2409f91b7e3SAndre Fischer
2419f91b7e3SAndre Fischer
2429f91b7e3SAndre Fischer
2439f91b7e3SAndre Fischer
244c9b362f6SAndre Fischer=head2 EnsureAYoungerThanB ($filename_a, $filename_b)
245c9b362f6SAndre Fischer
246c9b362f6SAndre Fischer    Internal function (not a method) that compares to files according
247c9b362f6SAndre Fischer    to their last modification times (mtime).
248c9b362f6SAndre Fischer
249c9b362f6SAndre Fischer=cut
250*60b96b8dSAndre Fischer
251c9b362f6SAndre Fischersub EnsureAYoungerThanB ($$)
252c9b362f6SAndre Fischer{
253c9b362f6SAndre Fischer    my ($filename_a, $filename_b) = @_;
254c9b362f6SAndre Fischer
255c9b362f6SAndre Fischer    die("file $filename_a does not exist") unless -f $filename_a;
256c9b362f6SAndre Fischer    die("file $filename_b does not exist") unless -f $filename_b;
257c9b362f6SAndre Fischer
258c9b362f6SAndre Fischer    my @stat_a = stat($filename_a);
259c9b362f6SAndre Fischer    my @stat_b = stat($filename_b);
260c9b362f6SAndre Fischer
261c9b362f6SAndre Fischer    if ($stat_a[9] <= $stat_b[9])
262c9b362f6SAndre Fischer    {
263c9b362f6SAndre Fischer        return 0;
264c9b362f6SAndre Fischer    }
265c9b362f6SAndre Fischer    else
266c9b362f6SAndre Fischer    {
267c9b362f6SAndre Fischer        return 1;
268c9b362f6SAndre Fischer    }
269c9b362f6SAndre Fischer}
270c9b362f6SAndre Fischer
271c9b362f6SAndre Fischer
272c9b362f6SAndre Fischer
273c9b362f6SAndre Fischer
274c9b362f6SAndre Fischer=head2 SplitLongShortName($name)
275c9b362f6SAndre Fischer
276c9b362f6SAndre Fischer    Split $name (typically from the 'FileName' column in the 'File'
277c9b362f6SAndre Fischer    table or 'DefaultDir' column in the 'Directory' table) at the '|'
278c9b362f6SAndre Fischer    into short (8.3) and long names.  If there is no '|' in $name then
279c9b362f6SAndre Fischer    $name is returned as both short and long name.
280c9b362f6SAndre Fischer
281c9b362f6SAndre Fischer    Returns long and short name (in this order) as array.
282c9b362f6SAndre Fischer
283c9b362f6SAndre Fischer=cut
284*60b96b8dSAndre Fischer
285c9b362f6SAndre Fischersub SplitLongShortName ($)
286c9b362f6SAndre Fischer{
287c9b362f6SAndre Fischer    my ($name) = @_;
288c9b362f6SAndre Fischer
289c9b362f6SAndre Fischer    if ($name =~ /^([^\|]*)\|(.*)$/)
290c9b362f6SAndre Fischer    {
291c9b362f6SAndre Fischer        return ($2,$1);
292c9b362f6SAndre Fischer    }
293c9b362f6SAndre Fischer    else
294c9b362f6SAndre Fischer    {
295c9b362f6SAndre Fischer        return ($name,$name);
296c9b362f6SAndre Fischer    }
297c9b362f6SAndre Fischer}
298c9b362f6SAndre Fischer
299c9b362f6SAndre Fischer
300c9b362f6SAndre Fischer
301c9b362f6SAndre Fischer=head2 SplitTargetSourceLongShortName ($name)
302c9b362f6SAndre Fischer
303c9b362f6SAndre Fischer    Split $name first at the ':' into target and source parts and each
304c9b362f6SAndre Fischer    of those at the '|'s into long and short parts.  Names that follow
305c9b362f6SAndre Fischer    this pattern come from the 'DefaultDir' column in the 'Directory'
306c9b362f6SAndre Fischer    table.
307c9b362f6SAndre Fischer
308c9b362f6SAndre Fischer=cut
309*60b96b8dSAndre Fischer
310c9b362f6SAndre Fischersub SplitTargetSourceLongShortName ($)
311c9b362f6SAndre Fischer{
312c9b362f6SAndre Fischer    my ($name) = @_;
313c9b362f6SAndre Fischer
314c9b362f6SAndre Fischer    if ($name =~ /^([^:]*):(.*)$/)
315c9b362f6SAndre Fischer    {
316c9b362f6SAndre Fischer        return (installer::patch::Msi::SplitLongShortName($1), installer::patch::Msi::SplitLongShortName($2));
317c9b362f6SAndre Fischer    }
318c9b362f6SAndre Fischer    else
319c9b362f6SAndre Fischer    {
320c9b362f6SAndre Fischer        my ($long,$short) = installer::patch::Msi::SplitLongShortName($name);
321c9b362f6SAndre Fischer        return ($long,$short,$long,$short);
322c9b362f6SAndre Fischer    }
323c9b362f6SAndre Fischer}
324c9b362f6SAndre Fischer
325c9b362f6SAndre Fischer
3269f91b7e3SAndre Fischer=head2 GetDirectoryMap($self)
327c9b362f6SAndre Fischer
3289f91b7e3SAndre Fischer    Return a map that maps directory unique names (column 'Directory' in table 'Directory')
3299f91b7e3SAndre Fischer    to hashes that contains short and long source and target names.
330c9b362f6SAndre Fischer
331c9b362f6SAndre Fischer=cut
332*60b96b8dSAndre Fischer
3339f91b7e3SAndre Fischersub GetDirectoryMap ($)
334c9b362f6SAndre Fischer{
335c9b362f6SAndre Fischer    my ($self) = @_;
336c9b362f6SAndre Fischer
3379f91b7e3SAndre Fischer    if (defined $self->{'DirectoryMap'})
338c9b362f6SAndre Fischer    {
3399f91b7e3SAndre Fischer        return $self->{'DirectoryMap'};
340c9b362f6SAndre Fischer    }
341c9b362f6SAndre Fischer
342c9b362f6SAndre Fischer    my $directory_table = $self->GetTable("Directory");
343c9b362f6SAndre Fischer    my %dir_map = ();
344c9b362f6SAndre Fischer    foreach my $row (@{$directory_table->GetAllRows()})
345c9b362f6SAndre Fischer    {
3469f91b7e3SAndre Fischer        my ($target_long_name, $target_short_name, $source_long_name, $source_short_name)
347c9b362f6SAndre Fischer            = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir"));
3489f91b7e3SAndre Fischer        my $unique_name = $row->GetValue("Directory");
3499f91b7e3SAndre Fischer        $dir_map{$unique_name} =
3509f91b7e3SAndre Fischer        {
3519f91b7e3SAndre Fischer            'unique_name' => $unique_name,
352c9b362f6SAndre Fischer            'parent' => $row->GetValue("Directory_Parent"),
3539f91b7e3SAndre Fischer            'default_dir' => $row->GetValue("DefaultDir"),
3549f91b7e3SAndre Fischer            'source_long_name' => $source_long_name,
3559f91b7e3SAndre Fischer            'source_short_name' => $source_short_name,
3569f91b7e3SAndre Fischer            'target_long_name' => $target_long_name,
3579f91b7e3SAndre Fischer            'target_short_name' => $target_short_name
3589f91b7e3SAndre Fischer        };
359c9b362f6SAndre Fischer    }
360c9b362f6SAndre Fischer
361c9b362f6SAndre Fischer    # Set up full names for all directories.
362c9b362f6SAndre Fischer    my @todo = map {$_} (keys %dir_map);
363c9b362f6SAndre Fischer    while (scalar @todo > 0)
364c9b362f6SAndre Fischer    {
365c9b362f6SAndre Fischer        my $key = shift @todo;
366c9b362f6SAndre Fischer        my $item = $dir_map{$key};
367c9b362f6SAndre Fischer        next if defined $item->{'full_source_name'};
368c9b362f6SAndre Fischer
369c9b362f6SAndre Fischer        if ($item->{'parent'} eq "")
370c9b362f6SAndre Fischer        {
371c9b362f6SAndre Fischer            # Directory has no parent => full names are the same as the name.
3729f91b7e3SAndre Fischer            $item->{'full_source_long_name'} = $item->{'source_long_name'};
3739f91b7e3SAndre Fischer            $item->{'full_source_short_name'} = $item->{'source_short_name'};
3749f91b7e3SAndre Fischer            $item->{'full_target_long_name'} = $item->{'target_long_name'};
3759f91b7e3SAndre Fischer            $item->{'full_target_short_name'} = $item->{'target_short_name'};
376c9b362f6SAndre Fischer        }
377c9b362f6SAndre Fischer        else
378c9b362f6SAndre Fischer        {
379c9b362f6SAndre Fischer            my $parent = $dir_map{$item->{'parent'}};
3809f91b7e3SAndre Fischer            if ( defined $parent->{'full_source_long_name'})
381c9b362f6SAndre Fischer            {
382c9b362f6SAndre Fischer                # Parent aleady has full names => we can create the full name of the current item.
3839f91b7e3SAndre Fischer                $item->{'full_source_long_name'}
3849f91b7e3SAndre Fischer                    = $parent->{'full_source_long_name'} . "/" . $item->{'source_long_name'};
3859f91b7e3SAndre Fischer                $item->{'full_source_short_name'}
3869f91b7e3SAndre Fischer                    = $parent->{'full_source_short_name'} . "/" . $item->{'source_short_name'};
3879f91b7e3SAndre Fischer                $item->{'full_target_long_name'}
3889f91b7e3SAndre Fischer                    = $parent->{'full_target_long_name'} . "/" . $item->{'target_long_name'};
3899f91b7e3SAndre Fischer                $item->{'full_target_short_name'}
3909f91b7e3SAndre Fischer                    = $parent->{'full_target_short_name'} . "/" . $item->{'target_short_name'};
391c9b362f6SAndre Fischer            }
392c9b362f6SAndre Fischer            else
393c9b362f6SAndre Fischer            {
394c9b362f6SAndre Fischer                # Parent has to be processed before the current item can be processed.
395c9b362f6SAndre Fischer                # Push both to the head of the list.
396c9b362f6SAndre Fischer                unshift @todo, $key;
397c9b362f6SAndre Fischer                unshift @todo, $item->{'parent'};
398c9b362f6SAndre Fischer            }
399c9b362f6SAndre Fischer        }
400c9b362f6SAndre Fischer    }
401c9b362f6SAndre Fischer
4029f91b7e3SAndre Fischer    # Postprocess the path names for cleanup.
4039f91b7e3SAndre Fischer    foreach my $item (values %dir_map)
404c9b362f6SAndre Fischer    {
4059f91b7e3SAndre Fischer        foreach my $id (
4069f91b7e3SAndre Fischer            'full_source_long_name',
4079f91b7e3SAndre Fischer            'full_source_short_name',
4089f91b7e3SAndre Fischer            'full_target_long_name',
4099f91b7e3SAndre Fischer            'full_target_short_name')
4109f91b7e3SAndre Fischer        {
4119f91b7e3SAndre Fischer            $item->{$id} =~ s/\/(\.\/)+/\//g;
4129f91b7e3SAndre Fischer            $item->{$id} =~ s/^SourceDir\///;
4139f91b7e3SAndre Fischer            $item->{$id} =~ s/^\.$//;
414c9b362f6SAndre Fischer        }
4159f91b7e3SAndre Fischer    }
4169f91b7e3SAndre Fischer
4179f91b7e3SAndre Fischer    $self->{'DirectoryMap'} = \%dir_map;
4189f91b7e3SAndre Fischer    return $self->{'DirectoryMap'};
4199f91b7e3SAndre Fischer}
4209f91b7e3SAndre Fischer
4219f91b7e3SAndre Fischer
4229f91b7e3SAndre Fischer
4239f91b7e3SAndre Fischer
4249f91b7e3SAndre Fischer=head2 GetFileMap ($)
4259f91b7e3SAndre Fischer
4269f91b7e3SAndre Fischer    Return a map (hash) that maps the unique name (column 'File' in
4279f91b7e3SAndre Fischer    the 'File' table) to data that is associated with that file, like
4289f91b7e3SAndre Fischer    the directory or component.
4299f91b7e3SAndre Fischer
4309f91b7e3SAndre Fischer    The map is kept alive for the lifetime of the Msi object.  All
4319f91b7e3SAndre Fischer    calls but the first are cheap.
4329f91b7e3SAndre Fischer
4339f91b7e3SAndre Fischer=cut
434*60b96b8dSAndre Fischer
4359f91b7e3SAndre Fischersub GetFileMap ($)
4369f91b7e3SAndre Fischer{
4379f91b7e3SAndre Fischer    my ($self) = @_;
4389f91b7e3SAndre Fischer
4399f91b7e3SAndre Fischer    if (defined $self->{'FileMap'})
4409f91b7e3SAndre Fischer    {
4419f91b7e3SAndre Fischer        return $self->{'FileMap'};
4429f91b7e3SAndre Fischer    }
4439f91b7e3SAndre Fischer
4449f91b7e3SAndre Fischer    my $file_table = $self->GetTable("File");
4459f91b7e3SAndre Fischer    my $component_table = $self->GetTable("Component");
4469f91b7e3SAndre Fischer    my $dir_map = $self->GetDirectoryMap();
447c9b362f6SAndre Fischer
448c9b362f6SAndre Fischer    # Setup a map from component names to directory items.
4499f91b7e3SAndre Fischer    my %component_to_directory_map =
4509f91b7e3SAndre Fischer        map
4519f91b7e3SAndre Fischer        {$_->GetValue('Component') => $_->GetValue('Directory_')}
4529f91b7e3SAndre Fischer        @{$component_table->GetAllRows()};
453c9b362f6SAndre Fischer
454c9b362f6SAndre Fischer    # Finally, create the map from files to directories.
4559f91b7e3SAndre Fischer    my $file_map = {};
456c9b362f6SAndre Fischer    my $file_component_index = $file_table->GetColumnIndex("Component_");
457c9b362f6SAndre Fischer    my $file_file_index = $file_table->GetColumnIndex("File");
458c9b362f6SAndre Fischer    foreach my $file_row (@{$file_table->GetAllRows()})
459c9b362f6SAndre Fischer    {
460c9b362f6SAndre Fischer        my $component_name = $file_row->GetValue($file_component_index);
461c9b362f6SAndre Fischer        my $directory_name = $component_to_directory_map{$component_name};
462c9b362f6SAndre Fischer        my $unique_name = $file_row->GetValue($file_file_index);
4639f91b7e3SAndre Fischer        $file_map->{$unique_name} = {
4649f91b7e3SAndre Fischer            'directory' => $dir_map->{$directory_name},
4659f91b7e3SAndre Fischer            'component_name' => $component_name
4669f91b7e3SAndre Fischer        };
467c9b362f6SAndre Fischer    }
468c9b362f6SAndre Fischer
4699f91b7e3SAndre Fischer    $self->{'FileMap'} = $file_map;
4709f91b7e3SAndre Fischer    return $file_map;
471c9b362f6SAndre Fischer}
472c9b362f6SAndre Fischer
473c9b362f6SAndre Fischer
474c9b362f6SAndre Fischer1;
475