InstallationSet.pm (c9b362f6) InstallationSet.pm (9f91b7e3)
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

--- 11 unchanged lines hidden (view full) ---

20#**************************************************************
21
22package installer::patch::InstallationSet;
23
24use installer::patch::Tools;
25use installer::patch::Version;
26use installer::logger;
27
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

--- 11 unchanged lines hidden (view full) ---

20#**************************************************************
21
22package installer::patch::InstallationSet;
23
24use installer::patch::Tools;
25use installer::patch::Version;
26use installer::logger;
27
28use strict;
28
29
30# TODO: Detect the location of 7z.exe
29my $Unpacker = "/c/Program\\ Files/7-Zip/7z.exe";
30
31my $Unpacker = "/c/Program\\ Files/7-Zip/7z.exe";
32
33
34
35# TODO: Is there a touch in a standard library?
36sub touch ($)
37{
38 my ($filename) = @_;
39
40 open my $out, ">", $filename;
41 close $out;
42}
43
44
45
46
31=head1 NAME
32
33 package installer::patch::InstallationSet - Functions for handling installation sets
34
35=head1 DESCRIPTION
36
37 This package contains functions for unpacking the .exe files that
38 are created by the NSIS installer creator and the .cab files in

--- 4 unchanged lines hidden (view full) ---

