xref: /AOO41X/main/solenv/bin/modules/installer/patch/Msi.pm (revision 0ede1db122bb4823ff18bce0be06c84d0dd0204f)
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,
5460b96b8dSAndre 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
7860b96b8dSAndre Fischer
79677600b0SAndre 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
105677600b0SAndre Fischer    # Fill in some missing values from the 'Properties' table.
106677600b0SAndre Fischer    if ( ! (defined $version && defined $language && defined $product_name))
107677600b0SAndre Fischer    {
108677600b0SAndre Fischer        my $property_table = $self->GetTable("Property");
109677600b0SAndre Fischer
110677600b0SAndre Fischer        $self->{'version'} = $property_table->GetValue("Property", "DEFINEDVERSION", "Value")
111677600b0SAndre Fischer            unless defined $self->{'version'};
112677600b0SAndre Fischer        $self->{'product_name'} = $property_table->GetValue("Property", "DEFINEDPRODUCT", "Value")
113677600b0SAndre Fischer            unless defined $self->{'product_name'};
114677600b0SAndre Fischer
115677600b0SAndre Fischer        my $language = $property_table->GetValue("Property", "ProductLanguage", "Value");
116677600b0SAndre Fischer        # TODO: Convert numerical language id to language name.
117677600b0SAndre Fischer        $self->{'language'} = $language
118677600b0SAndre Fischer            unless defined $self->{'language'};
119677600b0SAndre Fischer    }
120677600b0SAndre Fischer
121c9b362f6SAndre Fischer    return $self;
122c9b362f6SAndre Fischer}
123c9b362f6SAndre Fischer
124c9b362f6SAndre Fischer
125c9b362f6SAndre Fischer
126c9b362f6SAndre Fischer
127c9b362f6SAndre Fischersub IsValid ($)
128c9b362f6SAndre Fischer{
129c9b362f6SAndre Fischer    my ($self) = @_;
130c9b362f6SAndre Fischer
131c9b362f6SAndre Fischer    return $self->{'is_valid'};
132c9b362f6SAndre Fischer}
133c9b362f6SAndre Fischer
134c9b362f6SAndre Fischer
135c9b362f6SAndre Fischer
136c9b362f6SAndre Fischer
1379f91b7e3SAndre Fischer=head2 Commit($self)
1389f91b7e3SAndre Fischer
139*0ede1db1SMatthias Seidel    Write all modified tables back into the database.
1409f91b7e3SAndre Fischer
1419f91b7e3SAndre Fischer=cut
14260b96b8dSAndre Fischer
1439f91b7e3SAndre Fischersub Commit ($)
1449f91b7e3SAndre Fischer{
1459f91b7e3SAndre Fischer    my $self = shift;
1469f91b7e3SAndre Fischer
1479f91b7e3SAndre Fischer    my @tables_to_update = ();
1489f91b7e3SAndre Fischer    foreach my $table (values %{$self->{'tables'}})
1499f91b7e3SAndre Fischer    {
1509f91b7e3SAndre Fischer        push @tables_to_update,$table if ($table->IsModified());
1519f91b7e3SAndre Fischer    }
1529f91b7e3SAndre Fischer
1539f91b7e3SAndre Fischer    if (scalar @tables_to_update > 0)
1549f91b7e3SAndre Fischer    {
1559f91b7e3SAndre Fischer        $installer::logger::Info->printf("writing modified tables to database:\n");
1569f91b7e3SAndre Fischer        foreach my $table (@tables_to_update)
1579f91b7e3SAndre Fischer        {
1589f91b7e3SAndre Fischer            $installer::logger::Info->printf("    %s\n", $table->GetName());
1599f91b7e3SAndre Fischer            $self->PutTable($table);
1609f91b7e3SAndre Fischer        }
1619f91b7e3SAndre Fischer
1629f91b7e3SAndre Fischer        foreach my $table (@tables_to_update)
1639f91b7e3SAndre Fischer        {
1649f91b7e3SAndre Fischer            $table->UpdateTimestamp();
1659f91b7e3SAndre Fischer            $table->MarkAsUnmodified();
1669f91b7e3SAndre Fischer        }
1679f91b7e3SAndre Fischer    }
1689f91b7e3SAndre Fischer}
1699f91b7e3SAndre Fischer
1709f91b7e3SAndre Fischer
1719f91b7e3SAndre Fischer
1729f91b7e3SAndre Fischer
173c9b362f6SAndre Fischer=head2 GetTable($seld, $table_name)
174c9b362f6SAndre Fischer
175c9b362f6SAndre Fischer    Return an MsiTable object for $table_name. Table objects are kept
176c9b362f6SAndre Fischer    alive for the life time of the Msi object. Therefore the second
177c9b362f6SAndre Fischer    call for the same table is very cheap.
178c9b362f6SAndre Fischer
179c9b362f6SAndre Fischer=cut
18060b96b8dSAndre Fischer
181c9b362f6SAndre Fischersub GetTable ($$)
182c9b362f6SAndre Fischer{
183c9b362f6SAndre Fischer    my ($self, $table_name) = @_;
184c9b362f6SAndre Fischer
185c9b362f6SAndre Fischer    my $table = $self->{'tables'}->{$table_name};
186c9b362f6SAndre Fischer    if ( ! defined $table)
187c9b362f6SAndre Fischer    {
188c9b362f6SAndre Fischer        my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt");
189c9b362f6SAndre Fischer        if ( ! -f $table_filename
190c9b362f6SAndre Fischer            || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'}))
191c9b362f6SAndre Fischer        {
192c9b362f6SAndre Fischer            # Extract table from database to text file on disk.
193c9b362f6SAndre Fischer            my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name;
194c9b362f6SAndre Fischer            my $command = join(" ",
195c9b362f6SAndre Fischer                "msidb.exe",
1969f91b7e3SAndre Fischer                "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
1979f91b7e3SAndre Fischer                "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
198c9b362f6SAndre Fischer                "-e", $table_name);
199c9b362f6SAndre Fischer            my $result = qx($command);
200c9b362f6SAndre Fischer        }
201c9b362f6SAndre Fischer
202c9b362f6SAndre Fischer        # Read table into memory.
203c9b362f6SAndre Fischer        $table = new installer::patch::MsiTable($table_filename, $table_name);
204c9b362f6SAndre Fischer        $self->{'tables'}->{$table_name} = $table;
205c9b362f6SAndre Fischer    }
206c9b362f6SAndre Fischer
207c9b362f6SAndre Fischer    return $table;
208c9b362f6SAndre Fischer}
209c9b362f6SAndre Fischer
210c9b362f6SAndre Fischer
211c9b362f6SAndre Fischer
212c9b362f6SAndre Fischer
2139f91b7e3SAndre Fischer=head2 PutTable($self, $table)
2149f91b7e3SAndre Fischer
215*0ede1db1SMatthias Seidel    Write the given table back to the database.
2169f91b7e3SAndre Fischer
2179f91b7e3SAndre Fischer=cut
21860b96b8dSAndre Fischer
2199f91b7e3SAndre Fischersub PutTable ($$)
2209f91b7e3SAndre Fischer{
2219f91b7e3SAndre Fischer    my ($self, $table) = @_;
2229f91b7e3SAndre Fischer
2239f91b7e3SAndre Fischer    # Create text file from the current table content.
2249f91b7e3SAndre Fischer    $table->WriteFile();
2259f91b7e3SAndre Fischer
2269f91b7e3SAndre Fischer    my $table_name = $table->GetName();
2279f91b7e3SAndre Fischer
2289f91b7e3SAndre Fischer    # Store table from text file into database.
2299f91b7e3SAndre Fischer    my $table_filename = $table->{'filename'};
2309f91b7e3SAndre Fischer
2319f91b7e3SAndre Fischer    if (length($table_name) > 8)
2329f91b7e3SAndre Fischer    {
2339f91b7e3SAndre Fischer        # The file name of the table data must not be longer than 8 characters (not counting the extension).
2349f91b7e3SAndre Fischer        # The name passed as argument to the -i option may be longer.
2359f91b7e3SAndre Fischer        my $truncated_table_name = substr($table_name,0,8);
2369f91b7e3SAndre Fischer        my $table_truncated_filename = File::Spec->catfile(
2379f91b7e3SAndre Fischer            dirname($table_filename),
2389f91b7e3SAndre Fischer            $truncated_table_name.".idt");
2399f91b7e3SAndre Fischer        File::Copy::copy($table_filename, $table_truncated_filename) || die("can not create table file with short name");
2409f91b7e3SAndre Fischer    }
2419f91b7e3SAndre Fischer
2429f91b7e3SAndre Fischer    my $command = join(" ",
2439f91b7e3SAndre Fischer        "msidb.exe",
2449f91b7e3SAndre Fischer        "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
2459f91b7e3SAndre Fischer        "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
2469f91b7e3SAndre Fischer        "-i", $table_name);
2479f91b7e3SAndre Fischer    my $result = system($command);
2489f91b7e3SAndre Fischer
2499f91b7e3SAndre Fischer    if ($result != 0)
2509f91b7e3SAndre Fischer    {
2519f91b7e3SAndre Fischer        installer::logger::PrintError("writing table '%s' back to database failed", $table_name);
2529f91b7e3SAndre Fischer        # For error messages see http://msdn.microsoft.com/en-us/library/windows/desktop/aa372835%28v=vs.85%29.aspx
2539f91b7e3SAndre Fischer    }
2549f91b7e3SAndre Fischer}
2559f91b7e3SAndre Fischer
2569f91b7e3SAndre Fischer
2579f91b7e3SAndre Fischer
2589f91b7e3SAndre Fischer
259c9b362f6SAndre Fischer=head2 EnsureAYoungerThanB ($filename_a, $filename_b)
260c9b362f6SAndre Fischer
261c9b362f6SAndre Fischer    Internal function (not a method) that compares to files according
262c9b362f6SAndre Fischer    to their last modification times (mtime).
263c9b362f6SAndre Fischer
264c9b362f6SAndre Fischer=cut
26560b96b8dSAndre Fischer
266c9b362f6SAndre Fischersub EnsureAYoungerThanB ($$)
267c9b362f6SAndre Fischer{
268c9b362f6SAndre Fischer    my ($filename_a, $filename_b) = @_;
269c9b362f6SAndre Fischer
270c9b362f6SAndre Fischer    die("file $filename_a does not exist") unless -f $filename_a;
271c9b362f6SAndre Fischer    die("file $filename_b does not exist") unless -f $filename_b;
272c9b362f6SAndre Fischer
273c9b362f6SAndre Fischer    my @stat_a = stat($filename_a);
274c9b362f6SAndre Fischer    my @stat_b = stat($filename_b);
275c9b362f6SAndre Fischer
276c9b362f6SAndre Fischer    if ($stat_a[9] <= $stat_b[9])
277c9b362f6SAndre Fischer    {
278c9b362f6SAndre Fischer        return 0;
279c9b362f6SAndre Fischer    }
280c9b362f6SAndre Fischer    else
281c9b362f6SAndre Fischer    {
282c9b362f6SAndre Fischer        return 1;
283c9b362f6SAndre Fischer    }
284c9b362f6SAndre Fischer}
285c9b362f6SAndre Fischer
286c9b362f6SAndre Fischer
287c9b362f6SAndre Fischer
288c9b362f6SAndre Fischer
289c9b362f6SAndre Fischer=head2 SplitLongShortName($name)
290c9b362f6SAndre Fischer
291c9b362f6SAndre Fischer    Split $name (typically from the 'FileName' column in the 'File'
292c9b362f6SAndre Fischer    table or 'DefaultDir' column in the 'Directory' table) at the '|'
293c9b362f6SAndre Fischer    into short (8.3) and long names. If there is no '|' in $name then
294c9b362f6SAndre Fischer    $name is returned as both short and long name.
295c9b362f6SAndre Fischer
296c9b362f6SAndre Fischer    Returns long and short name (in this order) as array.
297c9b362f6SAndre Fischer
298c9b362f6SAndre Fischer=cut
29960b96b8dSAndre Fischer
300c9b362f6SAndre Fischersub SplitLongShortName ($)
301c9b362f6SAndre Fischer{
302c9b362f6SAndre Fischer    my ($name) = @_;
303c9b362f6SAndre Fischer
304c9b362f6SAndre Fischer    if ($name =~ /^([^\|]*)\|(.*)$/)
305c9b362f6SAndre Fischer    {
306c9b362f6SAndre Fischer        return ($2,$1);
307c9b362f6SAndre Fischer    }
308c9b362f6SAndre Fischer    else
309c9b362f6SAndre Fischer    {
310c9b362f6SAndre Fischer        return ($name,$name);
311c9b362f6SAndre Fischer    }
312c9b362f6SAndre Fischer}
313c9b362f6SAndre Fischer
314c9b362f6SAndre Fischer
315c9b362f6SAndre Fischer
316c9b362f6SAndre Fischer=head2 SplitTargetSourceLongShortName ($name)
317c9b362f6SAndre Fischer
318c9b362f6SAndre Fischer    Split $name first at the ':' into target and source parts and each
319c9b362f6SAndre Fischer    of those at the '|'s into long and short parts. Names that follow
320c9b362f6SAndre Fischer    this pattern come from the 'DefaultDir' column in the 'Directory'
321c9b362f6SAndre Fischer    table.
322c9b362f6SAndre Fischer
323c9b362f6SAndre Fischer=cut
32460b96b8dSAndre Fischer
325c9b362f6SAndre Fischersub SplitTargetSourceLongShortName ($)
326c9b362f6SAndre Fischer{
327c9b362f6SAndre Fischer    my ($name) = @_;
328c9b362f6SAndre Fischer
329c9b362f6SAndre Fischer    if ($name =~ /^([^:]*):(.*)$/)
330c9b362f6SAndre Fischer    {
331c9b362f6SAndre Fischer        return (installer::patch::Msi::SplitLongShortName($1), installer::patch::Msi::SplitLongShortName($2));
332c9b362f6SAndre Fischer    }
333c9b362f6SAndre Fischer    else
334c9b362f6SAndre Fischer    {
335c9b362f6SAndre Fischer        my ($long,$short) = installer::patch::Msi::SplitLongShortName($name);
336c9b362f6SAndre Fischer        return ($long,$short,$long,$short);
337c9b362f6SAndre Fischer    }
338c9b362f6SAndre Fischer}
339c9b362f6SAndre Fischer
340c9b362f6SAndre Fischer
341677600b0SAndre Fischer
342677600b0SAndre Fischer
343677600b0SAndre Fischersub SetupFullNames ($$);
344677600b0SAndre Fischersub SetupFullNames ($$)
345677600b0SAndre Fischer{
346677600b0SAndre Fischer    my ($item, $directory_map) = @_;
347677600b0SAndre Fischer
348677600b0SAndre Fischer    # Don't process any item twice.
349677600b0SAndre Fischer    return if defined $item->{'full_source_name'};
350677600b0SAndre Fischer
351677600b0SAndre Fischer    my $parent = $item->{'parent'};
352677600b0SAndre Fischer    if (defined $parent)
353677600b0SAndre Fischer    {
354677600b0SAndre Fischer        # Process the parent first.
355677600b0SAndre Fischer        if ( ! defined $parent->{'full_source_long_name'})
356677600b0SAndre Fischer        {
357677600b0SAndre Fischer            SetupFullNames($parent, $directory_map);
358677600b0SAndre Fischer        }
359677600b0SAndre Fischer
360677600b0SAndre Fischer        # Prepend the full names of the parent to our names.
361677600b0SAndre Fischer        $item->{'full_source_long_name'}
362677600b0SAndre Fischer            = $parent->{'full_source_long_name'} . "/" . $item->{'source_long_name'};
363677600b0SAndre Fischer        $item->{'full_source_short_name'}
364677600b0SAndre Fischer            = $parent->{'full_source_short_name'} . "/" . $item->{'source_short_name'};
365677600b0SAndre Fischer        $item->{'full_target_long_name'}
366677600b0SAndre Fischer            = $parent->{'full_target_long_name'} . "/" . $item->{'target_long_name'};
367677600b0SAndre Fischer        $item->{'full_target_short_name'}
368677600b0SAndre Fischer            = $parent->{'full_target_short_name'} . "/" . $item->{'target_short_name'};
369677600b0SAndre Fischer    }
370677600b0SAndre Fischer    else
371677600b0SAndre Fischer    {
372677600b0SAndre Fischer        # Directory has no parent => full names are the same as the name.
373677600b0SAndre Fischer        $item->{'full_source_long_name'} = $item->{'source_long_name'};
374677600b0SAndre Fischer        $item->{'full_source_short_name'} = $item->{'source_short_name'};
375677600b0SAndre Fischer        $item->{'full_target_long_name'} = $item->{'target_long_name'};
376677600b0SAndre Fischer        $item->{'full_target_short_name'} = $item->{'target_short_name'};
377677600b0SAndre Fischer    }
378677600b0SAndre Fischer}
379677600b0SAndre Fischer
380677600b0SAndre Fischer
381677600b0SAndre Fischer
382677600b0SAndre Fischer
3839f91b7e3SAndre Fischer=head2 GetDirectoryMap($self)
384c9b362f6SAndre Fischer
3859f91b7e3SAndre Fischer    Return a map that maps directory unique names (column 'Directory' in table 'Directory')
3869f91b7e3SAndre Fischer    to hashes that contains short and long source and target names.
387c9b362f6SAndre Fischer
388c9b362f6SAndre Fischer=cut
38960b96b8dSAndre Fischer
3909f91b7e3SAndre Fischersub GetDirectoryMap ($)
391c9b362f6SAndre Fischer{
392c9b362f6SAndre Fischer    my ($self) = @_;
393c9b362f6SAndre Fischer
3949f91b7e3SAndre Fischer    if (defined $self->{'DirectoryMap'})
395c9b362f6SAndre Fischer    {
3969f91b7e3SAndre Fischer        return $self->{'DirectoryMap'};
397c9b362f6SAndre Fischer    }
398c9b362f6SAndre Fischer
399677600b0SAndre Fischer    # Initialize the directory map.
400c9b362f6SAndre Fischer    my $directory_table = $self->GetTable("Directory");
401677600b0SAndre Fischer    my $directory_map = ();
402c9b362f6SAndre Fischer    foreach my $row (@{$directory_table->GetAllRows()})
403c9b362f6SAndre Fischer    {
4049f91b7e3SAndre Fischer        my ($target_long_name, $target_short_name, $source_long_name, $source_short_name)
405c9b362f6SAndre Fischer            = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir"));
4069f91b7e3SAndre Fischer        my $unique_name = $row->GetValue("Directory");
407677600b0SAndre Fischer        $directory_map->{$unique_name} =
4089f91b7e3SAndre Fischer        {
4099f91b7e3SAndre Fischer            'unique_name' => $unique_name,
410677600b0SAndre Fischer            'parent_name' => $row->GetValue("Directory_Parent"),
4119f91b7e3SAndre Fischer            'default_dir' => $row->GetValue("DefaultDir"),
4129f91b7e3SAndre Fischer            'source_long_name' => $source_long_name,
4139f91b7e3SAndre Fischer            'source_short_name' => $source_short_name,
4149f91b7e3SAndre Fischer            'target_long_name' => $target_long_name,
4159f91b7e3SAndre Fischer            'target_short_name' => $target_short_name
4169f91b7e3SAndre Fischer        };
417c9b362f6SAndre Fischer    }
418c9b362f6SAndre Fischer
419677600b0SAndre Fischer    # Add references to parent directories.
420677600b0SAndre Fischer    foreach my $item (values %$directory_map)
421677600b0SAndre Fischer    {
422677600b0SAndre Fischer        $item->{'parent'} = $directory_map->{$item->{'parent_name'}};
423677600b0SAndre Fischer    }
424677600b0SAndre Fischer
425c9b362f6SAndre Fischer    # Set up full names for all directories.
426677600b0SAndre Fischer    foreach my $item (values %$directory_map)
427c9b362f6SAndre Fischer    {
428677600b0SAndre Fischer        SetupFullNames($item, $directory_map);
429c9b362f6SAndre Fischer    }
430c9b362f6SAndre Fischer
431677600b0SAndre Fischer    # Cleanup the names.
432677600b0SAndre Fischer    foreach my $item (values %$directory_map)
433c9b362f6SAndre Fischer    {
4349f91b7e3SAndre Fischer        foreach my $id (
4359f91b7e3SAndre Fischer            'full_source_long_name',
4369f91b7e3SAndre Fischer            'full_source_short_name',
4379f91b7e3SAndre Fischer            'full_target_long_name',
4389f91b7e3SAndre Fischer            'full_target_short_name')
4399f91b7e3SAndre Fischer        {
4409f91b7e3SAndre Fischer            $item->{$id} =~ s/\/(\.\/)+/\//g;
4419f91b7e3SAndre Fischer            $item->{$id} =~ s/^SourceDir\///;
4429f91b7e3SAndre Fischer            $item->{$id} =~ s/^\.$//;
443c9b362f6SAndre Fischer        }
4449f91b7e3SAndre Fischer    }
4459f91b7e3SAndre Fischer
446677600b0SAndre Fischer    $self->{'DirectoryMap'} = $directory_map;
4479f91b7e3SAndre Fischer    return $self->{'DirectoryMap'};
4489f91b7e3SAndre Fischer}
4499f91b7e3SAndre Fischer
4509f91b7e3SAndre Fischer
4519f91b7e3SAndre Fischer
4529f91b7e3SAndre Fischer
4539f91b7e3SAndre Fischer=head2 GetFileMap ($)
4549f91b7e3SAndre Fischer
4559f91b7e3SAndre Fischer    Return a map (hash) that maps the unique name (column 'File' in
4569f91b7e3SAndre Fischer    the 'File' table) to data that is associated with that file, like
4579f91b7e3SAndre Fischer    the directory or component.
4589f91b7e3SAndre Fischer
4599f91b7e3SAndre Fischer    The map is kept alive for the lifetime of the Msi object.  All
4609f91b7e3SAndre Fischer    calls but the first are cheap.
4619f91b7e3SAndre Fischer
4629f91b7e3SAndre Fischer=cut
46360b96b8dSAndre Fischer
4649f91b7e3SAndre Fischersub GetFileMap ($)
4659f91b7e3SAndre Fischer{
4669f91b7e3SAndre Fischer    my ($self) = @_;
4679f91b7e3SAndre Fischer
4689f91b7e3SAndre Fischer    if (defined $self->{'FileMap'})
4699f91b7e3SAndre Fischer    {
4709f91b7e3SAndre Fischer        return $self->{'FileMap'};
4719f91b7e3SAndre Fischer    }
4729f91b7e3SAndre Fischer
4739f91b7e3SAndre Fischer    my $file_table = $self->GetTable("File");
4749f91b7e3SAndre Fischer    my $component_table = $self->GetTable("Component");
4759f91b7e3SAndre Fischer    my $dir_map = $self->GetDirectoryMap();
476c9b362f6SAndre Fischer
477c9b362f6SAndre Fischer    # Setup a map from component names to directory items.
4789f91b7e3SAndre Fischer    my %component_to_directory_map =
4799f91b7e3SAndre Fischer        map
4809f91b7e3SAndre Fischer        {$_->GetValue('Component') => $_->GetValue('Directory_')}
4819f91b7e3SAndre Fischer        @{$component_table->GetAllRows()};
482c9b362f6SAndre Fischer
483c9b362f6SAndre Fischer    # Finally, create the map from files to directories.
4849f91b7e3SAndre Fischer    my $file_map = {};
485c9b362f6SAndre Fischer    my $file_component_index = $file_table->GetColumnIndex("Component_");
486c9b362f6SAndre Fischer    my $file_file_index = $file_table->GetColumnIndex("File");
487677600b0SAndre Fischer    my $file_filename_index = $file_table->GetColumnIndex("FileName");
488c9b362f6SAndre Fischer    foreach my $file_row (@{$file_table->GetAllRows()})
489c9b362f6SAndre Fischer    {
490c9b362f6SAndre Fischer        my $component_name = $file_row->GetValue($file_component_index);
491c9b362f6SAndre Fischer        my $directory_name = $component_to_directory_map{$component_name};
492c9b362f6SAndre Fischer        my $unique_name = $file_row->GetValue($file_file_index);
493677600b0SAndre Fischer        my $file_name = $file_row->GetValue($file_filename_index);
494677600b0SAndre Fischer        my ($long_name, $short_name) = SplitLongShortName($file_name);
4959f91b7e3SAndre Fischer        $file_map->{$unique_name} = {
4969f91b7e3SAndre Fischer            'directory' => $dir_map->{$directory_name},
497677600b0SAndre Fischer            'component_name' => $component_name,
498677600b0SAndre Fischer            'file_name' => $file_name,
499677600b0SAndre Fischer            'long_name' => $long_name,
500677600b0SAndre Fischer            'short_name' => $short_name
5019f91b7e3SAndre Fischer        };
502c9b362f6SAndre Fischer    }
503c9b362f6SAndre Fischer
5049f91b7e3SAndre Fischer    $self->{'FileMap'} = $file_map;
5059f91b7e3SAndre Fischer    return $file_map;
506c9b362f6SAndre Fischer}
507c9b362f6SAndre Fischer
508c9b362f6SAndre Fischer
509c9b362f6SAndre Fischer1;
510