xref: /trunk/main/solenv/bin/modules/installer/windows/file.pm (revision 3033dfcf8c739242feb0ed220e2d080f454b8919)
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, $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($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, $msi, $component_to_features_map);
651        push @new_files, $file_item;
652    }
653
654    return @new_files;
655}
656
657
658
659
660sub create_script_item_for_deleted_file ($$$)
661{
662    my ($file_row, $msi, $component_to_features_map) = @_;
663
664    my $uniquename = $file_row->GetValue('File');
665
666    my $file_map = $msi->GetFileMap();
667
668    my $directory_item = $file_map->{$uniquename}->{'directory'};
669    my $source_path = $directory_item->{'full_source_long_name'};
670    my $target_path = $directory_item->{'full_target_long_name'};
671    my $full_source_name = File::Spec->catfile(
672        installer::patch::InstallationSet::GetUnpackedCabPath(
673            $msi->{'version'},
674            $msi->{'is_current_version'},
675            $msi->{'language'},
676            $msi->{'package_format'},
677            $msi->{'product_name'}),
678        $source_path,
679        $uniquename);
680    my ($long_name, undef) = installer::patch::Msi::SplitLongShortName($file_row->GetValue("FileName"));
681    my $target_name = File::Spec->catfile($target_path, $long_name);
682    if ( ! -f $full_source_name)
683    {
684        installer::logger::PrintError("can not find file '%s' in previous version (tried '%s')\n",
685            $uniquename,
686            $full_source_name);
687        return undef;
688    }
689    my $cygwin_full_source_name = qx(cygpath -w '$full_source_name');
690    my $component_name = $file_row->GetValue('Component_');
691    my $module_names = join(",", @{$component_to_features_map->{$component_name}});
692    my $sequence_number = $file_row->GetValue('Sequence');
693
694    return {
695        'uniquename' => $uniquename,
696        'destination' => $target_name,
697        'componentname' => $component_name,
698        'modules' => $module_names,
699        'UnixRights' => 444,
700        'Name' => $long_name,
701        'sourcepath' => $full_source_name,
702        'cyg_sourcepath' => $cygwin_full_source_name,
703        'sequencenumber' => $sequence_number
704        };
705}
706
707
708
709
710=head2 create_feature_component_maps($msi)
711
712    Return a hash map that maps from component names to arrays of
713    feature names.  In most cases the array of features contains only
714    one element.  But there can be cases where the number is greater.
715
716=cut
717sub create_feature_component_map ($)
718{
719    my ($msi) = @_;
720
721    my $component_to_features_map = {};
722    my $feature_component_table = $msi->GetTable("FeatureComponents");
723    my $feature_column_index = $feature_component_table->GetColumnIndex("Feature_");
724    my $component_column_index = $feature_component_table->GetColumnIndex("Component_");
725    foreach my $row (@{$feature_component_table->GetAllRows()})
726    {
727        my $feature = $row->GetValue($feature_column_index);
728        my $component = $row->GetValue($component_column_index);
729        if ( ! defined $component_to_features_map->{$component})
730        {
731            $component_to_features_map->{$component} = [$feature];
732        }
733        else
734        {
735            push @{$component_to_features_map->{$component}}, $feature;
736        }
737    }
738
739    return $component_to_features_map;
740}
741
742
743#############################################
744# Returning the Windows language of a file
745#############################################
746
747sub get_language_for_file
748{
749    my ($fileref) = @_;
750
751    my $language = "";
752
753    if ( $fileref->{'specificlanguage'} ) { $language = $fileref->{'specificlanguage'}; }
754
755    if ( $language eq "" )
756    {
757        $language = 0;  # language independent
758        # If this is not a font, the return value should be "0" (Check ICE 60)
759        my $styles = "";
760        if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; }
761        if ( $styles =~ /\bFONT\b/ ) { $language = ""; }
762    }
763    else
764    {
765        $language = installer::windows::language::get_windows_language($language);
766    }
767
768    return $language;
769}
770
771####################################################################
772# Creating a new KeyPath for components in TemplatesFolder.
773####################################################################
774
775sub generate_registry_keypath
776{
777    my ($onefile) = @_;
778
779    my $keypath = $onefile->{'Name'};
780    $keypath =~ s/\.//g;
781    $keypath = lc($keypath);
782    $keypath = "userreg_" . $keypath;
783
784    return $keypath;
785}
786
787
788###################################################################
789# Collecting further conditions for the component table.
790# This is used by multilayer products, to enable installation
791# of separate layers.
792###################################################################
793
794sub get_tree_condition_for_component
795{
796    my ($onefile, $componentname) = @_;
797
798    if ( $onefile->{'destination'} )
799    {
800        my $dest = $onefile->{'destination'};
801
802        # Comparing the destination path with
803        # $installer::globals::hostnametreestyles{$hostname} = $treestyle;
804        # (-> hostname is the key, the style the value!)
805
806        foreach my $hostname ( keys %installer::globals::hostnametreestyles )
807        {
808            if (( $dest eq $hostname ) || ( $dest =~ /^\s*\Q$hostname\E\\/ ))
809            {
810                # the value is the style
811                my $style = $installer::globals::hostnametreestyles{$hostname};
812                # the condition is saved in %installer::globals::treestyles
813                my $condition = $installer::globals::treestyles{$style};
814                # Saving condition to be added in table Property
815                $installer::globals::usedtreeconditions{$condition} = 1;
816                $condition = $condition . "=1";
817                # saving this condition
818                $installer::globals::treeconditions{$componentname} = $condition;
819
820                # saving also at the file, for usage in fileinfo
821                $onefile->{'layer'} = $installer::globals::treelayername{$style};
822            }
823        }
824    }
825}
826
827############################################
828# Collecting all short names, that are
829# already used by the old database
830############################################
831
832sub collect_shortnames_from_old_database
833{
834    my ($uniquefilenamehashref, $shortnameshashref) = @_;
835
836    foreach my $key ( keys %{$uniquefilenamehashref} )
837    {
838        my $value = $uniquefilenamehashref->{$key};  # syntax of $value: ($uniquename;$shortname)
839
840        if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ )
841        {
842            my $shortstring = $2;
843            $shortnameshashref->{$shortstring} = 1; # adding the shortname to the array of all shortnames
844        }
845    }
846}
847
848
849sub process_language_conditions ($)
850{
851    my ($onefile) = @_;
852
853    # Collecting all languages specific conditions
854    if ( $onefile->{'ismultilingual'} )
855    {
856        if ( $onefile->{'ComponentCondition'} )
857        {
858            installer::exiter::exit_program(
859                "ERROR: Cannot set language condition. There is already another component condition for file $onefile->{'gid'}: \"$onefile->{'ComponentCondition'}\" !", "create_files_table");
860        }
861
862        if ( $onefile->{'specificlanguage'} eq "" )
863        {
864            installer::exiter::exit_program(
865                "ERROR: There is no specific language for file at language module: $onefile->{'gid'} !", "create_files_table");
866        }
867        my $locallanguage = $onefile->{'specificlanguage'};
868        my $property = "IS" . $onefile->{'windows_language'};
869        my $value = 1;
870        my $condition = $property . "=" . $value;
871
872        $onefile->{'ComponentCondition'} = $condition;
873
874        if ( exists($installer::globals::componentcondition{$onefile->{'componentname'}}))
875        {
876            if ( $installer::globals::componentcondition{$onefile->{'componentname'}} ne $condition )
877            {
878                installer::exiter::exit_program(
879                    sprintf(
880                        "ERROR: There is already another component condition for file %s: \"%s\" and \"%s\" !",
881                        $onefile->{'gid'},
882                        $installer::globals::componentcondition{$onefile->{'componentname'}},
883                        $condition),
884                    "create_files_table");
885            }
886        }
887        else
888        {
889            $installer::globals::componentcondition{$onefile->{'componentname'}} = $condition;
890        }
891
892        # collecting all properties for table Property
893        if ( ! exists($installer::globals::languageproperties{$property}) )
894        {
895            $installer::globals::languageproperties{$property} = $value;
896        }
897    }
898}
899
900
901
902
903sub has_style ($$)
904{
905    my ($style_list_string, $style_name) = @_;
906
907    return 0 unless defined $style_list_string;
908    return $style_list_string =~ /\b$style_name\b/ ? 1 : 0;
909}
910
911
912
913
914sub prepare_file_table_creation ($$$)
915{
916    my ($file_list, $directory_list, $allvariables) = @_;
917
918    if ( $^O =~ /cygwin/i )
919    {
920        installer::worker::generate_cygwin_pathes($file_list);
921    }
922
923    # Reset the fields 'sequencenumber' and 'uniquename'. They should not yet exist but better be sure.
924    foreach my $file (@$file_list)
925    {
926        delete $file->{'sequencenumber'};
927        delete $file->{'uniquename'};
928    }
929
930    # Create FileSequenceList object for the old sequence data.
931    if (defined $installer::globals::source_msi)
932    {
933        my $previous_sequence_data = new installer::patch::FileSequenceList();
934        $previous_sequence_data->SetFromMsi($installer::globals::source_msi);
935        my @added_files = retrieve_sequence_and_uniquename($file_list, $previous_sequence_data);
936
937        # Extract just the unique names.
938        my %target_unique_names = map {$_->{'uniquename'} => 1} @$file_list;
939        my @removed_items = $previous_sequence_data->get_removed_files(\%target_unique_names);
940
941        $installer::logger::Lang->printf(
942            "there are %d files that have been removed from source and %d files added\n",
943            scalar @removed_items,
944            scalar @added_files);
945
946        my $file_map = $installer::globals::source_msi->GetFileMap();
947        my $index = 0;
948        foreach my $removed_row (@removed_items)
949        {
950            $installer::logger::Lang->printf("    removed file %d: %s\n",
951                ++$index,
952                $removed_row->GetValue('File'));
953            my $directory = $file_map->{$removed_row->GetValue('File')}->{'directory'};
954            while (my ($key,$value) = each %$directory)
955            {
956                $installer::logger::Lang->printf("        %16s -> %s\n", $key, $value);
957            }
958        }
959        $index = 0;
960        foreach my $added_file (@added_files)
961        {
962            $installer::logger::Lang->printf("    added file %d: %s\n",
963                ++$index,
964                $added_file->{'uniquename'});
965            installer::scriptitems::print_script_item($added_file);
966        }
967        my @new_files = create_items_for_missing_files(
968            \@removed_items,
969            $installer::globals::source_msi,
970            $directory_list);
971        push @$file_list, @new_files;
972    }
973    assign_missing_sequence_numbers($file_list);
974
975    foreach my $file (@$file_list)
976    {
977        if ( ! defined $file->{'componentname'})
978        {
979            $file->{'componentname'} = get_file_component_name($file, $file_list);
980        }
981        if ( ! defined $file->{'uniquename'})
982        {
983            $file->{'uniquename'} = generate_unique_filename_for_filetable($file->{'Name'});
984        }
985
986        # Collecting all component conditions
987        if ( $file->{'ComponentCondition'} )
988        {
989            if ( ! exists($installer::globals::componentcondition{$file->{'componentname'}}))
990            {
991                $installer::globals::componentcondition{$file->{'componentname'}}
992                = $file->{'ComponentCondition'};
993            }
994        }
995        # Collecting also all tree conditions for multilayer products
996        get_tree_condition_for_component($file, $file->{'componentname'});
997
998        # Collecting all component names, that have flag VERSION_INDEPENDENT_COMP_ID
999        # This should be all components with constant API, for example URE
1000        if (has_style($file->{'Styles'}, "VERSION_INDEPENDENT_COMP_ID"))
1001        {
1002            $installer::globals::base_independent_components{$file->{'componentname'}} = 1;
1003        }
1004
1005        # Special handling for files in PREDEFINED_OSSHELLNEWDIR. These components
1006        # need as KeyPath a RegistryItem in HKCU
1007        if ($file->{'needs_user_registry_key'}
1008            || (defined $file->{'Dir'} && $file->{'Dir'} =~ /\bPREDEFINED_OSSHELLNEWDIR\b/))
1009        {
1010            my $keypath = generate_registry_keypath($file);
1011            $file->{'userregkeypath'} = $keypath;
1012            push(@installer::globals::userregistrycollector, $file);
1013            $installer::globals::addeduserregitrykeys = 1;
1014        }
1015
1016        $file->{'windows_language'} = get_language_for_file($file);
1017
1018        process_language_conditions($file);
1019    }
1020
1021    # The filenames must be collected because of uniqueness
1022    # 01-44-~1.DAT, 01-44-~2.DAT, ...
1023    my %shortnames = ();
1024    foreach my $file (@$file_list)
1025    {
1026        $file->{'short_name'} = generate_filename_for_filetable($file, \%shortnames);
1027    }
1028}
1029
1030
1031
1032
1033sub create_file_table_data ($$)
1034{
1035    my ($file_list, $allvariables) = @_;
1036
1037    my @file_table_data = ();
1038    foreach my $file (@$file_list)
1039    {
1040        my $attributes;
1041        if (has_style($file->{'Styles'}, "DONT_PACK"))
1042        {
1043            # Sourcefile is unpacked (msidbFileAttributesNoncompressed).
1044            $attributes = "8192";
1045        }
1046        else
1047        {
1048            # Sourcefile is packed (msidbFileAttributesCompressed).
1049            $attributes = "16384";
1050        }
1051
1052        my $row_data = {
1053            'File' => $file->{'uniquename'},
1054            'Component_' => $file->{'componentname'},
1055            'FileName' => $file->{'short_name'},
1056            'FileSize' => get_filesize($file),
1057            'Version' => get_fileversion($file, $allvariables),
1058            'Language' => $file->{'windows_language'},
1059            'Attributes' => $attributes,
1060            'Sequence' => $file->{'sequencenumber'}
1061            };
1062        push @file_table_data, $row_data;
1063    }
1064
1065    return \@file_table_data;
1066}
1067
1068
1069
1070
1071sub collect_components ($)
1072{
1073    my ($file_list) = @_;
1074
1075    my %components = ();
1076    foreach my $file (@$file_list)
1077    {
1078        $components{$file->{'componentname'}} = 1;
1079    }
1080    return keys %components;
1081}
1082
1083
1084
1085
1086=head filter_files($file_list, $allvariables)
1087
1088    Filter out Java files when not building a Java product.
1089
1090    Is this still triggered?
1091
1092=cut
1093sub filter_files ($$)
1094{
1095    my ($file_list, $allvariables) = @_;
1096
1097    if ($allvariables->{'JAVAPRODUCT'})
1098    {
1099        return $file_list;
1100    }
1101    else
1102    {
1103        my @filtered_files = ();
1104        foreach my $file (@$file_list)
1105        {
1106            if ( ! has_style($file->{'Styles'}, "JAVAFILE"))
1107            {
1108                push @filtered_files, $file;
1109            }
1110        }
1111        return \@filtered_files;
1112    }
1113}
1114
1115
1116
1117
1118# Structure of the files table:
1119# File Component_ FileName FileSize Version Language Attributes Sequence
1120sub create_file_table ($$)
1121{
1122    my ($file_table_data, $basedir) = @_;
1123
1124    # Set up the 'File' table.
1125    my @filetable = ();
1126    installer::windows::idtglobal::write_idt_header(\@filetable, "file");
1127    my @keys = ('File', 'Component_', 'FileName', 'FileSize', 'Version', 'Language', 'Attributes', 'Sequence');
1128    my $index = 0;
1129    foreach my $row_data (@$file_table_data)
1130    {
1131        ++$index;
1132        my @values = map {$row_data->{$_}} @keys;
1133        my $line = join("\t", @values) . "\n";
1134        push(@filetable, $line);
1135    }
1136
1137    my $filetablename = $basedir . $installer::globals::separator . "File.idt";
1138    installer::files::save_file($filetablename ,\@filetable);
1139    $installer::logger::Lang->print("\n");
1140    $installer::logger::Lang->printf("Created idt file: %s\n", $filetablename);
1141}
1142
1143
1144
1145
1146sub create_filehash_table ($$)
1147{
1148    my ($file_list, $basedir) = @_;
1149
1150    my @filehashtable = ();
1151
1152    if ( $installer::globals::prepare_winpatch )
1153    {
1154
1155        installer::windows::idtglobal::write_idt_header(\@filehashtable, "filehash");
1156
1157        foreach my $file (@$file_list)
1158        {
1159            my $path = $file->{'sourcepath'};
1160            if ($^O =~ /cygwin/i)
1161            {
1162                $path = $file->{'cyg_sourcepath'};
1163            }
1164
1165            open(FILE, $path) or die "ERROR: Can't open $path for creating file hash";
1166            binmode(FILE);
1167            my $hashinfo = pack("l", 20);
1168            $hashinfo .= Digest::MD5->new->addfile(*FILE)->digest;
1169
1170            my @i = unpack ('x[l]l4', $hashinfo);
1171            my $oneline = join("\t",
1172                (
1173                    $file->{'uniquename'},
1174                    "0",
1175                    @i
1176                ));
1177            push (@filehashtable, $oneline . "\n");
1178        }
1179
1180        my $filehashtablename = $basedir . $installer::globals::separator . "MsiFileHash.idt";
1181        installer::files::save_file($filehashtablename ,\@filehashtable);
1182        $installer::logger::Lang->print("\n");
1183        $installer::logger::Lang->printf("Created idt file: %s\n", $filehashtablename);
1184    }
1185}
1186
1187
11881;
1189