xref: /AOO41X/main/solenv/bin/modules/installer/windows/file.pm (revision 677600b012ebe37b797093a198c8d8edc3d08429)
1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22
23
24package installer::windows::file;
25
26use Digest::MD5;
27use installer::existence;
28use installer::exiter;
29use installer::files;
30use installer::globals;
31use installer::logger;
32use installer::pathanalyzer;
33use installer::worker;
34use installer::windows::font;
35use installer::windows::idtglobal;
36use installer::windows::msiglobal;
37use installer::windows::language;
38use installer::patch::InstallationSet;
39use installer::patch::FileSequenceList;
40use File::Basename;
41use File::Spec;
42use strict;
43
44##########################################################################
45# Assigning one cabinet file to each file. This is requrired,
46# if cabinet files shall be equivalent to packages.
47##########################################################################
48
49sub assign_cab_to_files
50{
51    my ( $filesref ) = @_;
52
53    my $infoline = "";
54
55    foreach my $file (@$filesref)
56    {
57        if ( ! exists($file->{'modules'}) )
58        {
59            installer::exiter::exit_program(
60                sprintf("ERROR: No module assignment found for %s", $file->{'gid'}),
61                "assign_cab_to_files");
62        }
63        my $module = $file->{'modules'};
64        # If modules contains a list of modules, only taking the first one.
65        if ( $module =~ /^\s*(.*?)\,/ ) { $module = $1; }
66
67        if ( ! exists($installer::globals::allcabinetassigns{$module}) )
68        {
69            installer::exiter::exit_program(
70                sprintf("ERROR: No cabinet file assigned to module \"%s\" %s",
71                    $module,
72                    $file->{'gid'}),
73                "assign_cab_to_files");
74        }
75        $file->{'assignedcabinetfile'} = $installer::globals::allcabinetassigns{$module};
76
77        # Counting the files in each cabinet file
78        if ( ! exists($installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}}) )
79        {
80            $installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}} = 1;
81        }
82        else
83        {
84            $installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}}++;
85        }
86    }
87
88    # assigning startsequencenumbers for each cab file
89
90    my %count = ();
91    my $offset = 1;
92    foreach my $cabfile ( sort keys %installer::globals::cabfilecounter )
93    {
94        my $filecount = $installer::globals::cabfilecounter{$cabfile};
95        $count{$cabfile} = $filecount;
96        $installer::globals::cabfilecounter{$cabfile} = $offset;
97        $offset = $offset + $filecount;
98
99        $installer::globals::lastsequence{$cabfile} = $offset - 1;
100    }
101
102    # logging the number of files in each cabinet file
103
104    $installer::logger::Lang->print("\n");
105    $installer::logger::Lang->print("Cabinet files:\n");
106    foreach my $cabfile (sort keys %installer::globals::cabfilecounter)
107    {
108        $installer::logger::Lang->printf(
109            "%-30s : %4s files, from %4d to %4d\n",
110            $cabfile,
111            $count{$cabfile},
112            $installer::globals::cabfilecounter{$cabfile},
113            $installer::globals::lastsequence{$cabfile});
114    }
115}
116
117##########################################################################
118# Assigning sequencenumbers to files. This is requrired,
119# if cabinet files shall be equivalent to packages.
120##########################################################################
121
122sub assign_sequencenumbers_to_files
123{
124    my ( $filesref ) = @_;
125
126    my %directaccess = ();
127    my %allassigns = ();
128
129    for ( my $i = 0; $i <= $#{$filesref}; $i++ )
130    {
131        my $onefile = ${$filesref}[$i];
132
133        # Keeping order in cabinet files
134        # -> collecting all files in one cabinet file
135        # -> sorting files and assigning numbers
136
137        # Saving counter $i for direct access into files array
138        # "destination" of the file is a unique identifier ('Name' is not unique!)
139        if ( exists($directaccess{$onefile->{'destination'}}) ) { installer::exiter::exit_program("ERROR: 'destination' at file not unique: $onefile->{'destination'}", "assign_sequencenumbers_to_files"); }
140        $directaccess{$onefile->{'destination'}} = $i;
141
142        my $cabfilename = $onefile->{'assignedcabinetfile'};
143        # collecting files in cabinet files
144        if ( ! exists($allassigns{$cabfilename}) )
145        {
146            my %onecabfile = ();
147            $onecabfile{$onefile->{'destination'}} = 1;
148            $allassigns{$cabfilename} = \%onecabfile;
149        }
150        else
151        {
152            $allassigns{$cabfilename}->{$onefile->{'destination'}} = 1;
153        }
154    }
155
156    # Sorting each hash and assigning numbers
157    # The destination of the file determines the sort order, not the filename!
158    my $cabfile;
159    foreach $cabfile ( sort keys %allassigns )
160    {
161        my $counter = $installer::globals::cabfilecounter{$cabfile};
162        my $dest;
163        foreach $dest ( sort keys %{$allassigns{$cabfile}} ) # <- sorting the destination!
164        {
165            my $directaccessnumber = $directaccess{$dest};
166            ${$filesref}[$directaccessnumber]->{'assignedsequencenumber'} = $counter;
167            $counter++;
168        }
169    }
170}
171
172#########################################################
173# Create a shorter version of a long component name,
174# because maximum length in msi database is 72.
175# Attention: In multi msi installation sets, the short
176# names have to be unique over all packages, because
177# this string is used to create the globally unique id
178# -> no resetting of
179# %installer::globals::allshortcomponents
180# after a package was created.
181# Using no counter because of reproducibility.
182#########################################################
183
184sub generate_new_short_componentname
185{
186    my ($componentname) = @_;
187
188    my $startversion = substr($componentname, 0, 60); # taking only the first 60 characters
189    my $subid = installer::windows::msiglobal::calculate_id($componentname, 9); # taking only the first 9 digits
190    my $shortcomponentname = $startversion . "_" . $subid;
191
192    if ( exists($installer::globals::allshortcomponents{$shortcomponentname}) ) { installer::exiter::exit_program("Failed to create unique component name: \"$shortcomponentname\"", "generate_new_short_componentname"); }
193
194    $installer::globals::allshortcomponents{$shortcomponentname} = 1;
195
196    return $shortcomponentname;
197}
198
199###############################################
200# Generating the component name from a file
201###############################################
202
203sub get_file_component_name
204{
205    my ($fileref, $filesref) = @_;
206
207    my $componentname = "";
208
209    # Special handling for files with ASSIGNCOMPOMENT
210
211    my $styles = "";
212    if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; }
213    if ( $styles =~ /\bASSIGNCOMPOMENT\b/ )
214    {
215        $componentname = get_component_from_assigned_file($fileref->{'AssignComponent'}, $filesref);
216    }
217    else
218    {
219        # In this function exists the rule to create components from files
220        # Rule:
221        # Two files get the same componentid, if:
222        # both have the same destination directory.
223        # both have the same "gid" -> both were packed in the same zip file
224        # All other files are included into different components!
225
226        # my $componentname = $fileref->{'gid'} . "_" . $fileref->{'Dir'};
227
228        # $fileref->{'Dir'} is not sufficient! All files in a zip file have the same $fileref->{'Dir'},
229        # but can be in different subdirectories.
230        # Solution: destination=share\Scripts\beanshell\Capitalise\capitalise.bsh
231        # in which the filename (capitalise.bsh) has to be removed and all backslashes (slashes) are
232        # converted into underline.
233
234        my $destination = $fileref->{'destination'};
235        installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination);
236        $destination =~ s/\s//g;
237        $destination =~ s/\\/\_/g;
238        $destination =~ s/\//\_/g;
239        $destination =~ s/\_\s*$//g;    # removing ending underline
240
241        $componentname = $fileref->{'gid'} . "__" . $destination;
242
243        # Files with different languages, need to be packed into different components.
244        # Then the installation of the language specific component is determined by a language condition.
245
246        if ( $fileref->{'ismultilingual'} )
247        {
248            my $officelanguage = $fileref->{'specificlanguage'};
249            $componentname = $componentname . "_" . $officelanguage;
250        }
251
252        $componentname = lc($componentname);    # componentnames always lowercase
253
254        $componentname =~ s/\-/\_/g;            # converting "-" to "_"
255        $componentname =~ s/\./\_/g;            # converting "-" to "_"
256
257        # Attention: Maximum length for the componentname is 72
258        # %installer::globals::allcomponents_in_this_database : resetted for each database
259        # %installer::globals::allcomponents : not resetted for each database
260        # Component strings must be unique for the complete product, because they are used for
261        # the creation of the globally unique identifier.
262
263        my $fullname = $componentname;  # This can be longer than 72
264
265        if (( exists($installer::globals::allcomponents{$fullname}) ) && ( ! exists($installer::globals::allcomponents_in_this_database{$fullname}) ))
266        {
267            # This is not allowed: One component cannot be installed with different packages.
268            installer::exiter::exit_program("ERROR: Component \"$fullname\" is already included into another package. This is not allowed.", "get_file_component_name");
269        }
270
271        if ( exists($installer::globals::allcomponents{$fullname}) )
272        {
273            $componentname = $installer::globals::allcomponents{$fullname};
274        }
275        else
276        {
277            if ( length($componentname) > 70 )
278            {
279                $componentname = generate_new_short_componentname($componentname); # This has to be unique for the complete product, not only one package
280            }
281
282            $installer::globals::allcomponents{$fullname} = $componentname;
283            $installer::globals::allcomponents_in_this_database{$fullname} = 1;
284        }
285
286        # $componentname =~ s/gid_file_/g_f_/g;
287        # $componentname =~ s/_extra_/_e_/g;
288        # $componentname =~ s/_config_/_c_/g;
289        # $componentname =~ s/_org_openoffice_/_o_o_/g;
290        # $componentname =~ s/_program_/_p_/g;
291        # $componentname =~ s/_typedetection_/_td_/g;
292        # $componentname =~ s/_linguistic_/_l_/g;
293        # $componentname =~ s/_module_/_m_/g;
294        # $componentname =~ s/_optional_/_opt_/g;
295        # $componentname =~ s/_packages/_pack/g;
296        # $componentname =~ s/_menubar/_mb/g;
297        # $componentname =~ s/_common_/_cm_/g;
298        # $componentname =~ s/_export_/_exp_/g;
299        # $componentname =~ s/_table_/_tb_/g;
300        # $componentname =~ s/_sofficecfg_/_sc_/g;
301        # $componentname =~ s/_soffice_cfg_/_sc_/g;
302        # $componentname =~ s/_startmodulecommands_/_smc_/g;
303        # $componentname =~ s/_drawimpresscommands_/_dic_/g;
304        # $componentname =~ s/_basiccommands_/_bac_/g;
305        # $componentname =~ s/_basicidecommands_/_baic_/g;
306        # $componentname =~ s/_genericcommands_/_genc_/g;
307        # $componentname =~ s/_bibliographycommands_/_bibc_/g;
308        # $componentname =~ s/_gentiumbookbasicbolditalic_/_gbbbi_/g;
309        # $componentname =~ s/_share_/_s_/g;
310        # $componentname =~ s/_extension_/_ext_/g;
311        # $componentname =~ s/_extensions_/_exs_/g;
312        # $componentname =~ s/_modules_/_ms_/g;
313        # $componentname =~ s/_uiconfig_zip_/_ucz_/g;
314        # $componentname =~ s/_productivity_/_pr_/g;
315        # $componentname =~ s/_wizard_/_wz_/g;
316        # $componentname =~ s/_import_/_im_/g;
317        # $componentname =~ s/_javascript_/_js_/g;
318        # $componentname =~ s/_template_/_tpl_/g;
319        # $componentname =~ s/_tplwizletter_/_twl_/g;
320        # $componentname =~ s/_beanshell_/_bs_/g;
321        # $componentname =~ s/_presentation_/_bs_/g;
322        # $componentname =~ s/_columns_/_cls_/g;
323        # $componentname =~ s/_python_/_py_/g;
324
325        # $componentname =~ s/_tools/_ts/g;
326        # $componentname =~ s/_transitions/_trs/g;
327        # $componentname =~ s/_scriptbinding/_scrb/g;
328        # $componentname =~ s/_spreadsheet/_ssh/g;
329        # $componentname =~ s/_publisher/_pub/g;
330        # $componentname =~ s/_presenter/_pre/g;
331        # $componentname =~ s/_registry/_reg/g;
332
333        # $componentname =~ s/screen/sc/g;
334        # $componentname =~ s/wordml/wm/g;
335        # $componentname =~ s/openoffice/oo/g;
336    }
337
338    return $componentname;
339}
340
341####################################################################
342# Returning the component name for a defined file gid.
343# This is necessary for files with flag ASSIGNCOMPOMENT
344####################################################################
345
346sub get_component_from_assigned_file
347{
348    my ($gid, $filesref) = @_;
349
350    my $onefile = installer::existence::get_specified_file($filesref, $gid);
351    my $componentname = "";
352    if ( $onefile->{'componentname'} ) { $componentname = $onefile->{'componentname'}; }
353    else { installer::exiter::exit_program("ERROR: No component defined for file: $gid", "get_component_from_assigned_file"); }
354
355    return $componentname;
356}
357
358####################################################################
359# Generating the special filename for the database file File.idt
360# Sample: CONTEXTS, CONTEXTS1
361# This name has to be unique.
362# In most cases this is simply the filename.
363####################################################################
364
365sub generate_unique_filename_for_filetable ($)
366{
367    my ($oldname) = @_;
368
369    # This new filename has to be saved into $fileref, because this is needed to find the source.
370    # The filename sbasic.idx/OFFSETS is changed to OFFSETS, but OFFSETS is not unique.
371    # In this procedure names like OFFSETS5 are produced. And exactly this string has to be added to
372    # the array of all files.
373
374    my $uniquefilename = $oldname;
375    if ( ! defined $uniquefilename || $uniquefilename eq "")
376    {
377        installer::logger::PrintError("file name does not exist or is empty, can not create unique name for it.");
378        die;
379        return;
380    }
381
382    # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs
383    installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$uniquefilename);
384
385    $uniquefilename =~ s/\-/\_/g;       # no "-" allowed
386    $uniquefilename =~ s/\@/\_/g;       # no "@" allowed
387    $uniquefilename =~ s/\$/\_/g;       # no "$" allowed
388    $uniquefilename =~ s/^\s*\./\_/g;       # no "." at the beginning allowed allowed
389    $uniquefilename =~ s/^\s*\d/\_d/g;      # no number at the beginning allowed allowed (even file "0.gif", replacing to "_d.gif")
390    $uniquefilename =~ s/org_openoffice_/ooo_/g;    # shorten the unique file name
391
392    my $lcuniquefilename = lc($uniquefilename); # only lowercase names
393
394    my $newname = 0;
395
396    if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename}))
397    {
398        $installer::globals::alluniquefilenames{$uniquefilename} = 1;
399        $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1;
400        $newname = 1;
401    }
402
403    if ( ! $newname )
404    {
405        # adding a number until the name is really unique: OFFSETS, OFFSETS1, OFFSETS2, ...
406        # But attention: Making "abc.xcu" to "abc1.xcu"
407
408        my $uniquefilenamebase = $uniquefilename;
409
410        my $counter = 0;
411        do
412        {
413            $counter++;
414
415            if ( $uniquefilenamebase =~ /\./ )
416            {
417                $uniquefilename = $uniquefilenamebase;
418                $uniquefilename =~ s/\./$counter\./;
419            }
420            else
421            {
422                $uniquefilename = $uniquefilenamebase . $counter;
423            }
424
425            $newname = 0;
426            $lcuniquefilename = lc($uniquefilename);    # only lowercase names
427
428            if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename}))
429            {
430                $installer::globals::alluniquefilenames{$uniquefilename} = 1;
431                $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1;
432                $newname = 1;
433            }
434        }
435        until ( $newname )
436    }
437
438    return $uniquefilename;
439}
440
441####################################################################
442# Generating the special file column for the database file File.idt
443# Sample: NAMETR~1.TAB|.nametranslation.table
444# The first part has to be 8.3 conform.
445####################################################################
446
447sub generate_filename_for_filetable ($$)
448{
449    my ($fileref, $shortnamesref) = @_;
450
451    my $returnstring = "";
452
453    my $filename = $fileref->{'Name'};
454
455    # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs
456    installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$filename);
457
458    my $shortstring = installer::windows::idtglobal::make_eight_three_conform_with_hash($filename, "file", $shortnamesref);
459
460    if ( $shortstring eq $filename )
461    {
462        # nothing changed
463        $returnstring = $filename;
464    }
465    else
466    {
467        $returnstring = $shortstring . "\|" . $filename;
468    }
469
470    return $returnstring;
471}
472
473#########################################
474# Returning the filesize of a file
475#########################################
476
477sub get_filesize
478{
479    my ($fileref) = @_;
480
481    my $file = $fileref->{'sourcepath'};
482
483    my $filesize;
484
485    if ( -f $file ) # test of existence. For instance services.rdb does not always exist
486    {
487        $filesize = ( -s $file );   # file size can be "0"
488    }
489    else
490    {
491        $filesize = -1;
492    }
493
494    return $filesize;
495}
496
497#############################################
498# Returning the file version, if required
499# Sample: "8.0.1.8976";
500#############################################
501
502sub get_fileversion
503{
504    my ($onefile, $allvariables) = @_;
505
506    my $fileversion = "";
507
508    if ( $allvariables->{'USE_FILEVERSION'} )
509    {
510        if ( ! $allvariables->{'LIBRARYVERSION'} )
511        {
512            installer::exiter::exit_program("ERROR: USE_FILEVERSION is set, but not LIBRARYVERSION", "get_fileversion");
513        }
514        my $libraryversion = $allvariables->{'LIBRARYVERSION'};
515        if ( $libraryversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ )
516        {
517            my $major = $1;
518            my $minor = $2;
519            my $micro = $3;
520            my $concat = 100 * $minor + $micro;
521            $libraryversion = $major . "\." . $concat;
522        }
523        my $vendornumber = 0;
524        if ( $allvariables->{'VENDORPATCHVERSION'} )
525        {
526            $vendornumber = $allvariables->{'VENDORPATCHVERSION'};
527        }
528        $fileversion = $libraryversion . "\." . $installer::globals::buildid . "\." . $vendornumber;
529        if ( $onefile->{'FileVersion'} )
530        {
531            # overriding FileVersion in scp
532            $fileversion = $onefile->{'FileVersion'};
533        }
534    }
535
536    if ( $installer::globals::prepare_winpatch )
537    {
538        # Windows patches do not allow this version # -> who says so?
539        $fileversion = "";
540    }
541
542    return $fileversion;
543}
544
545
546
547
548sub retrieve_sequence_and_uniquename ($$)
549{
550    my ($file_list, $source_data) = @_;
551
552    my @added_files = ();
553
554    # Read the sequence numbers of the previous version.
555    if ($installer::globals::is_release)
556    {
557        foreach my $file (@$file_list)
558        {
559            # Use the source path of the file as key to retrieve sequence number and unique name.
560            # The source path is the part of the 'destination' without the first part.
561            # There is a special case when 'Dir' is PREDEFINED_OSSHELLNEWDIR.
562            my $source_path;
563            if (defined $file->{'Dir'} && $file->{'Dir'} eq "PREDEFINED_OSSHELLNEWDIR")
564            {
565                $source_path = $installer::globals::templatefoldername
566                    . $installer::globals::separator
567                    . $file->{'Name'};
568            }
569            else
570            {
571                $source_path = $file->{'destination'};
572                $source_path =~ s/^[^\/]+\///;
573            }
574            my ($sequence, $uniquename) = $source_data->get_sequence_and_unique_name($source_path);
575            if (defined $sequence && defined $uniquename)
576            {
577                $file->{'sequencenumber'} = $sequence;
578                $file->{'uniquename'} = $uniquename;
579            }
580            else
581            {
582                # No data found in the source release.  File has been added.
583                push @added_files, $file;
584            }
585        }
586    }
587
588    return @added_files;
589}
590
591
592
593
594=head2 assign_mssing_sequence_numbers ($file_list)
595
596    Assign sequence numbers where still missing.
597
598    When we are preparing a patch then all files that have no sequence numbers
599    at this point are new.  Otherwise no file has a sequence number yet.
600
601=cut
602sub assign_missing_sequence_numbers ($)
603{
604    my ($file_list) = @_;
605
606    # First, set up a hash on the sequence numbers that are already in use.
607    my %used_sequence_numbers = ();
608    foreach my $file (@$file_list)
609    {
610        next unless defined $file->{'sequencenumber'};
611        $used_sequence_numbers{$file->{'sequencenumber'}} = 1;
612    }
613
614    # Assign sequence numbers.  Try consecutive numbers, starting at 1.
615    my $current_sequence_number = 1;
616    foreach my $file (@$file_list)
617    {
618        # Skip over all files that already have sequence numbers.
619        next if defined $file->{'sequencenumber'};
620
621        # Find the next available number.
622        while (defined $used_sequence_numbers{$current_sequence_number})
623        {
624            ++$current_sequence_number;
625        }
626
627        # Use the number and mark it as used.
628        $file->{'sequencenumber'} = $current_sequence_number;
629        $used_sequence_numbers{$current_sequence_number} = 1;
630    }
631}
632
633
634
635
636sub create_items_for_missing_files ($$$)
637{
638    my ($missing_items, $source_msi, $directory_list) = @_;
639
640    # For creation of the FeatureComponent table (in a later step) we
641    # have to provide references from the file to component and
642    # modules (ie features).  Note that Each file belongs to exactly
643    # one component but one component can belong to multiple features.
644    my $component_to_features_map = create_feature_component_map($source_msi);
645
646    my @new_files = ();
647    foreach my $row (@$missing_items)
648    {
649        $installer::logger::Info->printf("creating new file item for '%s'\n", $row->GetValue('File'));
650        my $file_item = create_script_item_for_deleted_file($row, $source_msi, $component_to_features_map);
651        push @new_files, $file_item;
652    }
653
654    return @new_files;
655}
656
657
658
659
660=head2 create_script_item_for_deleted_file (($file_row, $source_msi, $component_to_features_map)
661
662    Create a new script item for a file that was present in the
663    previous release but isn't anymore.  Most of the necessary
664    information is taken from the 'File' table of the source release.
665
666    The values of 'sourcepath' and 'cyg_sourcepath' will point to the
667    respective file in the unpacked source release.  An alternative
668    would be to let them point to an empty file.  That, however, might
669    make the patch bigger (diff between identical file contents is
670    (almost) empty, diff between file and empty file is the 'inverse'
671    of the file).
672
673=cut
674
675my $use_source_files_for_missing_files = 1;
676
677sub create_script_item_for_deleted_file ($$$)
678{
679    my ($file_row, $source_msi, $component_to_features_map) = @_;
680
681    my $uniquename = $file_row->GetValue('File');
682
683    my $file_map = $source_msi->GetFileMap();
684
685    my $file_item = $file_map->{$uniquename};
686    my $directory_item = $file_item->{'directory'};
687    my $source_path = $directory_item->{'full_source_long_name'};
688    my $target_path = $directory_item->{'full_target_long_name'};
689    my $full_source_name = undef;
690    if ($use_source_files_for_missing_files)
691    {
692        $full_source_name = File::Spec->catfile(
693            installer::patch::InstallationSet::GetUnpackedCabPath(
694                $source_msi->{'version'},
695                $source_msi->{'is_current_version'},
696                $source_msi->{'language'},
697                $source_msi->{'package_format'},
698                $source_msi->{'product_name'}),
699            $source_path,
700            $file_item->{'long_name'});
701    }
702    else
703    {
704        $full_source_name = "/c/tmp/missing/".$uniquename;
705        installer::patch::Tools::touch($full_source_name);
706    }
707    my ($long_name, undef) = installer::patch::Msi::SplitLongShortName($file_row->GetValue("FileName"));
708    my $target_name = File::Spec->catfile($target_path, $long_name);
709    if ( ! -f $full_source_name)
710    {
711        installer::logger::PrintError("can not find file '%s' in previous version (tried '%s')\n",
712            $uniquename,
713            $full_source_name);
714        return undef;
715    }
716    my $cygwin_full_source_name = qx(cygpath -w '$full_source_name');
717    my $component_name = $file_row->GetValue('Component_');
718    my $module_names = join(",", @{$component_to_features_map->{$component_name}});
719    my $sequence_number = $file_row->GetValue('Sequence');
720
721    return {
722        'uniquename' => $uniquename,
723        'destination' => $target_name,
724        'componentname' => $component_name,
725        'modules' => $module_names,
726        'UnixRights' => 444,
727        'Name' => $long_name,
728        'sourcepath' => $full_source_name,
729        'cyg_sourcepath' => $cygwin_full_source_name,
730        'sequencenumber' => $sequence_number
731        };
732}
733
734
735
736
737=head2 create_feature_component_maps($msi)
738
739    Return a hash map that maps from component names to arrays of
740    feature names.  In most cases the array of features contains only
741    one element.  But there can be cases where the number is greater.
742
743=cut
744sub create_feature_component_map ($)
745{
746    my ($msi) = @_;
747
748    my $component_to_features_map = {};
749    my $feature_component_table = $msi->GetTable("FeatureComponents");
750    my $feature_column_index = $feature_component_table->GetColumnIndex("Feature_");
751    my $component_column_index = $feature_component_table->GetColumnIndex("Component_");
752    foreach my $row (@{$feature_component_table->GetAllRows()})
753    {
754        my $feature = $row->GetValue($feature_column_index);
755        my $component = $row->GetValue($component_column_index);
756        if ( ! defined $component_to_features_map->{$component})
757        {
758            $component_to_features_map->{$component} = [$feature];
759        }
760        else
761        {
762            push @{$component_to_features_map->{$component}}, $feature;
763        }
764    }
765
766    return $component_to_features_map;
767}
768
769
770#############################################
771# Returning the Windows language of a file
772#############################################
773
774sub get_language_for_file
775{
776    my ($fileref) = @_;
777
778    my $language = "";
779
780    if ( $fileref->{'specificlanguage'} ) { $language = $fileref->{'specificlanguage'}; }
781
782    if ( $language eq "" )
783    {
784        $language = 0;  # language independent
785        # If this is not a font, the return value should be "0" (Check ICE 60)
786        my $styles = "";
787        if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; }
788        if ( $styles =~ /\bFONT\b/ ) { $language = ""; }
789    }
790    else
791    {
792        $language = installer::windows::language::get_windows_language($language);
793    }
794
795    return $language;
796}
797
798####################################################################
799# Creating a new KeyPath for components in TemplatesFolder.
800####################################################################
801
802sub generate_registry_keypath
803{
804    my ($onefile) = @_;
805
806    my $keypath = $onefile->{'Name'};
807    $keypath =~ s/\.//g;
808    $keypath = lc($keypath);
809    $keypath = "userreg_" . $keypath;
810
811    return $keypath;
812}
813
814
815###################################################################
816# Collecting further conditions for the component table.
817# This is used by multilayer products, to enable installation
818# of separate layers.
819###################################################################
820
821sub get_tree_condition_for_component
822{
823    my ($onefile, $componentname) = @_;
824
825    if ( $onefile->{'destination'} )
826    {
827        my $dest = $onefile->{'destination'};
828
829        # Comparing the destination path with
830        # $installer::globals::hostnametreestyles{$hostname} = $treestyle;
831        # (-> hostname is the key, the style the value!)
832
833        foreach my $hostname ( keys %installer::globals::hostnametreestyles )
834        {
835            if (( $dest eq $hostname ) || ( $dest =~ /^\s*\Q$hostname\E\\/ ))
836            {
837                # the value is the style
838                my $style = $installer::globals::hostnametreestyles{$hostname};
839                # the condition is saved in %installer::globals::treestyles
840                my $condition = $installer::globals::treestyles{$style};
841                # Saving condition to be added in table Property
842                $installer::globals::usedtreeconditions{$condition} = 1;
843                $condition = $condition . "=1";
844                # saving this condition
845                $installer::globals::treeconditions{$componentname} = $condition;
846
847                # saving also at the file, for usage in fileinfo
848                $onefile->{'layer'} = $installer::globals::treelayername{$style};
849            }
850        }
851    }
852}
853
854############################################
855# Collecting all short names, that are
856# already used by the old database
857############################################
858
859sub collect_shortnames_from_old_database
860{
861    my ($uniquefilenamehashref, $shortnameshashref) = @_;
862
863    foreach my $key ( keys %{$uniquefilenamehashref} )
864    {
865        my $value = $uniquefilenamehashref->{$key};  # syntax of $value: ($uniquename;$shortname)
866
867        if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ )
868        {
869            my $shortstring = $2;
870            $shortnameshashref->{$shortstring} = 1; # adding the shortname to the array of all shortnames
871        }
872    }
873}
874
875
876sub process_language_conditions ($)
877{
878    my ($onefile) = @_;
879
880    # Collecting all languages specific conditions
881    if ( $onefile->{'ismultilingual'} )
882    {
883        if ( $onefile->{'ComponentCondition'} )
884        {
885            installer::exiter::exit_program(
886                "ERROR: Cannot set language condition. There is already another component condition for file $onefile->{'gid'}: \"$onefile->{'ComponentCondition'}\" !", "create_files_table");
887        }
888
889        if ( $onefile->{'specificlanguage'} eq "" )
890        {
891            installer::exiter::exit_program(
892                "ERROR: There is no specific language for file at language module: $onefile->{'gid'} !", "create_files_table");
893        }
894        my $locallanguage = $onefile->{'specificlanguage'};
895        my $property = "IS" . $onefile->{'windows_language'};
896        my $value = 1;
897        my $condition = $property . "=" . $value;
898
899        $onefile->{'ComponentCondition'} = $condition;
900
901        if ( exists($installer::globals::componentcondition{$onefile->{'componentname'}}))
902        {
903            if ( $installer::globals::componentcondition{$onefile->{'componentname'}} ne $condition )
904            {
905                installer::exiter::exit_program(
906                    sprintf(
907                        "ERROR: There is already another component condition for file %s: \"%s\" and \"%s\" !",
908                        $onefile->{'gid'},
909                        $installer::globals::componentcondition{$onefile->{'componentname'}},
910                        $condition),
911                    "create_files_table");
912            }
913        }
914        else
915        {
916            $installer::globals::componentcondition{$onefile->{'componentname'}} = $condition;
917        }
918
919        # collecting all properties for table Property
920        if ( ! exists($installer::globals::languageproperties{$property}) )
921        {
922            $installer::globals::languageproperties{$property} = $value;
923        }
924    }
925}
926
927
928
929
930sub has_style ($$)
931{
932    my ($style_list_string, $style_name) = @_;
933
934    return 0 unless defined $style_list_string;
935    return $style_list_string =~ /\b$style_name\b/ ? 1 : 0;
936}
937
938
939
940
941sub prepare_file_table_creation ($$$)
942{
943    my ($file_list, $directory_list, $allvariables) = @_;
944
945    if ( $^O =~ /cygwin/i )
946    {
947        installer::worker::generate_cygwin_pathes($file_list);
948    }
949
950    # Reset the fields 'sequencenumber' and 'uniquename'. They should not yet exist but better be sure.
951    foreach my $file (@$file_list)
952    {
953        delete $file->{'sequencenumber'};
954        delete $file->{'uniquename'};
955    }
956
957    # Create FileSequenceList object for the old sequence data.
958    if (defined $installer::globals::source_msi)
959    {
960        my $previous_sequence_data = new installer::patch::FileSequenceList();
961        $previous_sequence_data->SetFromMsi($installer::globals::source_msi);
962        my @added_files = retrieve_sequence_and_uniquename($file_list, $previous_sequence_data);
963
964        # Extract just the unique names.
965        my %target_unique_names = map {$_->{'uniquename'} => 1} @$file_list;
966        my @removed_items = $previous_sequence_data->get_removed_files(\%target_unique_names);
967
968        $installer::logger::Lang->printf(
969            "there are %d files that have been removed from source and %d files added\n",
970            scalar @removed_items,
971            scalar @added_files);
972
973        my $file_map = $installer::globals::source_msi->GetFileMap();
974        my $index = 0;
975        foreach my $removed_row (@removed_items)
976        {
977            $installer::logger::Lang->printf("    removed file %d: %s\n",
978                ++$index,
979                $removed_row->GetValue('File'));
980            my $directory = $file_map->{$removed_row->GetValue('File')}->{'directory'};
981            while (my ($key,$value) = each %$directory)
982            {
983                $installer::logger::Lang->printf("        %16s -> %s\n", $key, $value);
984            }
985        }
986        $index = 0;
987        foreach my $added_file (@added_files)
988        {
989            $installer::logger::Lang->printf("    added file %d: %s\n",
990                ++$index,
991                $added_file->{'uniquename'});
992            installer::scriptitems::print_script_item($added_file);
993        }
994        my @new_files = create_items_for_missing_files(
995            \@removed_items,
996            $installer::globals::source_msi,
997            $directory_list);
998        push @$file_list, @new_files;
999    }
1000    assign_missing_sequence_numbers($file_list);
1001
1002    foreach my $file (@$file_list)
1003    {
1004        if ( ! defined $file->{'componentname'})
1005        {
1006            $file->{'componentname'} = get_file_component_name($file, $file_list);
1007        }
1008        if ( ! defined $file->{'uniquename'})
1009        {
1010            $file->{'uniquename'} = generate_unique_filename_for_filetable($file->{'Name'});
1011        }
1012
1013        # Collecting all component conditions
1014        if ( $file->{'ComponentCondition'} )
1015        {
1016            if ( ! exists($installer::globals::componentcondition{$file->{'componentname'}}))
1017            {
1018                $installer::globals::componentcondition{$file->{'componentname'}}
1019                = $file->{'ComponentCondition'};
1020            }
1021        }
1022        # Collecting also all tree conditions for multilayer products
1023        get_tree_condition_for_component($file, $file->{'componentname'});
1024
1025        # Collecting all component names, that have flag VERSION_INDEPENDENT_COMP_ID
1026        # This should be all components with constant API, for example URE
1027        if (has_style($file->{'Styles'}, "VERSION_INDEPENDENT_COMP_ID"))
1028        {
1029            $installer::globals::base_independent_components{$file->{'componentname'}} = 1;
1030        }
1031
1032        # Special handling for files in PREDEFINED_OSSHELLNEWDIR. These components
1033        # need as KeyPath a RegistryItem in HKCU
1034        if ($file->{'needs_user_registry_key'}
1035            || (defined $file->{'Dir'} && $file->{'Dir'} =~ /\bPREDEFINED_OSSHELLNEWDIR\b/))
1036        {
1037            my $keypath = generate_registry_keypath($file);
1038            $file->{'userregkeypath'} = $keypath;
1039            push(@installer::globals::userregistrycollector, $file);
1040            $installer::globals::addeduserregitrykeys = 1;
1041        }
1042
1043        $file->{'windows_language'} = get_language_for_file($file);
1044
1045        process_language_conditions($file);
1046    }
1047
1048    # The filenames must be collected because of uniqueness
1049    # 01-44-~1.DAT, 01-44-~2.DAT, ...
1050    my %shortnames = ();
1051    foreach my $file (@$file_list)
1052    {
1053        $file->{'short_name'} = generate_filename_for_filetable($file, \%shortnames);
1054    }
1055}
1056
1057
1058
1059
1060sub create_file_table_data ($$)
1061{
1062    my ($file_list, $allvariables) = @_;
1063
1064    my @file_table_data = ();
1065    foreach my $file (@$file_list)
1066    {
1067        my $attributes;
1068        if (has_style($file->{'Styles'}, "DONT_PACK"))
1069        {
1070            # Sourcefile is unpacked (msidbFileAttributesNoncompressed).
1071            $attributes = "8192";
1072        }
1073        else
1074        {
1075            # Sourcefile is packed (msidbFileAttributesCompressed).
1076            $attributes = "16384";
1077        }
1078
1079        my $row_data = {
1080            'File' => $file->{'uniquename'},
1081            'Component_' => $file->{'componentname'},
1082            'FileName' => $file->{'short_name'},
1083            'FileSize' => get_filesize($file),
1084            'Version' => get_fileversion($file, $allvariables),
1085            'Language' => $file->{'windows_language'},
1086            'Attributes' => $attributes,
1087            'Sequence' => $file->{'sequencenumber'}
1088            };
1089        push @file_table_data, $row_data;
1090    }
1091
1092    return \@file_table_data;
1093}
1094
1095
1096
1097
1098sub collect_components ($)
1099{
1100    my ($file_list) = @_;
1101
1102    my %components = ();
1103    foreach my $file (@$file_list)
1104    {
1105        $components{$file->{'componentname'}} = 1;
1106    }
1107    return keys %components;
1108}
1109
1110
1111
1112
1113=head filter_files($file_list, $allvariables)
1114
1115    Filter out Java files when not building a Java product.
1116
1117    Is this still triggered?
1118
1119=cut
1120sub filter_files ($$)
1121{
1122    my ($file_list, $allvariables) = @_;
1123
1124    if ($allvariables->{'JAVAPRODUCT'})
1125    {
1126        return $file_list;
1127    }
1128    else
1129    {
1130        my @filtered_files = ();
1131        foreach my $file (@$file_list)
1132        {
1133            if ( ! has_style($file->{'Styles'}, "JAVAFILE"))
1134            {
1135                push @filtered_files, $file;
1136            }
1137        }
1138        return \@filtered_files;
1139    }
1140}
1141
1142
1143
1144
1145# Structure of the files table:
1146# File Component_ FileName FileSize Version Language Attributes Sequence
1147sub create_file_table ($$)
1148{
1149    my ($file_table_data, $basedir) = @_;
1150
1151    # Set up the 'File' table.
1152    my @filetable = ();
1153    installer::windows::idtglobal::write_idt_header(\@filetable, "file");
1154    my @keys = ('File', 'Component_', 'FileName', 'FileSize', 'Version', 'Language', 'Attributes', 'Sequence');
1155    my $index = 0;
1156    foreach my $row_data (@$file_table_data)
1157    {
1158        ++$index;
1159        my @values = map {$row_data->{$_}} @keys;
1160        my $line = join("\t", @values) . "\n";
1161        push(@filetable, $line);
1162    }
1163
1164    my $filetablename = $basedir . $installer::globals::separator . "File.idt";
1165    installer::files::save_file($filetablename ,\@filetable);
1166    $installer::logger::Lang->print("\n");
1167    $installer::logger::Lang->printf("Created idt file: %s\n", $filetablename);
1168}
1169
1170
1171
1172
1173sub create_filehash_table ($$)
1174{
1175    my ($file_list, $basedir) = @_;
1176
1177    my @filehashtable = ();
1178
1179    if ( $installer::globals::prepare_winpatch )
1180    {
1181
1182        installer::windows::idtglobal::write_idt_header(\@filehashtable, "filehash");
1183
1184        foreach my $file (@$file_list)
1185        {
1186            my $path = $file->{'sourcepath'};
1187            if ($^O =~ /cygwin/i)
1188            {
1189                $path = $file->{'cyg_sourcepath'};
1190            }
1191
1192            open(FILE, $path) or die "ERROR: Can't open $path for creating file hash";
1193            binmode(FILE);
1194            my $hashinfo = pack("l", 20);
1195            $hashinfo .= Digest::MD5->new->addfile(*FILE)->digest;
1196
1197            my @i = unpack ('x[l]l4', $hashinfo);
1198            my $oneline = join("\t",
1199                (
1200                    $file->{'uniquename'},
1201                    "0",
1202                    @i
1203                ));
1204            push (@filehashtable, $oneline . "\n");
1205        }
1206
1207        my $filehashtablename = $basedir . $installer::globals::separator . "MsiFileHash.idt";
1208        installer::files::save_file($filehashtablename ,\@filehashtable);
1209        $installer::logger::Lang->print("\n");
1210        $installer::logger::Lang->printf("Created idt file: %s\n", $filehashtablename);
1211    }
1212}
1213
1214
12151;
1216