| #************************************************************** |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| #************************************************************** |
| |
| |
| |
| package installer::windows::file; |
| |
| use Digest::MD5; |
| use installer::existence; |
| use installer::exiter; |
| use installer::files; |
| use installer::globals; |
| use installer::logger; |
| use installer::pathanalyzer; |
| use installer::worker; |
| use installer::windows::font; |
| use installer::windows::idtglobal; |
| use installer::windows::msiglobal; |
| use installer::windows::language; |
| use installer::patch::InstallationSet; |
| use installer::patch::FileSequenceList; |
| use File::Basename; |
| use File::Spec; |
| use strict; |
| |
| ########################################################################## |
| # Assigning one cabinet file to each file. This is requrired, |
| # if cabinet files shall be equivalent to packages. |
| ########################################################################## |
| |
| sub assign_cab_to_files |
| { |
| my ( $filesref ) = @_; |
| |
| my $infoline = ""; |
| |
| foreach my $file (@$filesref) |
| { |
| if ( ! exists($file->{'modules'}) ) |
| { |
| installer::exiter::exit_program( |
| sprintf("ERROR: No module assignment found for %s", $file->{'gid'}), |
| "assign_cab_to_files"); |
| } |
| my $module = $file->{'modules'}; |
| # If modules contains a list of modules, only taking the first one. |
| if ( $module =~ /^\s*(.*?)\,/ ) { $module = $1; } |
| |
| if ( ! exists($installer::globals::allcabinetassigns{$module}) ) |
| { |
| installer::exiter::exit_program( |
| sprintf("ERROR: No cabinet file assigned to module \"%s\" %s", |
| $module, |
| $file->{'gid'}), |
| "assign_cab_to_files"); |
| } |
| $file->{'assignedcabinetfile'} = $installer::globals::allcabinetassigns{$module}; |
| |
| # Counting the files in each cabinet file |
| if ( ! exists($installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}}) ) |
| { |
| $installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}} = 1; |
| } |
| else |
| { |
| $installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}}++; |
| } |
| } |
| |
| # assigning startsequencenumbers for each cab file |
| |
| my %count = (); |
| my $offset = 1; |
| foreach my $cabfile ( sort keys %installer::globals::cabfilecounter ) |
| { |
| my $filecount = $installer::globals::cabfilecounter{$cabfile}; |
| $count{$cabfile} = $filecount; |
| $installer::globals::cabfilecounter{$cabfile} = $offset; |
| $offset = $offset + $filecount; |
| |
| $installer::globals::lastsequence{$cabfile} = $offset - 1; |
| } |
| |
| # logging the number of files in each cabinet file |
| |
| $installer::logger::Lang->print("\n"); |
| $installer::logger::Lang->print("Cabinet files:\n"); |
| foreach my $cabfile (sort keys %installer::globals::cabfilecounter) |
| { |
| $installer::logger::Lang->printf( |
| "%-30s : %4s files, from %4d to %4d\n", |
| $cabfile, |
| $count{$cabfile}, |
| $installer::globals::cabfilecounter{$cabfile}, |
| $installer::globals::lastsequence{$cabfile}); |
| } |
| } |
| |
| ########################################################################## |
| # Assigning sequencenumbers to files. This is requrired, |
| # if cabinet files shall be equivalent to packages. |
| ########################################################################## |
| |
| sub assign_sequencenumbers_to_files |
| { |
| my ( $filesref ) = @_; |
| |
| my %directaccess = (); |
| my %allassigns = (); |
| |
| for ( my $i = 0; $i <= $#{$filesref}; $i++ ) |
| { |
| my $onefile = ${$filesref}[$i]; |
| |
| # Keeping order in cabinet files |
| # -> collecting all files in one cabinet file |
| # -> sorting files and assigning numbers |
| |
| # Saving counter $i for direct access into files array |
| # "destination" of the file is a unique identifier ('Name' is not unique!) |
| if ( exists($directaccess{$onefile->{'destination'}}) ) { installer::exiter::exit_program("ERROR: 'destination' at file not unique: $onefile->{'destination'}", "assign_sequencenumbers_to_files"); } |
| $directaccess{$onefile->{'destination'}} = $i; |
| |
| my $cabfilename = $onefile->{'assignedcabinetfile'}; |
| # collecting files in cabinet files |
| if ( ! exists($allassigns{$cabfilename}) ) |
| { |
| my %onecabfile = (); |
| $onecabfile{$onefile->{'destination'}} = 1; |
| $allassigns{$cabfilename} = \%onecabfile; |
| } |
| else |
| { |
| $allassigns{$cabfilename}->{$onefile->{'destination'}} = 1; |
| } |
| } |
| |
| # Sorting each hash and assigning numbers |
| # The destination of the file determines the sort order, not the filename! |
| my $cabfile; |
| foreach $cabfile ( sort keys %allassigns ) |
| { |
| my $counter = $installer::globals::cabfilecounter{$cabfile}; |
| my $dest; |
| foreach $dest ( sort keys %{$allassigns{$cabfile}} ) # <- sorting the destination! |
| { |
| my $directaccessnumber = $directaccess{$dest}; |
| ${$filesref}[$directaccessnumber]->{'assignedsequencenumber'} = $counter; |
| $counter++; |
| } |
| } |
| } |
| |
| ######################################################### |
| # Create a shorter version of a long component name, |
| # because maximum length in msi database is 72. |
| # Attention: In multi msi installation sets, the short |
| # names have to be unique over all packages, because |
| # this string is used to create the globally unique id |
| # -> no resetting of |
| # %installer::globals::allshortcomponents |
| # after a package was created. |
| # Using no counter because of reproducibility. |
| ######################################################### |
| |
| sub generate_new_short_componentname |
| { |
| my ($componentname) = @_; |
| |
| my $startversion = substr($componentname, 0, 60); # taking only the first 60 characters |
| my $subid = installer::windows::msiglobal::calculate_id($componentname, 9); # taking only the first 9 digits |
| my $shortcomponentname = $startversion . "_" . $subid; |
| |
| if ( exists($installer::globals::allshortcomponents{$shortcomponentname}) ) { installer::exiter::exit_program("Failed to create unique component name: \"$shortcomponentname\"", "generate_new_short_componentname"); } |
| |
| $installer::globals::allshortcomponents{$shortcomponentname} = 1; |
| |
| return $shortcomponentname; |
| } |
| |
| ############################################### |
| # Generating the component name from a file |
| ############################################### |
| |
| sub get_file_component_name |
| { |
| my ($fileref, $filesref) = @_; |
| |
| my $componentname = ""; |
| |
| # Special handling for files with ASSIGNCOMPOMENT |
| |
| my $styles = ""; |
| if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } |
| if ( $styles =~ /\bASSIGNCOMPOMENT\b/ ) |
| { |
| $componentname = get_component_from_assigned_file($fileref->{'AssignComponent'}, $filesref); |
| } |
| else |
| { |
| # In this function exists the rule to create components from files |
| # Rule: |
| # Two files get the same componentid, if: |
| # both have the same destination directory. |
| # both have the same "gid" -> both were packed in the same zip file |
| # All other files are included into different components! |
| |
| # my $componentname = $fileref->{'gid'} . "_" . $fileref->{'Dir'}; |
| |
| # $fileref->{'Dir'} is not sufficient! All files in a zip file have the same $fileref->{'Dir'}, |
| # but can be in different subdirectories. |
| # Solution: destination=share\Scripts\beanshell\Capitalise\capitalise.bsh |
| # in which the filename (capitalise.bsh) has to be removed and all backslashes (slashes) are |
| # converted into underline. |
| |
| my $destination = $fileref->{'destination'}; |
| installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination); |
| $destination =~ s/\s//g; |
| $destination =~ s/\\/\_/g; |
| $destination =~ s/\//\_/g; |
| $destination =~ s/\_\s*$//g; # removing ending underline |
| |
| $componentname = $fileref->{'gid'} . "__" . $destination; |
| |
| # Files with different languages, need to be packed into different components. |
| # Then the installation of the language specific component is determined by a language condition. |
| |
| if ( $fileref->{'ismultilingual'} ) |
| { |
| my $officelanguage = $fileref->{'specificlanguage'}; |
| $componentname = $componentname . "_" . $officelanguage; |
| } |
| |
| $componentname = lc($componentname); # componentnames always lowercase |
| |
| $componentname =~ s/\-/\_/g; # converting "-" to "_" |
| $componentname =~ s/\./\_/g; # converting "-" to "_" |
| |
| # Attention: Maximum length for the componentname is 72 |
| # %installer::globals::allcomponents_in_this_database : resetted for each database |
| # %installer::globals::allcomponents : not resetted for each database |
| # Component strings must be unique for the complete product, because they are used for |
| # the creation of the globally unique identifier. |
| |
| my $fullname = $componentname; # This can be longer than 72 |
| |
| if (( exists($installer::globals::allcomponents{$fullname}) ) && ( ! exists($installer::globals::allcomponents_in_this_database{$fullname}) )) |
| { |
| # This is not allowed: One component cannot be installed with different packages. |
| installer::exiter::exit_program("ERROR: Component \"$fullname\" is already included into another package. This is not allowed.", "get_file_component_name"); |
| } |
| |
| if ( exists($installer::globals::allcomponents{$fullname}) ) |
| { |
| $componentname = $installer::globals::allcomponents{$fullname}; |
| } |
| else |
| { |
| if ( length($componentname) > 70 ) |
| { |
| $componentname = generate_new_short_componentname($componentname); # This has to be unique for the complete product, not only one package |
| } |
| |
| $installer::globals::allcomponents{$fullname} = $componentname; |
| $installer::globals::allcomponents_in_this_database{$fullname} = 1; |
| } |
| |
| # $componentname =~ s/gid_file_/g_f_/g; |
| # $componentname =~ s/_extra_/_e_/g; |
| # $componentname =~ s/_config_/_c_/g; |
| # $componentname =~ s/_org_openoffice_/_o_o_/g; |
| # $componentname =~ s/_program_/_p_/g; |
| # $componentname =~ s/_typedetection_/_td_/g; |
| # $componentname =~ s/_linguistic_/_l_/g; |
| # $componentname =~ s/_module_/_m_/g; |
| # $componentname =~ s/_optional_/_opt_/g; |
| # $componentname =~ s/_packages/_pack/g; |
| # $componentname =~ s/_menubar/_mb/g; |
| # $componentname =~ s/_common_/_cm_/g; |
| # $componentname =~ s/_export_/_exp_/g; |
| # $componentname =~ s/_table_/_tb_/g; |
| # $componentname =~ s/_sofficecfg_/_sc_/g; |
| # $componentname =~ s/_soffice_cfg_/_sc_/g; |
| # $componentname =~ s/_startmodulecommands_/_smc_/g; |
| # $componentname =~ s/_drawimpresscommands_/_dic_/g; |
| # $componentname =~ s/_basiccommands_/_bac_/g; |
| # $componentname =~ s/_basicidecommands_/_baic_/g; |
| # $componentname =~ s/_genericcommands_/_genc_/g; |
| # $componentname =~ s/_bibliographycommands_/_bibc_/g; |
| # $componentname =~ s/_gentiumbookbasicbolditalic_/_gbbbi_/g; |
| # $componentname =~ s/_share_/_s_/g; |
| # $componentname =~ s/_extension_/_ext_/g; |
| # $componentname =~ s/_extensions_/_exs_/g; |
| # $componentname =~ s/_modules_/_ms_/g; |
| # $componentname =~ s/_uiconfig_zip_/_ucz_/g; |
| # $componentname =~ s/_productivity_/_pr_/g; |
| # $componentname =~ s/_wizard_/_wz_/g; |
| # $componentname =~ s/_import_/_im_/g; |
| # $componentname =~ s/_javascript_/_js_/g; |
| # $componentname =~ s/_template_/_tpl_/g; |
| # $componentname =~ s/_tplwizletter_/_twl_/g; |
| # $componentname =~ s/_beanshell_/_bs_/g; |
| # $componentname =~ s/_presentation_/_bs_/g; |
| # $componentname =~ s/_columns_/_cls_/g; |
| # $componentname =~ s/_python_/_py_/g; |
| |
| # $componentname =~ s/_tools/_ts/g; |
| # $componentname =~ s/_transitions/_trs/g; |
| # $componentname =~ s/_scriptbinding/_scrb/g; |
| # $componentname =~ s/_spreadsheet/_ssh/g; |
| # $componentname =~ s/_publisher/_pub/g; |
| # $componentname =~ s/_presenter/_pre/g; |
| # $componentname =~ s/_registry/_reg/g; |
| |
| # $componentname =~ s/screen/sc/g; |
| # $componentname =~ s/wordml/wm/g; |
| # $componentname =~ s/openoffice/oo/g; |
| } |
| |
| return $componentname; |
| } |
| |
| #################################################################### |
| # Returning the component name for a defined file gid. |
| # This is necessary for files with flag ASSIGNCOMPOMENT |
| #################################################################### |
| |
| sub get_component_from_assigned_file |
| { |
| my ($gid, $filesref) = @_; |
| |
| my $onefile = installer::existence::get_specified_file($filesref, $gid); |
| my $componentname = ""; |
| if ( $onefile->{'componentname'} ) { $componentname = $onefile->{'componentname'}; } |
| else { installer::exiter::exit_program("ERROR: No component defined for file: $gid", "get_component_from_assigned_file"); } |
| |
| return $componentname; |
| } |
| |
| #################################################################### |
| # Generating the special filename for the database file File.idt |
| # Sample: CONTEXTS, CONTEXTS1 |
| # This name has to be unique. |
| # In most cases this is simply the filename. |
| #################################################################### |
| |
| sub generate_unique_filename_for_filetable ($) |
| { |
| my ($oldname) = @_; |
| |
| # This new filename has to be saved into $fileref, because this is needed to find the source. |
| # The filename sbasic.idx/OFFSETS is changed to OFFSETS, but OFFSETS is not unique. |
| # In this procedure names like OFFSETS5 are produced. And exactly this string has to be added to |
| # the array of all files. |
| |
| my $uniquefilename = $oldname; |
| if ( ! defined $uniquefilename || $uniquefilename eq "") |
| { |
| installer::logger::PrintError("file name does not exist or is empty, can not create unique name for it."); |
| die; |
| return; |
| } |
| |
| # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs |
| installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$uniquefilename); |
| |
| $uniquefilename =~ s/\-/\_/g; # no "-" allowed |
| $uniquefilename =~ s/\@/\_/g; # no "@" allowed |
| $uniquefilename =~ s/\$/\_/g; # no "$" allowed |
| $uniquefilename =~ s/^\s*\./\_/g; # no "." at the beginning allowed allowed |
| $uniquefilename =~ s/^\s*\d/\_d/g; # no number at the beginning allowed allowed (even file "0.gif", replacing to "_d.gif") |
| $uniquefilename =~ s/org_openoffice_/ooo_/g; # shorten the unique file name |
| |
| my $lcuniquefilename = lc($uniquefilename); # only lowercase names |
| |
| my $newname = 0; |
| |
| if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename})) |
| { |
| $installer::globals::alluniquefilenames{$uniquefilename} = 1; |
| $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; |
| $newname = 1; |
| } |
| |
| if ( ! $newname ) |
| { |
| # adding a number until the name is really unique: OFFSETS, OFFSETS1, OFFSETS2, ... |
| # But attention: Making "abc.xcu" to "abc1.xcu" |
| |
| my $uniquefilenamebase = $uniquefilename; |
| |
| my $counter = 0; |
| do |
| { |
| $counter++; |
| |
| if ( $uniquefilenamebase =~ /\./ ) |
| { |
| $uniquefilename = $uniquefilenamebase; |
| $uniquefilename =~ s/\./$counter\./; |
| } |
| else |
| { |
| $uniquefilename = $uniquefilenamebase . $counter; |
| } |
| |
| $newname = 0; |
| $lcuniquefilename = lc($uniquefilename); # only lowercase names |
| |
| if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename})) |
| { |
| $installer::globals::alluniquefilenames{$uniquefilename} = 1; |
| $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; |
| $newname = 1; |
| } |
| } |
| until ( $newname ) |
| } |
| |
| return $uniquefilename; |
| } |
| |
| #################################################################### |
| # Generating the special file column for the database file File.idt |
| # Sample: NAMETR~1.TAB|.nametranslation.table |
| # The first part has to be 8.3 conform. |
| #################################################################### |
| |
| sub generate_filename_for_filetable ($$) |
| { |
| my ($fileref, $shortnamesref) = @_; |
| |
| my $returnstring = ""; |
| |
| my $filename = $fileref->{'Name'}; |
| |
| # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs |
| installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$filename); |
| |
| my $shortstring = installer::windows::idtglobal::make_eight_three_conform_with_hash($filename, "file", $shortnamesref); |
| |
| if ( $shortstring eq $filename ) |
| { |
| # nothing changed |
| $returnstring = $filename; |
| } |
| else |
| { |
| $returnstring = $shortstring . "\|" . $filename; |
| } |
| |
| return $returnstring; |
| } |
| |
| ######################################### |
| # Returning the filesize of a file |
| ######################################### |
| |
| sub get_filesize |
| { |
| my ($fileref) = @_; |
| |
| my $file = $fileref->{'sourcepath'}; |
| |
| my $filesize; |
| |
| if ( -f $file ) # test of existence. For instance services.rdb does not always exist |
| { |
| $filesize = ( -s $file ); # file size can be "0" |
| } |
| else |
| { |
| $filesize = -1; |
| } |
| |
| return $filesize; |
| } |
| |
| ############################################# |
| # Returning the file version, if required |
| # Sample: "8.0.1.8976"; |
| ############################################# |
| |
| sub get_fileversion |
| { |
| my ($onefile, $allvariables) = @_; |
| |
| my $fileversion = ""; |
| |
| if ( $allvariables->{'USE_FILEVERSION'} ) |
| { |
| if ( ! $allvariables->{'LIBRARYVERSION'} ) |
| { |
| installer::exiter::exit_program("ERROR: USE_FILEVERSION is set, but not LIBRARYVERSION", "get_fileversion"); |
| } |
| my $libraryversion = $allvariables->{'LIBRARYVERSION'}; |
| if ( $libraryversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ ) |
| { |
| my $major = $1; |
| my $minor = $2; |
| my $micro = $3; |
| my $concat = 100 * $minor + $micro; |
| $libraryversion = $major . "\." . $concat; |
| } |
| my $vendornumber = 0; |
| if ( $allvariables->{'VENDORPATCHVERSION'} ) |
| { |
| $vendornumber = $allvariables->{'VENDORPATCHVERSION'}; |
| } |
| $fileversion = $libraryversion . "\." . $installer::globals::buildid . "\." . $vendornumber; |
| if ( $onefile->{'FileVersion'} ) |
| { |
| # overriding FileVersion in scp |
| $fileversion = $onefile->{'FileVersion'}; |
| } |
| } |
| |
| if ( $installer::globals::prepare_winpatch ) |
| { |
| # Windows patches do not allow this version # -> who says so? |
| $fileversion = ""; |
| } |
| |
| return $fileversion; |
| } |
| |
| |
| |
| |
| sub retrieve_sequence_and_uniquename ($$) |
| { |
| my ($file_list, $source_data) = @_; |
| |
| my @added_files = (); |
| |
| # Read the sequence numbers of the previous version. |
| if ($installer::globals::is_release) |
| { |
| foreach my $file (@$file_list) |
| { |
| # Use the source path of the file as key to retrieve sequence number and unique name. |
| # The source path is the part of the 'destination' without the first part. |
| # There is a special case when 'Dir' is PREDEFINED_OSSHELLNEWDIR. |
| my $source_path; |
| if (defined $file->{'Dir'} && $file->{'Dir'} eq "PREDEFINED_OSSHELLNEWDIR") |
| { |
| $source_path = $installer::globals::templatefoldername |
| . $installer::globals::separator |
| . $file->{'Name'}; |
| } |
| else |
| { |
| $source_path = $file->{'destination'}; |
| $source_path =~ s/^[^\/]+\///; |
| } |
| my ($sequence, $uniquename) = $source_data->get_sequence_and_unique_name($source_path); |
| if (defined $sequence && defined $uniquename) |
| { |
| $file->{'sequencenumber'} = $sequence; |
| $file->{'uniquename'} = $uniquename; |
| } |
| else |
| { |
| # No data found in the source release. File has been added. |
| push @added_files, $file; |
| } |
| } |
| } |
| |
| return @added_files; |
| } |
| |
| |
| |
| |
| =head2 assign_mssing_sequence_numbers ($file_list) |
| |
| Assign sequence numbers where still missing. |
| |
| When we are preparing a patch then all files that have no sequence numbers |
| at this point are new. Otherwise no file has a sequence number yet. |
| |
| =cut |
| sub assign_missing_sequence_numbers ($) |
| { |
| my ($file_list) = @_; |
| |
| # First, set up a hash on the sequence numbers that are already in use. |
| my %used_sequence_numbers = (); |
| foreach my $file (@$file_list) |
| { |
| next unless defined $file->{'sequencenumber'}; |
| $used_sequence_numbers{$file->{'sequencenumber'}} = 1; |
| } |
| |
| # Assign sequence numbers. Try consecutive numbers, starting at 1. |
| my $current_sequence_number = 1; |
| foreach my $file (@$file_list) |
| { |
| # Skip over all files that already have sequence numbers. |
| next if defined $file->{'sequencenumber'}; |
| |
| # Find the next available number. |
| while (defined $used_sequence_numbers{$current_sequence_number}) |
| { |
| ++$current_sequence_number; |
| } |
| |
| # Use the number and mark it as used. |
| $file->{'sequencenumber'} = $current_sequence_number; |
| $used_sequence_numbers{$current_sequence_number} = 1; |
| } |
| } |
| |
| |
| |
| |
| sub create_items_for_missing_files ($$$) |
| { |
| my ($missing_items, $source_msi, $directory_list) = @_; |
| |
| # For creation of the FeatureComponent table (in a later step) we |
| # have to provide references from the file to component and |
| # modules (ie features). Note that Each file belongs to exactly |
| # one component but one component can belong to multiple features. |
| my $component_to_features_map = create_feature_component_map($source_msi); |
| |
| my @new_files = (); |
| foreach my $row (@$missing_items) |
| { |
| $installer::logger::Info->printf("creating new file item for '%s'\n", $row->GetValue('File')); |
| my $file_item = create_script_item_for_deleted_file($row, $source_msi, $component_to_features_map); |
| push @new_files, $file_item; |
| } |
| |
| return @new_files; |
| } |
| |
| |
| |
| |
| =head2 create_script_item_for_deleted_file (($file_row, $source_msi, $component_to_features_map) |
| |
| Create a new script item for a file that was present in the |
| previous release but isn't anymore. Most of the necessary |
| information is taken from the 'File' table of the source release. |
| |
| The values of 'sourcepath' and 'cyg_sourcepath' will point to the |
| respective file in the unpacked source release. An alternative |
| would be to let them point to an empty file. That, however, might |
| make the patch bigger (diff between identical file contents is |
| (almost) empty, diff between file and empty file is the 'inverse' |
| of the file). |
| |
| =cut |
| |
| my $use_source_files_for_missing_files = 1; |
| |
| sub create_script_item_for_deleted_file ($$$) |
| { |
| my ($file_row, $source_msi, $component_to_features_map) = @_; |
| |
| my $uniquename = $file_row->GetValue('File'); |
| |
| my $file_map = $source_msi->GetFileMap(); |
| |
| my $file_item = $file_map->{$uniquename}; |
| my $directory_item = $file_item->{'directory'}; |
| my $source_path = $directory_item->{'full_source_long_name'}; |
| my $target_path = $directory_item->{'full_target_long_name'}; |
| my $full_source_name = undef; |
| if ($use_source_files_for_missing_files) |
| { |
| $full_source_name = File::Spec->catfile( |
| installer::patch::InstallationSet::GetUnpackedCabPath( |
| $source_msi->{'version'}, |
| $source_msi->{'is_current_version'}, |
| $source_msi->{'language'}, |
| $source_msi->{'package_format'}, |
| $source_msi->{'product_name'}), |
| $source_path, |
| $file_item->{'long_name'}); |
| } |
| else |
| { |
| $full_source_name = "/c/tmp/missing/".$uniquename; |
| installer::patch::Tools::touch($full_source_name); |
| } |
| my ($long_name, undef) = installer::patch::Msi::SplitLongShortName($file_row->GetValue("FileName")); |
| my $target_name = File::Spec->catfile($target_path, $long_name); |
| if ( ! -f $full_source_name) |
| { |
| installer::logger::PrintError("can not find file '%s' in previous version (tried '%s')\n", |
| $uniquename, |
| $full_source_name); |
| return undef; |
| } |
| my $cygwin_full_source_name = qx(cygpath -w '$full_source_name'); |
| my $component_name = $file_row->GetValue('Component_'); |
| my $module_names = join(",", @{$component_to_features_map->{$component_name}}); |
| my $sequence_number = $file_row->GetValue('Sequence'); |
| |
| return { |
| 'uniquename' => $uniquename, |
| 'destination' => $target_name, |
| 'componentname' => $component_name, |
| 'modules' => $module_names, |
| 'UnixRights' => 444, |
| 'Name' => $long_name, |
| 'sourcepath' => $full_source_name, |
| 'cyg_sourcepath' => $cygwin_full_source_name, |
| 'sequencenumber' => $sequence_number |
| }; |
| } |
| |
| |
| |
| |
| =head2 create_feature_component_maps($msi) |
| |
| Return a hash map that maps from component names to arrays of |
| feature names. In most cases the array of features contains only |
| one element. But there can be cases where the number is greater. |
| |
| =cut |
| sub create_feature_component_map ($) |
| { |
| my ($msi) = @_; |
| |
| my $component_to_features_map = {}; |
| my $feature_component_table = $msi->GetTable("FeatureComponents"); |
| my $feature_column_index = $feature_component_table->GetColumnIndex("Feature_"); |
| my $component_column_index = $feature_component_table->GetColumnIndex("Component_"); |
| foreach my $row (@{$feature_component_table->GetAllRows()}) |
| { |
| my $feature = $row->GetValue($feature_column_index); |
| my $component = $row->GetValue($component_column_index); |
| if ( ! defined $component_to_features_map->{$component}) |
| { |
| $component_to_features_map->{$component} = [$feature]; |
| } |
| else |
| { |
| push @{$component_to_features_map->{$component}}, $feature; |
| } |
| } |
| |
| return $component_to_features_map; |
| } |
| |
| |
| ############################################# |
| # Returning the Windows language of a file |
| ############################################# |
| |
| sub get_language_for_file |
| { |
| my ($fileref) = @_; |
| |
| my $language = ""; |
| |
| if ( $fileref->{'specificlanguage'} ) { $language = $fileref->{'specificlanguage'}; } |
| |
| if ( $language eq "" ) |
| { |
| $language = 0; # language independent |
| # If this is not a font, the return value should be "0" (Check ICE 60) |
| my $styles = ""; |
| if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } |
| if ( $styles =~ /\bFONT\b/ ) { $language = ""; } |
| } |
| else |
| { |
| $language = installer::windows::language::get_windows_language($language); |
| } |
| |
| return $language; |
| } |
| |
| #################################################################### |
| # Creating a new KeyPath for components in TemplatesFolder. |
| #################################################################### |
| |
| sub generate_registry_keypath |
| { |
| my ($onefile) = @_; |
| |
| my $keypath = $onefile->{'Name'}; |
| $keypath =~ s/\.//g; |
| $keypath = lc($keypath); |
| $keypath = "userreg_" . $keypath; |
| |
| return $keypath; |
| } |
| |
| |
| ################################################################### |
| # Collecting further conditions for the component table. |
| # This is used by multilayer products, to enable installation |
| # of separate layers. |
| ################################################################### |
| |
| sub get_tree_condition_for_component |
| { |
| my ($onefile, $componentname) = @_; |
| |
| if ( $onefile->{'destination'} ) |
| { |
| my $dest = $onefile->{'destination'}; |
| |
| # Comparing the destination path with |
| # $installer::globals::hostnametreestyles{$hostname} = $treestyle; |
| # (-> hostname is the key, the style the value!) |
| |
| foreach my $hostname ( keys %installer::globals::hostnametreestyles ) |
| { |
| if (( $dest eq $hostname ) || ( $dest =~ /^\s*\Q$hostname\E\\/ )) |
| { |
| # the value is the style |
| my $style = $installer::globals::hostnametreestyles{$hostname}; |
| # the condition is saved in %installer::globals::treestyles |
| my $condition = $installer::globals::treestyles{$style}; |
| # Saving condition to be added in table Property |
| $installer::globals::usedtreeconditions{$condition} = 1; |
| $condition = $condition . "=1"; |
| # saving this condition |
| $installer::globals::treeconditions{$componentname} = $condition; |
| |
| # saving also at the file, for usage in fileinfo |
| $onefile->{'layer'} = $installer::globals::treelayername{$style}; |
| } |
| } |
| } |
| } |
| |
| ############################################ |
| # Collecting all short names, that are |
| # already used by the old database |
| ############################################ |
| |
| sub collect_shortnames_from_old_database |
| { |
| my ($uniquefilenamehashref, $shortnameshashref) = @_; |
| |
| foreach my $key ( keys %{$uniquefilenamehashref} ) |
| { |
| my $value = $uniquefilenamehashref->{$key}; # syntax of $value: ($uniquename;$shortname) |
| |
| if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ ) |
| { |
| my $shortstring = $2; |
| $shortnameshashref->{$shortstring} = 1; # adding the shortname to the array of all shortnames |
| } |
| } |
| } |
| |
| |
| sub process_language_conditions ($) |
| { |
| my ($onefile) = @_; |
| |
| # Collecting all languages specific conditions |
| if ( $onefile->{'ismultilingual'} ) |
| { |
| if ( $onefile->{'ComponentCondition'} ) |
| { |
| installer::exiter::exit_program( |
| "ERROR: Cannot set language condition. There is already another component condition for file $onefile->{'gid'}: \"$onefile->{'ComponentCondition'}\" !", "create_files_table"); |
| } |
| |
| if ( $onefile->{'specificlanguage'} eq "" ) |
| { |
| installer::exiter::exit_program( |
| "ERROR: There is no specific language for file at language module: $onefile->{'gid'} !", "create_files_table"); |
| } |
| my $locallanguage = $onefile->{'specificlanguage'}; |
| my $property = "IS" . $onefile->{'windows_language'}; |
| my $value = 1; |
| my $condition = $property . "=" . $value; |
| |
| $onefile->{'ComponentCondition'} = $condition; |
| |
| if ( exists($installer::globals::componentcondition{$onefile->{'componentname'}})) |
| { |
| if ( $installer::globals::componentcondition{$onefile->{'componentname'}} ne $condition ) |
| { |
| installer::exiter::exit_program( |
| sprintf( |
| "ERROR: There is already another component condition for file %s: \"%s\" and \"%s\" !", |
| $onefile->{'gid'}, |
| $installer::globals::componentcondition{$onefile->{'componentname'}}, |
| $condition), |
| "create_files_table"); |
| } |
| } |
| else |
| { |
| $installer::globals::componentcondition{$onefile->{'componentname'}} = $condition; |
| } |
| |
| # collecting all properties for table Property |
| if ( ! exists($installer::globals::languageproperties{$property}) ) |
| { |
| $installer::globals::languageproperties{$property} = $value; |
| } |
| } |
| } |
| |
| |
| |
| |
| sub has_style ($$) |
| { |
| my ($style_list_string, $style_name) = @_; |
| |
| return 0 unless defined $style_list_string; |
| return $style_list_string =~ /\b$style_name\b/ ? 1 : 0; |
| } |
| |
| |
| |
| |
| sub prepare_file_table_creation ($$$) |
| { |
| my ($file_list, $directory_list, $allvariables) = @_; |
| |
| if ( $^O =~ /cygwin/i ) |
| { |
| installer::worker::generate_cygwin_pathes($file_list); |
| } |
| |
| # Reset the fields 'sequencenumber' and 'uniquename'. They should not yet exist but better be sure. |
| foreach my $file (@$file_list) |
| { |
| delete $file->{'sequencenumber'}; |
| delete $file->{'uniquename'}; |
| } |
| |
| # Create FileSequenceList object for the old sequence data. |
| if (defined $installer::globals::source_msi) |
| { |
| my $previous_sequence_data = new installer::patch::FileSequenceList(); |
| $previous_sequence_data->SetFromMsi($installer::globals::source_msi); |
| my @added_files = retrieve_sequence_and_uniquename($file_list, $previous_sequence_data); |
| |
| # Extract just the unique names. |
| my %target_unique_names = map {$_->{'uniquename'} => 1} @$file_list; |
| my @removed_items = $previous_sequence_data->get_removed_files(\%target_unique_names); |
| |
| $installer::logger::Lang->printf( |
| "there are %d files that have been removed from source and %d files added\n", |
| scalar @removed_items, |
| scalar @added_files); |
| |
| my $file_map = $installer::globals::source_msi->GetFileMap(); |
| my $index = 0; |
| foreach my $removed_row (@removed_items) |
| { |
| $installer::logger::Lang->printf(" removed file %d: %s\n", |
| ++$index, |
| $removed_row->GetValue('File')); |
| my $directory = $file_map->{$removed_row->GetValue('File')}->{'directory'}; |
| while (my ($key,$value) = each %$directory) |
| { |
| $installer::logger::Lang->printf(" %16s -> %s\n", $key, $value); |
| } |
| } |
| $index = 0; |
| foreach my $added_file (@added_files) |
| { |
| $installer::logger::Lang->printf(" added file %d: %s\n", |
| ++$index, |
| $added_file->{'uniquename'}); |
| installer::scriptitems::print_script_item($added_file); |
| } |
| my @new_files = create_items_for_missing_files( |
| \@removed_items, |
| $installer::globals::source_msi, |
| $directory_list); |
| push @$file_list, @new_files; |
| } |
| assign_missing_sequence_numbers($file_list); |
| |
| foreach my $file (@$file_list) |
| { |
| if ( ! defined $file->{'componentname'}) |
| { |
| $file->{'componentname'} = get_file_component_name($file, $file_list); |
| } |
| if ( ! defined $file->{'uniquename'}) |
| { |
| $file->{'uniquename'} = generate_unique_filename_for_filetable($file->{'Name'}); |
| } |
| |
| # Collecting all component conditions |
| if ( $file->{'ComponentCondition'} ) |
| { |
| if ( ! exists($installer::globals::componentcondition{$file->{'componentname'}})) |
| { |
| $installer::globals::componentcondition{$file->{'componentname'}} |
| = $file->{'ComponentCondition'}; |
| } |
| } |
| # Collecting also all tree conditions for multilayer products |
| get_tree_condition_for_component($file, $file->{'componentname'}); |
| |
| # Collecting all component names, that have flag VERSION_INDEPENDENT_COMP_ID |
| # This should be all components with constant API, for example URE |
| if (has_style($file->{'Styles'}, "VERSION_INDEPENDENT_COMP_ID")) |
| { |
| $installer::globals::base_independent_components{$file->{'componentname'}} = 1; |
| } |
| |
| # Special handling for files in PREDEFINED_OSSHELLNEWDIR. These components |
| # need as KeyPath a RegistryItem in HKCU |
| if ($file->{'needs_user_registry_key'} |
| || (defined $file->{'Dir'} && $file->{'Dir'} =~ /\bPREDEFINED_OSSHELLNEWDIR\b/)) |
| { |
| my $keypath = generate_registry_keypath($file); |
| $file->{'userregkeypath'} = $keypath; |
| push(@installer::globals::userregistrycollector, $file); |
| $installer::globals::addeduserregitrykeys = 1; |
| } |
| |
| $file->{'windows_language'} = get_language_for_file($file); |
| |
| process_language_conditions($file); |
| } |
| |
| # The filenames must be collected because of uniqueness |
| # 01-44-~1.DAT, 01-44-~2.DAT, ... |
| my %shortnames = (); |
| foreach my $file (@$file_list) |
| { |
| $file->{'short_name'} = generate_filename_for_filetable($file, \%shortnames); |
| } |
| } |
| |
| |
| |
| |
| sub create_file_table_data ($$) |
| { |
| my ($file_list, $allvariables) = @_; |
| |
| my @file_table_data = (); |
| foreach my $file (@$file_list) |
| { |
| my $attributes; |
| if (has_style($file->{'Styles'}, "DONT_PACK")) |
| { |
| # Sourcefile is unpacked (msidbFileAttributesNoncompressed). |
| $attributes = "8192"; |
| } |
| else |
| { |
| # Sourcefile is packed (msidbFileAttributesCompressed). |
| $attributes = "16384"; |
| } |
| |
| my $row_data = { |
| 'File' => $file->{'uniquename'}, |
| 'Component_' => $file->{'componentname'}, |
| 'FileName' => $file->{'short_name'}, |
| 'FileSize' => get_filesize($file), |
| 'Version' => get_fileversion($file, $allvariables), |
| 'Language' => $file->{'windows_language'}, |
| 'Attributes' => $attributes, |
| 'Sequence' => $file->{'sequencenumber'} |
| }; |
| push @file_table_data, $row_data; |
| } |
| |
| return \@file_table_data; |
| } |
| |
| |
| |
| |
| sub collect_components ($) |
| { |
| my ($file_list) = @_; |
| |
| my %components = (); |
| foreach my $file (@$file_list) |
| { |
| $components{$file->{'componentname'}} = 1; |
| } |
| return keys %components; |
| } |
| |
| |
| |
| |
| =head filter_files($file_list, $allvariables) |
| |
| Filter out Java files when not building a Java product. |
| |
| Is this still triggered? |
| |
| =cut |
| sub filter_files ($$) |
| { |
| my ($file_list, $allvariables) = @_; |
| |
| if ($allvariables->{'JAVAPRODUCT'}) |
| { |
| return $file_list; |
| } |
| else |
| { |
| my @filtered_files = (); |
| foreach my $file (@$file_list) |
| { |
| if ( ! has_style($file->{'Styles'}, "JAVAFILE")) |
| { |
| push @filtered_files, $file; |
| } |
| } |
| return \@filtered_files; |
| } |
| } |
| |
| |
| |
| |
| # Structure of the files table: |
| # File Component_ FileName FileSize Version Language Attributes Sequence |
| sub create_file_table ($$) |
| { |
| my ($file_table_data, $basedir) = @_; |
| |
| # Set up the 'File' table. |
| my @filetable = (); |
| installer::windows::idtglobal::write_idt_header(\@filetable, "file"); |
| my @keys = ('File', 'Component_', 'FileName', 'FileSize', 'Version', 'Language', 'Attributes', 'Sequence'); |
| my $index = 0; |
| foreach my $row_data (@$file_table_data) |
| { |
| ++$index; |
| my @values = map {$row_data->{$_}} @keys; |
| my $line = join("\t", @values) . "\n"; |
| push(@filetable, $line); |
| } |
| |
| my $filetablename = $basedir . $installer::globals::separator . "File.idt"; |
| installer::files::save_file($filetablename ,\@filetable); |
| $installer::logger::Lang->print("\n"); |
| $installer::logger::Lang->printf("Created idt file: %s\n", $filetablename); |
| } |
| |
| |
| |
| |
| sub create_filehash_table ($$) |
| { |
| my ($file_list, $basedir) = @_; |
| |
| my @filehashtable = (); |
| |
| if ( $installer::globals::prepare_winpatch ) |
| { |
| |
| installer::windows::idtglobal::write_idt_header(\@filehashtable, "filehash"); |
| |
| foreach my $file (@$file_list) |
| { |
| my $path = $file->{'sourcepath'}; |
| if ($^O =~ /cygwin/i) |
| { |
| $path = $file->{'cyg_sourcepath'}; |
| } |
| |
| open(FILE, $path) or die "ERROR: Can't open $path for creating file hash"; |
| binmode(FILE); |
| my $hashinfo = pack("l", 20); |
| $hashinfo .= Digest::MD5->new->addfile(*FILE)->digest; |
| |
| my @i = unpack ('x[l]l4', $hashinfo); |
| my $oneline = join("\t", |
| ( |
| $file->{'uniquename'}, |
| "0", |
| @i |
| )); |
| push (@filehashtable, $oneline . "\n"); |
| } |
| |
| my $filehashtablename = $basedir . $installer::globals::separator . "MsiFileHash.idt"; |
| installer::files::save_file($filehashtablename ,\@filehashtable); |
| $installer::logger::Lang->print("\n"); |
| $installer::logger::Lang->printf("Created idt file: %s\n", $filehashtablename); |
| } |
| } |
| |
| |
| 1; |