43sub UnpackExe ($$)
44{
45 my ($filename, $destination_path) = @_;
46
47 $installer::logger::Info->printf("unpacking installation set to '%s'\n", $destination_path);
48
49 # Unpack to a temporary path and change its name to the destination path
50 # only when the unpacking has completed successfully.
47=head1 NAME
48
49 package installer::patch::InstallationSet - Functions for handling installation sets
50
51=head1 DESCRIPTION
52
53 This package contains functions for unpacking the .exe files that
54 are created by the NSIS installer creator and the .cab files in

--- 4 unchanged lines hidden (view full) ---

59sub UnpackExe ($$)
60{
61 my ($filename, $destination_path) = @_;
62
63 $installer::logger::Info->printf("unpacking installation set to '%s'\n", $destination_path);
64
65 # Unpack to a temporary path and change its name to the destination path
66 # only when the unpacking has completed successfully.
51 my $temporary_destination_path = $destination_path . ".tmp";
52 File::Path::make_path($temporary_destination_path);
67 File::Path::make_path($destination_path);
53
68
54 my $windows_filename = installer::patch::Tools::CygpathToWindows($filename);
55 my $windows_destination_path = installer::patch::Tools::CygpathToWindows($temporary_destination_path);
69 my $windows_filename = installer::patch::Tools::ToEscapedWindowsPath($filename);
70 my $windows_destination_path = installer::patch::Tools::ToEscapedWindowsPath($destination_path);
56 my $command = join(" ",
57 $Unpacker,
71 my $command = join(" ",
72 $Unpacker,
58 "x", "-o".$windows_destination_path,
73 "x",
74 "-y",
75 "-o".$windows_destination_path,
59 $windows_filename);
60 my $result = qx($command);
61
62 # Check the existence of the .cab files.
76 $windows_filename);
77 my $result = qx($command);
78
79 # Check the existence of the .cab files.
63 my $cab_filename = File::Spec->catfile($temporary_destination_path, "openoffice1.cab");
80 my $cab_filename = File::Spec->catfile($destination_path, "openoffice1.cab");
64 if ( ! -f $cab_filename)
65 {
66 installer::logger::PrintError("cab file '%s' was not extracted from installation set\n", $cab_filename);
67 return 0;
68 }
81 if ( ! -f $cab_filename)
82 {
83 installer::logger::PrintError("cab file '%s' was not extracted from installation set\n", $cab_filename);
84 return 0;
85 }
69 if (rename($temporary_destination_path, $destination_path) == 0)
70 {
71 installer::logger::PrintError("can not rename temporary extraction directory\n");
72 return 0;
73 }
74 return 1;
75}
76
77
78
79
80=head2 UnpackCab($cab_filename, $destination_path)
81

--- 12 unchanged lines hidden (view full) ---

94sub UnpackCab ($$$)
95{
96 my ($cab_filename, $msi, $destination_path) = @_;
97
98 # Step 1
99 # Extract the directory structure from the 'File' and 'Directory' tables in the given msi.
100 $installer::logger::Info->printf("setting up directory tree\n");
101 my $file_table = $msi->GetTable("File");
86 return 1;
87}
88
89
90
91
92=head2 UnpackCab($cab_filename, $destination_path)
93

--- 12 unchanged lines hidden (view full) ---

106sub UnpackCab ($$$)
107{
108 my ($cab_filename, $msi, $destination_path) = @_;
109
110 # Step 1
111 # Extract the directory structure from the 'File' and 'Directory' tables in the given msi.
112 $installer::logger::Info->printf("setting up directory tree\n");
113 my $file_table = $msi->GetTable("File");
102 my $file_to_directory_map = $msi->GetFileToDirectoryMap();
114 my $file_map = $msi->GetFileMap();
103
104 # Step 2
105 # Unpack the .cab file to a temporary path.
106 my $temporary_destination_path = $destination_path . ".tmp";
107 if ( -d $temporary_destination_path)
108 {
109 # Temporary directory already exists => cab file has already been unpacked (flat), nothing to do.
110 $installer::logger::Info->printf("cab file has already been unpacked to flat structure\n");

--- 6 unchanged lines hidden (view full) ---

117 # Step 3
118 # Move the files to their destinations.
119 File::Path::make_path($destination_path);
120 $installer::logger::Info->printf("moving files to their directories\n");
121 my $count = 0;
122 foreach my $file_row (@{$file_table->GetAllRows()})
123 {
124 my $unique_name = $file_row->GetValue('File');
115
116 # Step 2
117 # Unpack the .cab file to a temporary path.
118 my $temporary_destination_path = $destination_path . ".tmp";
119 if ( -d $temporary_destination_path)
120 {
121 # Temporary directory already exists => cab file has already been unpacked (flat), nothing to do.
122 $installer::logger::Info->printf("cab file has already been unpacked to flat structure\n");

--- 6 unchanged lines hidden (view full) ---

129 # Step 3
130 # Move the files to their destinations.
131 File::Path::make_path($destination_path);
132 $installer::logger::Info->printf("moving files to their directories\n");
133 my $count = 0;
134 foreach my $file_row (@{$file_table->GetAllRows()})
135 {
136 my $unique_name = $file_row->GetValue('File');
125 my $directory_full_names = $file_to_directory_map->{$unique_name};
126 my ($source_full_name, $target_full_name) = @$directory_full_names;
137 my $directory_item = $file_map->{$unique_name}->{'directory'};
138 my $source_full_name = $directory_item->{'full_source_long_name'};
127
128 my $flat_filename = File::Spec->catfile($temporary_destination_path, $unique_name);
129 my $dir_path = File::Spec->catfile($destination_path, $source_full_name);
130 my $dir_filename = File::Spec->catfile($dir_path, $unique_name);
131
139
140 my $flat_filename = File::Spec->catfile($temporary_destination_path, $unique_name);
141 my $dir_path = File::Spec->catfile($destination_path, $source_full_name);
142 my $dir_filename = File::Spec->catfile($dir_path, $unique_name);
143
132 printf("%d: making path %s and copying %s to %s\n",
133 $count,
134 $dir_path,
135 $unique_name,
136 $dir_filename);
137 File::Path::make_path($dir_path);
144 if ( ! -d $dir_path)
145 {
146 File::Path::make_path($dir_path);
147 }
138 File::Copy::move($flat_filename, $dir_filename);
139
140 ++$count;
141 }
142
143 # Cleanup. Remove the temporary directory. It should be empty by now.
144 rmdir($temporary_destination_path);
145}

--- 15 unchanged lines hidden (view full) ---

161 my ($cab_filename, $destination_path, $file_table) = @_;
162
163 # Unpack the .cab file to a temporary path (note that
164 # $destination_path may alreay bee a temporary path). Using a
165 # second one prevents the lengthy flat unpacking to be repeated
166 # when another step fails.
167
168 $installer::logger::Info->printf("unpacking cab file\n");
148 File::Copy::move($flat_filename, $dir_filename);
149
150 ++$count;
151 }
152
153 # Cleanup. Remove the temporary directory. It should be empty by now.
154 rmdir($temporary_destination_path);
155}

--- 15 unchanged lines hidden (view full) ---

171 my ($cab_filename, $destination_path, $file_table) = @_;
172
173 # Unpack the .cab file to a temporary path (note that
174 # $destination_path may alreay bee a temporary path). Using a
175 # second one prevents the lengthy flat unpacking to be repeated
176 # when another step fails.
177
178 $installer::logger::Info->printf("unpacking cab file\n");
169 my $temporary_destination_path = $destination_path . ".tmp";
170 File::Path::make_path($temporary_destination_path);
171 my $windows_cab_filename = installer::patch::Tools::CygpathToWindows($cab_filename);
172 my $windows_destination_path = installer::patch::Tools::CygpathToWindows($temporary_destination_path);
179 File::Path::make_path($destination_path);
180 my $windows_cab_filename = installer::patch::Tools::ToEscapedWindowsPath($cab_filename);
181 my $windows_destination_path = installer::patch::Tools::ToEscapedWindowsPath($destination_path);
173 my $command = join(" ",
174 $Unpacker,
175 "x", "-o".$windows_destination_path,
176 $windows_cab_filename,
177 "-y");
182 my $command = join(" ",
183 $Unpacker,
184 "x", "-o".$windows_destination_path,
185 $windows_cab_filename,
186 "-y");
178 printf("running command '%s'\n", $command);
179 open my $cmd, $command."|";
180 my $extraction_count = 0;
181 my $file_count = $file_table->GetRowCount();
182 while (<$cmd>)
183 {
184 my $message = $_;
185 chomp($message);
186 ++$extraction_count;
187 printf("%4d/%4d %3.2f%% \r",
188 $extraction_count,
189 $file_count,
190 $extraction_count*100/$file_count);
191 }
192 close $cmd;
187 open my $cmd, $command."|";
188 my $extraction_count = 0;
189 my $file_count = $file_table->GetRowCount();
190 while (<$cmd>)
191 {
192 my $message = $_;
193 chomp($message);
194 ++$extraction_count;
195 printf("%4d/%4d %3.2f%% \r",
196 $extraction_count,
197 $file_count,
198 $extraction_count*100/$file_count);
199 }
200 close $cmd;
193 printf("extraction done \n");
194
195 rename($temporary_destination_path, $destination_path)
196 || installer::logger::PrintError(
197 "can not rename the temporary directory '%s' to '%s'\n",
198 $temporary_destination_path,
199 $destination_path);
200}
201
202
203
204
201}
202
203
204
205
205=head GetUnpackedMsiPath ($version, $language, $package_format, $product)
206=head GetUnpackedExePath ($version, $is_current_version, $language, $package_format, $product)
206
207 Convenience function that returns where a downloadable installation set is extracted to.
208
209=cut
207
208 Convenience function that returns where a downloadable installation set is extracted to.
209
210=cut
210sub GetUnpackedMsiPath ($$$$)
211sub GetUnpackedExePath ($$$$$)
211{
212{
212 my ($version, $language, $package_format, $product) = @_;
213 my ($version, $is_current_version, $language, $package_format, $product) = @_;
213
214
214 return File::Spec->catfile(
215 GetUnpackedPath($version, $language, $package_format, $product),
216 "unpacked_msi");
215 my $path = GetUnpackedPath($version, $is_current_version, $language, $package_format, $product);
216 return File::Spec->catfile($path, "unpacked");
217}
218
219
220
221
217}
218
219
220
221
222=head GetUnpackedCabPath ($version, $language, $package_format, $product)
222=head GetUnpackedCabPath ($version, $is_current_version, $language, $package_format, $product)
223
224 Convenience function that returns where a cab file is extracted
225 (with injected directory structure from the msi file) to.
226
227=cut
223
224 Convenience function that returns where a cab file is extracted
225 (with injected directory structure from the msi file) to.
226
227=cut
228sub GetUnpackedCabPath ($$$$)
228sub GetUnpackedCabPath ($$$$$)
229{
229{
230 my ($version, $language, $package_format, $product) = @_;
230 my ($version, $is_current_version, $language, $package_format, $product) = @_;
231
231
232 return File::Spec->catfile(
233 GetUnpackedPath($version, $language, $package_format, $product),
234 "unpacked_cab");
232 my $path = GetUnpackedPath($version, $is_current_version, $language, $package_format, $product);
233 return File::Spec->catfile($path, "unpacked");
235}
236
237
238
239
234}
235
236
237
238
240=head2 GetUnpackedPath($version, $language, $package_format, $product)
239=head2 GetUnpackedPath($version, $is_current_version, $language, $package_format, $product)
241
242 Internal function for creating paths to where archives are unpacked.
243
244=cut
240
241 Internal function for creating paths to where archives are unpacked.
242
243=cut
245sub GetUnpackedPath ($$$$)
244sub GetUnpackedPath ($$$$$)
246{
245{
247 my ($version, $language, $package_format, $product) = @_;
246 my ($version, $is_current_version, $language, $package_format, $product) = @_;
248
249 return File::Spec->catfile(
250 $ENV{'SRC_ROOT'},
251 "instsetoo_native",
252 $ENV{'INPATH'},
253 $product,
254 $package_format,
247
248 return File::Spec->catfile(
249 $ENV{'SRC_ROOT'},
250 "instsetoo_native",
251 $ENV{'INPATH'},
252 $product,
253 $package_format,
255 installer::patch::Version::ArrayToDirectoryName(installer::patch::Version::StringToNumberArray($version)),
256 $language);
254 installer::patch::Version::ArrayToDirectoryName(
255 installer::patch::Version::StringToNumberArray($version)),
256 $language);
257}
258
259
260
261
257}
258
259
260
261
262sub GetMsiFilename ($$)
263{
264 my ($path, $version) = @_;
265
266 my $no_dot_version = installer::patch::Version::ArrayToNoDotName(
267 installer::patch::Version::StringToNumberArray(
268 $version));
269 return File::Spec->catfile(
270 $path,
271 "openoffice" . $no_dot_version . ".msi");
272}
273
274
275
276
277sub GetCabFilename ($$)
278{
279 my ($path, $version) = @_;
280
281 return File::Spec->catfile(
282 $path,
283 "openoffice1.cab");
284}
285
286
287
288
262=head2 Download($language, $release_data, $filename)
263
264 Download an installation set to $filename. The URL for the
265 download is taken from $release_data, a snippet from the
266 instsetoo_native/data/releases.xml file.
267
268=cut
269sub Download ($$$)

