Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / solenv / bin / modules / installer / windows / update.pm
blobad89d4065b1433df606eb9c37dc75a1518db1263
2 # This file is part of the LibreOffice project.
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # This file incorporates work covered by the following license notice:
10 # Licensed to the Apache Software Foundation (ASF) under one or more
11 # contributor license agreements. See the NOTICE file distributed
12 # with this work for additional information regarding copyright
13 # ownership. The ASF licenses this file to you under the Apache
14 # License, Version 2.0 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 package installer::windows::update;
21 use strict;
22 use warnings;
24 use installer::converter;
25 use installer::exiter;
26 use installer::files;
27 use installer::globals;
28 use installer::pathanalyzer;
29 use installer::systemactions;
31 #################################################################################
32 # Extracting all tables from an msi database
33 #################################################################################
35 sub extract_all_tables_from_msidatabase
37 my ($fulldatabasepath, $workdir) = @_;
39 my $msidb = "msidb.exe"; # Has to be in the path
40 my $infoline = "";
41 my $systemcall = "";
42 my $returnvalue = "";
43 my $extraslash = ""; # Has to be set for non-ActiveState perl
45 # Export of all tables by using "*"
47 if ( $^O =~ /cygwin/i ) {
48 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
49 $fulldatabasepath =~ s/\//\\\\/g;
50 $workdir =~ s/\//\\\\/g;
51 $extraslash = "\\";
53 if ( $^O =~ /linux/i) {
54 $extraslash = "\\";
57 $systemcall = $msidb . " -d " . $fulldatabasepath . " -f " . $workdir . " -e " . $extraslash . "*";
58 $returnvalue = system($systemcall);
60 $infoline = "Systemcall: $systemcall\n";
61 push( @installer::globals::logfileinfo, $infoline);
63 if ($returnvalue)
65 $infoline = "ERROR: Could not execute $systemcall !\n";
66 push( @installer::globals::logfileinfo, $infoline);
67 installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $fulldatabasepath !", "extract_all_tables_from_msidatabase");
69 else
71 $infoline = "Success: Executed $systemcall successfully!\n";
72 push( @installer::globals::logfileinfo, $infoline);
76 #################################################################################
77 # Collecting the keys from the first line of the idt file
78 #################################################################################
80 sub collect_all_keys
82 my ($line) = @_;
84 my @allkeys = ();
85 my $rownumber = 0;
86 my $onekey = "";
88 while ( $line =~ /^\s*(\S+?)\t(.*)$/ )
90 $onekey = $1;
91 $line = $2;
92 $rownumber++;
93 push(@allkeys, $onekey);
96 # and the last key
98 $onekey = $line;
99 $onekey =~ s/^\s*//g;
100 $onekey =~ s/\s*$//g;
102 $rownumber++;
103 push(@allkeys, $onekey);
105 return (\@allkeys, $rownumber);
108 #################################################################################
109 # Analyzing the content of one line of an idt file
110 #################################################################################
112 sub get_oneline_hash
114 my ($line, $allkeys, $rownumber) = @_;
116 my $counter = 0;
117 my %linehash = ();
119 $line =~ s/^\s*//;
120 $line =~ s/\s*$//;
122 my $value = "";
123 my $onekey = "";
125 while ( $line =~ /^(.*?)\t(.*)$/ )
127 $value = $1;
128 $line = $2;
129 $onekey = ${$allkeys}[$counter];
130 $linehash{$onekey} = $value;
131 $counter++;
134 # the last column
136 $value = $line;
137 $onekey = ${$allkeys}[$counter];
139 $linehash{$onekey} = $value;
141 return \%linehash;
144 #################################################################################
145 # Analyzing the content of an idt file
146 #################################################################################
148 sub analyze_idt_file
150 my ($filecontent) = @_;
152 my %table = ();
153 # keys are written in first line
154 my ($allkeys, $rownumber) = collect_all_keys(${$filecontent}[0]);
156 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
158 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
160 my $onelinehash = get_oneline_hash(${$filecontent}[$i], $allkeys, $rownumber);
161 my $linekey = $i - 2; # ! : The linenumber is the unique key !? Always decrease by two, because of removed first three lines.
162 $table{$linekey} = $onelinehash;
165 return \%table;
168 #################################################################################
169 # Reading all idt files in a specified directory
170 #################################################################################
172 sub read_all_tables_from_msidatabase
174 my ($workdir) = @_;
176 my %database = ();
178 my $ext = "idt";
180 my $allidtfiles = installer::systemactions::find_file_with_file_extension($ext, $workdir);
182 for ( my $i = 0; $i <= $#{$allidtfiles}; $i++ )
184 my $onefilename = ${$allidtfiles}[$i];
185 my $longonefilename = $workdir . $installer::globals::separator . $onefilename;
186 if ( ! -f $longonefilename ) { installer::exiter::exit_program("ERROR: Could not find idt file: $longonefilename!", "read_all_tables_from_msidatabase"); }
187 my $filecontent = installer::files::read_file($longonefilename);
188 my $idtcontent = analyze_idt_file($filecontent);
189 if ($onefilename eq "Directory.idt") {
190 collect_directories($filecontent, $longonefilename);
192 my $key = $onefilename;
193 $key =~ s/\.idt\s*$//;
194 $database{$key} = $idtcontent;
197 return \%database;
200 #################################################################################
201 # Checking, if this is the correct database.
202 #################################################################################
204 sub correct_database
206 my ($product, $pro, $langs, $languagestringref) = @_;
208 my $correct_database = 0;
210 # Comparing $product with $installer::globals::product and
211 # $pro with $installer::globals::pro and
212 # $langs with $languagestringref
214 my $product_is_good = 0;
216 my $localproduct = $installer::globals::product;
217 if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; }
218 elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; }
220 if ( $product eq $localproduct ) { $product_is_good = 1; }
222 if ( $product_is_good )
224 my $pro_is_good = 0;
226 if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; }
228 if ( $pro_is_good )
230 my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ",");
231 my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_");
233 my $not_included = 0;
234 foreach my $onelang ( keys %{$langlisthash} )
236 if ( ! exists($langstringhash->{$onelang}) )
238 $not_included = 1;
239 last;
243 if ( ! $not_included )
245 foreach my $onelanguage ( keys %{$langstringhash} )
247 if ( ! exists($langlisthash->{$onelanguage}) )
249 $not_included = 1;
250 last;
254 if ( ! $not_included ) { $correct_database = 1; }
259 return $correct_database;
262 #################################################################################
263 # Searching for the path to the reference database for this special product.
264 #################################################################################
266 sub get_databasename_from_list
268 my ($filecontent, $languagestringref, $filename) = @_;
270 my $databasepath = "";
272 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
274 my $line = ${$filecontent}[$i];
275 if ( $line =~ /^\s*$/ ) { next; } # empty line
276 if ( $line =~ /^\s*\#/ ) { next; } # comment line
278 if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ )
280 my $product = $1;
281 my $pro = $2;
282 my $langs = $3;
283 my $path = $4;
285 if (( $pro ne "pro" ) && ( $pro ne "nonpro" )) { installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename. Only \"pro\" or \"nonpro\" allowed in column 1! Line: \"$line\"", "get_databasename_from_list"); }
287 if ( correct_database($product, $pro, $langs, $languagestringref) )
289 $databasepath = $path;
290 last;
293 else
295 installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_databasename_from_list");
299 return $databasepath;
302 #################################################################################
303 # Reading an existing database completely
304 #################################################################################
306 sub readdatabase
308 my ($allvariables, $languagestringref, $includepatharrayref) = @_;
310 my $database = "";
311 my $infoline = "";
313 if ( ! $allvariables->{'UPDATE_DATABASE_LISTNAME'} ) { installer::exiter::exit_program("ERROR: If \"UPDATE_DATABASE\" is set, \"UPDATE_DATABASE_LISTNAME\" is required.", "Main"); }
314 my $listfilename = $allvariables->{'UPDATE_DATABASE_LISTNAME'};
316 # Searching the list in the include paths
317 my $listname = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$listfilename, $includepatharrayref, 1);
318 if ( $$listname eq "" ) { installer::exiter::exit_program("ERROR: List file not found: $listfilename !", "readdatabase"); }
319 my $completelistname = $$listname;
321 # Reading list file
322 my $listfile = installer::files::read_file($completelistname);
324 # Get name and path of reference database
325 my $databasename = get_databasename_from_list($listfile, $languagestringref, $completelistname);
327 # If the correct database was not found, this is not necessarily an error. But in this case, this is not an update packaging process!
328 if (( $databasename ) && ( $databasename ne "" )) # This is an update packaging process!
330 $installer::globals::updatedatabase = 1;
331 installer::logger::print_message( "... update process, using database $databasename ...\n" );
332 $infoline = "\nDatabase found in $completelistname: \"$databasename\"\n\n";
333 # Saving in global variable
334 $installer::globals::updatedatabasepath = $databasename;
336 else
338 $infoline = "\nNo database found in $completelistname. This is no update process!\n\n";
340 push( @installer::globals::logfileinfo, $infoline);
342 if ( $installer::globals::updatedatabase )
344 if ( ! -f $databasename ) { installer::exiter::exit_program("ERROR: Could not find reference database: $databasename!", "readdatabase"); }
346 my $msifilename = $databasename;
347 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename);
349 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase start");
351 # create directory for unpacking
352 my $databasedir = installer::systemactions::create_directories("database", $languagestringref);
354 # copy database
355 my $fulldatabasepath = $databasedir . $installer::globals::separator . $msifilename;
356 installer::systemactions::copy_one_file($databasename, $fulldatabasepath);
358 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before extracting tables");
360 # extract all tables from database
361 extract_all_tables_from_msidatabase($fulldatabasepath, $databasedir);
363 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before reading tables");
365 # read all tables
366 $database = read_all_tables_from_msidatabase($databasedir);
368 # Test output:
370 # foreach my $key1 ( keys %{$database} )
372 # print "Test1: $key1\n";
373 # foreach my $key2 ( keys %{$database->{$key1}} )
375 # print "\tTest2: $key2\n";
376 # foreach my $key3 ( keys %{$database->{$key1}->{$key2}} )
378 # print "\t\tTest3: $key3: $database->{$key1}->{$key2}->{$key3}\n";
383 # Example: File table
385 # my $filetable = $database->{'File'};
386 # foreach my $linenumber ( keys %{$filetable} )
388 # print "Test Filenumber: $linenumber\n";
389 # foreach my $key ( keys %{$filetable->{$linenumber}} )
391 # print "\t\tTest: $key: $filetable->{$linenumber}->{$key}\n";
395 # Example: Searching for ProductCode in table Property
397 # my $column1 = "Property";
398 # my $column2 = "Value";
399 # my $searchkey = "ProductCode";
400 # my $propertytable = $database->{'Property'};
401 # foreach my $linenumber ( keys %{$propertytable} )
403 # if ( $propertytable->{$linenumber}->{$column1} eq $searchkey )
405 # print("Test: $searchkey : $propertytable->{$linenumber}->{$column2}\n");
409 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase end");
412 return $database;
415 #########################################################################
416 # Reading the file "Directory.idt".
417 #########################################################################
419 sub collect_directories
421 my ($filecontent, $idtfilename) = @_;
423 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
425 if ( $i <= 2 ) { next; } # ignoring first three lines
426 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
427 # Format: Directory Directory_Parent DefaultDir
428 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
430 $installer::globals::merge_directory_hash{$1} = 1;
432 else
434 my $linecount = $i + 1;
435 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");
440 #################################################################################
441 # Files can be included in merge modules. This is also important for update.
442 #################################################################################
444 sub readmergedatabase
446 my ( $mergemodules, $languagestringref, $includepatharrayref ) = @_;
448 installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase start");
450 my $mergemoduledir = installer::systemactions::create_directories("mergedatabase", $languagestringref);
452 my %allmergefiles = ();
454 foreach my $mergemodule ( @{$mergemodules} )
456 my $filename = $mergemodule->{'Name'};
457 my $mergefile = $ENV{'MSM_PATH'} . $filename;
459 if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename !", "readmergedatabase"); }
460 my $completesource = $mergefile;
462 my $mergegid = $mergemodule->{'gid'};
463 my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
464 if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }
466 my $completedest = $workdir . $installer::globals::separator . $filename;
467 installer::systemactions::copy_one_file($completesource, $completedest);
468 if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "readmergedatabase"); }
470 # extract all tables from database
471 extract_all_tables_from_msidatabase($completedest, $workdir);
473 # read all tables
474 my $onemergefile = read_all_tables_from_msidatabase($workdir);
476 $allmergefiles{$mergegid} = $onemergefile;
479 foreach my $mergefilegid ( keys %allmergefiles )
481 my $onemergefile = $allmergefiles{$mergefilegid};
482 my $filetable = $onemergefile->{'File'};
484 foreach my $linenumber ( keys %{$filetable} )
486 # Collecting all files from merge modules in global hash
487 $installer::globals::mergemodulefiles{$filetable->{$linenumber}->{'File'}} = 1;
491 installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase end");
494 #################################################################################
495 # Creating several useful hashes from old database
496 #################################################################################
498 sub create_database_hashes
500 my ( $database ) = @_;
502 # 1. Hash ( Component -> UniqueFileName ), required in File table.
503 # Read from File table.
505 my %uniquefilename = ();
506 my %allupdatesequences = ();
507 my %allupdatecomponents = ();
508 my %allupdatefileorder = ();
509 my %allupdatecomponentorder = ();
510 my %revuniquefilename = ();
511 my %revshortfilename = ();
512 my %shortdirname = ();
513 my %componentid = ();
514 my %componentidkeypath = ();
515 my %alloldproperties = ();
516 my %allupdatelastsequences = ();
517 my %allupdatediskids = ();
519 my $filetable = $database->{'File'};
521 foreach my $linenumber ( keys %{$filetable} )
523 my $comp = $filetable->{$linenumber}->{'Component_'};
524 my $uniquename = $filetable->{$linenumber}->{'File'};
525 my $filename = $filetable->{$linenumber}->{'FileName'};
526 my $sequence = $filetable->{$linenumber}->{'Sequence'};
528 my $shortname = "";
529 if ( $filename =~ /^\s*(.*?)\|\s*(.*?)\s*$/ )
531 $shortname = $1;
532 $filename = $2;
535 # unique is the combination of $component and $filename
536 my $key = "$comp/$filename";
538 if ( exists($uniquefilename{$key}) ) { installer::exiter::exit_program("ERROR: Component/FileName \"$key\" is not unique in table \"File\" !", "create_database_hashes"); }
540 my $value = $uniquename;
541 if ( $shortname ne "" ) { $value = "$uniquename;$shortname"; }
542 $uniquefilename{$key} = $value; # saving the unique keys and short names in hash
544 # Saving reverse keys too
545 $revuniquefilename{$uniquename} = $key;
546 if ( $shortname ne "" ) { $revshortfilename{$shortname} = $key; }
548 # Saving Sequences for unique names (and also components)
549 $allupdatesequences{$uniquename} = $sequence;
550 $allupdatecomponents{$uniquename} = $comp;
552 # Saving unique names and components for sequences
553 $allupdatefileorder{$sequence} = $uniquename;
554 $allupdatecomponentorder{$sequence} = $comp;
557 # 2. Hash, required in Directory table.
559 my $dirtable = $database->{'Directory'};
561 foreach my $linenumber ( keys %{$dirtable} )
563 my $dir = $dirtable->{$linenumber}->{'Directory'}; # this is a unique name
564 my $defaultdir = $dirtable->{$linenumber}->{'DefaultDir'};
566 my $shortname = "";
567 if ( $defaultdir =~ /^\s*(.*?)\|\s*(.*?)\s*$/ )
569 $shortname = $1;
570 $shortdirname{$dir} = $shortname; # collecting only the short names
574 # 3. Hash, collecting info from Component table.
575 # ComponentID and KeyPath have to be reused.
577 my $comptable = $database->{'Component'};
579 foreach my $linenumber ( keys %{$comptable} )
581 my $comp = $comptable->{$linenumber}->{'Component'};
582 my $compid = $comptable->{$linenumber}->{'ComponentId'};
583 my $keypath = $comptable->{$linenumber}->{'KeyPath'};
585 $componentid{$comp} = $compid;
586 $componentidkeypath{$comp} = $keypath;
589 # 4. Hash, property table, required for ProductCode and Installlocation.
591 my $proptable = $database->{'Property'};
593 foreach my $linenumber ( keys %{$proptable} )
595 my $prop = $proptable->{$linenumber}->{'Property'};
596 my $value = $proptable->{$linenumber}->{'Value'};
598 $alloldproperties{$prop} = $value;
601 # 5. Media table, getting last sequence
603 my $mediatable = $database->{'Media'};
604 $installer::globals::updatelastsequence = 0;
606 foreach my $linenumber ( keys %{$mediatable} )
608 my $cabname = $mediatable->{$linenumber}->{'Cabinet'};
609 my $lastsequence = $mediatable->{$linenumber}->{'LastSequence'};
610 my $diskid = $mediatable->{$linenumber}->{'DiskId'};
611 $allupdatelastsequences{$cabname} = $lastsequence;
612 $allupdatediskids{$cabname} = $diskid;
614 if ( $lastsequence > $installer::globals::updatelastsequence ) { $installer::globals::updatelastsequence = $lastsequence; }
617 $installer::globals::updatesequencecounter = $installer::globals::updatelastsequence;
619 return (\%uniquefilename, \%revuniquefilename, \%revshortfilename, \%allupdatesequences, \%allupdatecomponents, \%allupdatefileorder, \%allupdatecomponentorder, \%shortdirname, \%componentid, \%componentidkeypath, \%alloldproperties, \%allupdatelastsequences, \%allupdatediskids);