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