--- 189 unchanged lines hidden (view full) ---

459 $installer::logger::Info->printf("download set could not be downloaded\n");
460 return 0;
461 }
462 }
463
464 return $ext_sources_filename;
465}
466
289=head2 Download($language, $release_data, $filename)
290
291 Download an installation set to $filename. The URL for the
292 download is taken from $release_data, a snippet from the
293 instsetoo_native/data/releases.xml file.
294
295=cut
296sub Download ($$$)

--- 189 unchanged lines hidden (view full) ---

486 $installer::logger::Info->printf("download set could not be downloaded\n");
487 return 0;
488 }
489 }
490
491 return $ext_sources_filename;
492}
493
494
495
496
497sub ProvideUnpackedExe ($$$$$)
498{
499 my ($version, $is_current_version, $language, $package_format, $product_name) = @_;
500
501 # Check if the exe has already been unpacked.
502 my $unpacked_exe_path = installer::patch::InstallationSet::GetUnpackedExePath(
503 $version,
504 $is_current_version,
505 $language,
506 $package_format,
507 $product_name);
508 my $unpacked_exe_flag_filename = File::Spec->catfile($unpacked_exe_path, "__exe_is_unpacked");
509 my $exe_is_unpacked = -f $unpacked_exe_flag_filename;
510
511 if ($exe_is_unpacked)
512 {
513 # Yes, exe has already been unpacked. There is nothing more to do.
514 $installer::logger::Info->printf("downloadable installation set has already been unpacked to\n");
515 $installer::logger::Info->printf(" %s\n", $unpacked_exe_path);
516 return 1;
517 }
518 elsif ($is_current_version)
519 {
520 # For the current version the exe is created from the unpacked
521 # content and both are expected to be already present.
522
523 # In order to have the .cab and its unpacked content in one
524 # directory and don't interfere with the creation of regular
525 # installation sets, we copy the unpacked .exe into a separate
526 # directory.
527
528 my $original_path = File::Spec->catfile(
529 $ENV{'SRC_ROOT'},
530 "instsetoo_native",
531 $ENV{'INPATH'},
532 $product_name,
533 $package_format,
534 "install",
535 $language);
536 $installer::logger::Info->printf("creating a copy\n");
537 $installer::logger::Info->printf(" of %s\n", $original_path);
538 $installer::logger::Info->printf(" at %s\n", $unpacked_exe_path);
539 File::Path::make_path($unpacked_exe_path) unless -d $unpacked_exe_path;
540 my ($file_count,$directory_count) = CopyRecursive($original_path, $unpacked_exe_path);
541 return 0 if ( ! defined $file_count);
542 $installer::logger::Info->printf(" copied %d files in %d directories\n",
543 $file_count,
544 $directory_count);
545
546 touch($unpacked_exe_flag_filename);
547
548 return 1;
549 }
550 else
551 {
552 # No, we have to unpack the exe.
553
554 # Provide the exe.
555 my $filename = installer::patch::InstallationSet::ProvideDownloadSet(
556 $version,
557 $language,
558 $package_format);
559
560 # Unpack it.
561 if (defined $filename)
562 {
563 if (installer::patch::InstallationSet::UnpackExe($filename, $unpacked_exe_path))
564 {
565 $installer::logger::Info->printf("downloadable installation set has been unpacked to\n");
566 $installer::logger::Info->printf(" %s\n", $unpacked_exe_path);
567
568 touch($unpacked_exe_flag_filename);
569
570 return 1;
571 }
572 }
573 else
574 {
575 installer::logger::PrintError("could not provide .exe installation set at '%s'\n", $filename);
576 }
577 }
578
579 return 0;
580}
581
582
583
584
585sub CopyRecursive ($$)
586{
587 my ($source_path, $destination_path) = @_;
588
589 return (undef,undef) unless -d $source_path;
590
591 my @todo = ([$source_path, $destination_path]);
592 my $file_count = 0;
593 my $directory_count = 0;
594 while (scalar @todo > 0)
595 {
596 my ($source,$destination) = @{shift @todo};
597
598 next if ! -d $source;
599 File::Path::make_path($destination);
600 ++$directory_count;
601
602 # Read list of files in the current source directory.
603 opendir( my $dir, $source);
604 my @files = readdir $dir;
605 closedir $dir;
606
607 # Copy all files and push all directories to @todo.
608 foreach my $file (@files)
609 {
610 next if $file =~ /^\.+$/;
611
612 my $source_file = File::Spec->catfile($source, $file);
613 my $destination_file = File::Spec->catfile($destination, $file);
614 if ( -f $source_file)
615 {
616 File::Copy::copy($source_file, $destination_file);
617 ++$file_count;
618 }
619 elsif ( -d $source_file)
620 {
621 push @todo, [$source_file, $destination_file];
622 }
623 }
624 }
625
626 return ($file_count, $directory_count);
627}
628
629
630
631
632sub CheckLocalCopy ($$$$)
633{
634 my ($version, $language, $package_format, $product_name) = @_;
635
636 # Compare creation times of the original .msi and its copy.
637
638 my $original_path = File::Spec->catfile(
639 $ENV{'SRC_ROOT'},
640 "instsetoo_native",
641 $ENV{'INPATH'},
642 $product_name,
643 $package_format,
644 "install",
645 $language);
646
647 my $copy_path = installer::patch::InstallationSet::GetUnpackedExePath(
648 $version,
649 1,
650 $language,
651 $package_format,
652 $product_name);
653
654 my $msi_basename = "openoffice"
655 . installer::patch::Version::ArrayToNoDotName(
656 installer::patch::Version::StringToNumberArray($version))
657 . ".msi";
658
659 my $original_msi_filename = File::Spec->catfile($original_path, $msi_basename);
660 my $copied_msi_filename = File::Spec->catfile($copy_path, $msi_basename);
661
662 my @original_msi_stats = stat($original_msi_filename);
663 my @copied_msi_stats = stat($copied_msi_filename);
664 my $original_msi_mtime = $original_msi_stats[9];
665 my $copied_msi_mtime = $copied_msi_stats[9];
666
667 if (defined $original_msi_mtime
668 && defined $copied_msi_mtime
669 && $original_msi_mtime > $copied_msi_mtime)
670 {
671 # The installation set is newer than its copy.
672 # Remove the copy.
673 $installer::logger::Info->printf(
674 "removing copy of installation set (version %s) because it is out of date\n",
675 $version);
676 File::Path::remove_tree($copy_path);
677 }
678}
679
680
681
682
683=head2 ProvideUnpackedCab
684
685 1a. Make sure that a downloadable installation set is present.
686 1b. or that a freshly built installation set (packed and unpacked is present)
687 2. Unpack the downloadable installation set
688 3. Unpack the .cab file.
689
690 The 'Provide' in the function name means that any step that has
691 already been made is not made again.
692
693=cut
694sub ProvideUnpackedCab ($$$$$)
695{
696 my ($version, $is_current_version, $language, $package_format, $product_name) = @_;
697
698 if ($is_current_version)
699 {
700 # For creating patches we maintain a copy of the unpacked .exe. Make sure that that is updated when
701 # a new installation set has been built.
702 CheckLocalCopy($version, $language, $package_format, $product_name);
703 }
704
705 # Check if the cab file has already been unpacked.
706 my $unpacked_cab_path = installer::patch::InstallationSet::GetUnpackedCabPath(
707 $version,
708 $is_current_version,
709 $language,
710 $package_format,
711 $product_name);
712 my $unpacked_cab_flag_filename = File::Spec->catfile($unpacked_cab_path, "__cab_is_unpacked");
713 my $cab_is_unpacked = -f $unpacked_cab_flag_filename;
714
715 if ($cab_is_unpacked)
716 {
717 # Yes. Cab was already unpacked. There is nothing more to do.
718 $installer::logger::Info->printf("cab has already been unpacked to\n");
719 $installer::logger::Info->printf(" %s\n", $unpacked_cab_path);
720
721 return 1;
722 }
723 else
724 {
725 # Make sure that the exe is unpacked and the cab file exists.
726 ProvideUnpackedExe($version, $is_current_version, $language, $package_format, $product_name);
727
728 # Unpack the cab file.
729 my $unpacked_exe_path = installer::patch::InstallationSet::GetUnpackedExePath(
730 $version,
731 $is_current_version,
732 $language,
733 $package_format,
734 $product_name);
735 my $msi = new installer::patch::Msi(
736 installer::patch::InstallationSet::GetMsiFilename($unpacked_exe_path, $version),
737 $version,
738 $is_current_version,
739 $language,
740 $product_name);
741
742 my $cab_filename = installer::patch::InstallationSet::GetCabFilename(
743 $unpacked_exe_path,
744 $version);
745 if ( ! -f $cab_filename)
746 {
747 # Cab file does not exist.
748 installer::logger::PrintError(
749 "could not find .cab file at '%s'. Extraction of .exe seems to have failed.\n",
750 $cab_filename);
751 return 0;
752 }
753
754 if (installer::patch::InstallationSet::UnpackCab(
755 $cab_filename,
756 $msi,
757 $unpacked_cab_path))
758 {
759 $installer::logger::Info->printf("unpacked cab file '%s'\n", $cab_filename);
760 $installer::logger::Info->printf(" to '%s'\n", $unpacked_cab_path);
761
762 touch($unpacked_cab_flag_filename);
763
764 return 1;
765 }
766 else
767 {
768 return 0;
769 }
770 }
771}
4671;
7721;