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::component;
25
26use installer::converter;
27use installer::existence;
28use installer::exiter;
29use installer::files;
30use installer::globals;
31use installer::windows::idtglobal;
32use installer::windows::language;
33
34use strict;
35
36##############################################################
37# Returning a globally unique ID (GUID) for a component
38# If the component is new, a unique guid has to be created.
39# If the component already exists, the guid has to be
40# taken from a list component <-> guid
41# Sample for a guid: {B68FD953-3CEF-4489-8269-8726848056E8}
42##############################################################
43
44sub get_component_guid ($)
45{
46	my ($componentname) = @_;
47
48	# At this time only a template
49	my $returnvalue = "\{COMPONENTGUID\}";
50
51	# Returning a ComponentID, that is assigned in scp project
52	if ( exists($installer::globals::componentid{$componentname}) )
53	{
54        $installer::logger::Lang->printf("reusing guid %s for component %s\n",
55            $installer::globals::componentid{$componentname},
56            $componentname);
57		$returnvalue = "\{" . $installer::globals::componentid{$componentname} . "\}";
58	}
59
60	return $returnvalue;
61}
62
63##############################################################
64# Returning the directory for a file component.
65##############################################################
66
67sub get_file_component_directory ($$$)
68{
69    my ($componentname, $filesref, $dirref) = @_;
70
71    my ($component,  $uniquedir);
72
73    foreach my $onefile (@$filesref)
74    {
75        if ($onefile->{'componentname'} eq $componentname)
76        {
77            return get_file_component_directory_for_file($onefile, $dirref);
78        }
79    }
80
81    # This component can be ignored, if it exists in a version with
82    # extension "_pff" (this was renamed in file::get_sequence_for_file() )
83    my $ignore_this_component = 0;
84    my $origcomponentname = $componentname;
85    my $componentname_pff = $componentname . "_pff";
86
87    foreach my $onefile (@$filesref)
88    {
89        if ($onefile->{'componentname'} eq $componentname_pff)
90        {
91            return "IGNORE_COMP";
92        }
93    }
94
95    installer::exiter::exit_program(
96        "ERROR: Did not find component \"$origcomponentname\" in file collection",
97        "get_file_component_directory");
98}
99
100
101
102
103sub get_file_component_directory_for_file ($$)
104{
105    my ($onefile, $dirref) = @_;
106
107	my $localstyles = $onefile->{'Styles'} // "";
108
109	if ( $localstyles =~ /\bFONT\b/ )	# special handling for font files
110	{
111		return $installer::globals::fontsfolder;
112	}
113
114	my $destdir = "";
115
116	if ( $onefile->{'Dir'} ) { $destdir = $onefile->{'Dir'}; }
117
118	if ( $destdir =~ /\bPREDEFINED_OSSHELLNEWDIR\b/ )	# special handling for shellnew files
119	{
120		return $installer::globals::templatefolder;
121	}
122
123	my $destination = $onefile->{'destination'};
124
125	installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination);
126
127	$destination =~ s/\Q$installer::globals::separator\E\s*$//;
128
129	# This path has to be defined in the directory collection at "HostName"
130
131    my $uniquedir = undef;
132	if ($destination eq "")		# files in the installation root
133	{
134		$uniquedir = "INSTALLLOCATION";
135	}
136	else
137	{
138		my $found = 0;
139        foreach my $directory (@$dirref)
140		{
141			if ($directory->{'HostName'} eq $destination)
142			{
143				$found = 1;
144                $uniquedir = $directory->{'uniquename'};
145				last;
146			}
147		}
148
149		if ( ! $found)
150		{
151			installer::exiter::exit_program(
152                "ERROR: Did not find destination $destination in directory collection",
153                "get_file_component_directory");
154		}
155
156		if ( $uniquedir eq $installer::globals::officeinstalldirectory )
157		{
158			$uniquedir = "INSTALLLOCATION";
159		}
160	}
161
162	$onefile->{'uniquedirname'} = $uniquedir;		# saving it in the file collection
163
164	return $uniquedir
165}
166
167##############################################################
168# Returning the directory for a registry component.
169# This cannot be a useful value
170##############################################################
171
172sub get_registry_component_directory
173{
174	my $componentdir = "INSTALLLOCATION";
175
176	return $componentdir;
177}
178
179##############################################################
180# Returning the attributes for a file component.
181# Always 8 in this first try?
182##############################################################
183
184sub get_file_component_attributes
185{
186	my ($componentname, $filesref, $allvariables) = @_;
187
188	my $attributes;
189
190	$attributes = 2;
191
192	# special handling for font files
193
194	my $onefile;
195	my $found = 0;
196
197	for ( my $i = 0; $i <= $#{$filesref}; $i++ )
198	{
199		$onefile = 	${$filesref}[$i];
200		my $component = $onefile->{'componentname'};
201
202		if ( $component eq $componentname )
203		{
204			$found = 1;
205			last;
206		}
207	}
208
209	if (!($found))
210	{
211		installer::exiter::exit_program("ERROR: Did not find component in file collection", "get_file_component_attributes");
212	}
213
214	my $localstyles = "";
215
216	if ( $onefile->{'Styles'} ) { $localstyles = $onefile->{'Styles'}; }
217
218	if ( $localstyles =~ /\bFONT\b/ )
219	{
220		$attributes = 8;	# font files will be deinstalled if the ref count is 0
221	}
222
223	if ( $localstyles =~ /\bASSEMBLY\b/ )
224	{
225		$attributes = 0;	# Assembly files cannot run from source
226	}
227
228	if ((defined $onefile->{'Dir'} && $onefile->{'Dir'} =~ /\bPREDEFINED_OSSHELLNEWDIR\b/)
229        || $onefile->{'needs_user_registry_key'})
230	{
231		$attributes = 4;	# Files in shellnew dir and in non advertised startmenu entries must have user registry key as KeyPath
232	}
233
234	# Adding 256, if this is a 64 bit installation set.
235	if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) { $attributes = $attributes + 256; }
236
237	return $attributes
238}
239
240##############################################################
241# Returning the attributes for a registry component.
242# Always 4, indicating, the keypath is a defined in
243# table registry
244##############################################################
245
246sub get_registry_component_attributes
247{
248	my ($componentname, $allvariables) = @_;
249
250	my $attributes;
251
252	$attributes = 4;
253
254	# Adding 256, if this is a 64 bit installation set.
255	if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) { $attributes = $attributes + 256; }
256
257	if ( exists($installer::globals::dontdeletecomponents{$componentname}) ) { $attributes = $attributes + 16; }
258
259	return $attributes
260}
261
262##############################################################
263# Returning the conditions for a component.
264# This is important for language dependent components
265# in multilingual installation sets.
266##############################################################
267
268sub get_file_component_condition
269{
270	my ($componentname, $filesref) = @_;
271
272	my $condition = "";
273
274	if (exists($installer::globals::componentcondition{$componentname}))
275	{
276		$condition = $installer::globals::componentcondition{$componentname};
277	}
278
279	# there can be also tree conditions for multilayer products
280	if (exists($installer::globals::treeconditions{$componentname}))
281	{
282		if ( $condition eq "" )
283		{
284			$condition = $installer::globals::treeconditions{$componentname};
285		}
286		else
287		{
288			$condition = "($condition) And ($installer::globals::treeconditions{$componentname})";
289		}
290	}
291
292	return $condition
293}
294
295##############################################################
296# Returning the conditions for a registry component.
297##############################################################
298
299sub get_component_condition
300{
301	my ($componentname) = @_;
302
303	my $condition;
304
305	$condition = "";	# Always ?
306
307	if (exists($installer::globals::componentcondition{$componentname}))
308	{
309		$condition = $installer::globals::componentcondition{$componentname};
310	}
311
312	return $condition
313}
314
315####################################################################
316# Returning the keypath for a component.
317# This will be the name of the first file/registry, found in the
318# collection $itemsref
319# Attention: This has to be the unique (file)name, not the
320# real filename!
321####################################################################
322
323sub get_component_keypath ($$)
324{
325	my ($componentname, $itemsref) = @_;
326
327	foreach my $oneitem (@$itemsref)
328	{
329		my $component = $oneitem->{'componentname'};
330
331		if ( ! defined $component)
332        {
333            installer::scriptitems::print_script_item($oneitem);
334            installer::logger::PrintError("item in get_component_keypath has no 'componentname'\n");
335            return "";
336        }
337		if ( $component eq $componentname )
338		{
339            my $keypath = $oneitem->{'uniquename'};	# "uniquename", not "Name"
340
341            # Special handling for components in
342            # PREDEFINED_OSSHELLNEWDIR. These components need as
343            # KeyPath a RegistryItem in HKCU
344            if ($oneitem->{'userregkeypath'})
345            {
346                $keypath = $oneitem->{'userregkeypath'};
347            }
348
349            # saving it in the file and registry collection
350            $oneitem->{'keypath'} = $keypath;
351
352            return $keypath
353		}
354	}
355
356    installer::exiter::exit_program(
357        "ERROR: Did not find component in file/registry collection, function get_component_keypath",
358        "get_component_keypath");
359}
360
361
362
363
364sub remove_ooversion_from_component_name($)
365{
366    my ($component_name) = @_;
367
368    $component_name =~ s/_openoffice\d+//;
369
370    return $component_name;
371}
372
373
374
375
376sub prepare_component_table_creation ($$$)
377{
378    my ($file_components, $registry_components, $variables) = @_;
379
380    if ($installer::globals::is_release)
381    {
382        my %source_component_data = ();
383
384        # Collect the components that are used in the source release.
385        my $component_table = $installer::globals::source_msi->GetTable("Component");
386        foreach my $row (@{$component_table->GetAllRows()})
387        {
388            $source_component_data{$row->GetValue("Component")} = $row;
389        }
390
391        # Find source components that do not exist in the target components, ie have been removed.
392
393        # Process file components.
394        my @missing_source_component_names = ();
395        my %file_component_hash = map {$_ => 1} @$file_components;
396        foreach my $source_component_name (keys %source_component_data)
397        {
398            # In this loop we only process components for files and ignore those for registry entries.
399            next if $source_component_name =~ /^registry_/;
400
401            if ( ! defined $file_component_hash{$source_component_name})
402            {
403                push @missing_source_component_names, [$source_component_name, $source_component_name];
404                $installer::logger::Info->printf("missing file component %s\n", $source_component_name);
405            }
406        }
407
408        # Process registry components.
409        my %registry_component_hash = map {$_ => 1} @$registry_components;
410        my %registry_component_hash_normalized = map {remove_ooversion_from_component_name($_) => $_} @$registry_components;
411        my %target_registry_component_translation = ();
412        foreach my $source_component_name (keys %source_component_data)
413        {
414            # In this loop we only process components for registry entries and ignore those for files.
415            next if $source_component_name !~ /^registry_/;
416
417            if (defined $registry_component_hash{$source_component_name})
418            {
419                # Found the non-normalized name.
420            }
421            elsif (defined $registry_component_hash_normalized{
422                remove_ooversion_from_component_name($source_component_name)})
423            {
424                # Found the normalized name.
425                my $target_component_name = $registry_component_hash_normalized{
426                    remove_ooversion_from_component_name($source_component_name)};
427                $target_registry_component_translation{$target_component_name} = $source_component_name;
428                $installer::logger::Info->printf("found normalized component name %s\n", $source_component_name);
429                $installer::logger::Info->printf("    %s -> %s\n", $target_component_name, $source_component_name);
430            }
431            else
432            {
433                # Source component was not found.
434                push @missing_source_component_names, $source_component_name;
435                $installer::logger::Info->printf("missing component %s\n", $source_component_name);
436            }
437        }
438
439        if (scalar @missing_source_component_names > 0)
440        {
441            $installer::logger::Info->printf("Error: there are %d missing components\n",
442                scalar @missing_source_component_names);
443            return {};
444        }
445        else
446        {
447            return \%target_registry_component_translation;
448        }
449    }
450
451    return {};
452}
453
454
455
456
457sub get_component_data ($$$$)
458{
459	my ($file_component_names,
460        $registry_component_names,
461        $files,
462        $registry_entries) = @_;
463
464    # When we are building a release then prepare building a patch by looking up some data
465    # from the previous release.
466    my %source_data = ();
467    if ($installer::globals::is_release)
468    {
469        my $source_component_table = $installer::globals::source_msi->GetTable("Component");
470        my $component_column_index = $source_component_table->GetColumnIndex("Component");
471        my $component_id_column_index = $source_component_table->GetColumnIndex("ComponentId");
472        my $key_path_column_index = $source_component_table->GetColumnIndex("KeyPath");
473        foreach my $source_row (@{$source_component_table->GetAllRows()})
474        {
475            my $component_name = $source_row->GetValue($component_column_index);
476            my $component_id = $source_row->GetValue($component_id_column_index);
477            my $key_path = $source_row->GetValue($key_path_column_index);
478
479            $source_data{$component_name} = {
480                'component_id' => $component_id,
481                'key_path' => $key_path
482            };
483        }
484    }
485
486    # Set up data for the target release.
487    # Use data from the source version where possible.
488    # Create missind data where necessary.
489
490    # Set up the target data with flags that remember whether a
491    # component contains files or registry entries.
492    my %target_data = ();
493    foreach my $name (@$file_component_names)
494    {
495        $target_data{$name} = {'is_file' => 1};
496    }
497    foreach my $name (@$registry_component_names)
498    {
499        $target_data{$name} = {'is_file' => 0};
500    }
501
502    # Add values for the ComponentId column.
503    $installer::logger::Lang->printf("preparing Component->ComponentId values\n");
504    foreach my $name (@$file_component_names,@$registry_component_names)
505    {
506        # Determine the component id.
507        my $guid = $installer::globals::is_release
508            ? $source_data{$name}->{'component_id'}
509            : undef;
510        if (defined $guid)
511        {
512            $installer::logger::Lang->printf("    reusing guid %s\n", $guid);
513        }
514        else
515        {
516            $guid = installer::windows::msiglobal::create_guid();
517            $installer::logger::Lang->printf("    creating new guid %s\n", $guid);
518        }
519        $target_data{$name}->{'component_id'} = $guid;
520    }
521
522    # Add values for the KeyPath column.
523    $installer::logger::Lang->printf("preparing Component->KeyPath values\n");
524    foreach my $name (@$file_component_names,@$registry_component_names)
525    {
526        # Determine the key path.
527        my $key_path = $installer::globals::is_release
528            ? $source_data{$name}->{'key_path'}
529            : undef;
530        if (defined $key_path)
531        {
532            $installer::logger::Lang->printf("    reusing key path %s\n", $key_path);
533        }
534        else
535        {
536            if ($target_data{$name}->{'is_file'})
537            {
538                $key_path = get_component_keypath($name, $files);
539            }
540            else
541            {
542                $key_path = get_component_keypath($name, $registry_entries);
543            }
544        }
545        $target_data{$name}->{'key_path'} = $key_path;
546    }
547
548    return \%target_data;
549}
550
551
552
553
554sub	create_component_table_data ($$$$$$)
555{
556	my ($filesref, $registryref, $dirref, $allfilecomponentsref, $allregistrycomponents, $allvariables) = @_;
557
558    my $target_data = get_component_data($allfilecomponentsref, $allregistrycomponents, $filesref, $registryref);
559
560    my @table_data = ();
561
562	# File components
563	foreach my $name (@$allfilecomponentsref)
564	{
565		my %onecomponent = ();
566
567		$onecomponent{'name'} = $name;
568		$onecomponent{'guid'} = $target_data->{$name}->{'component_id'};
569		$onecomponent{'directory'} = get_file_component_directory($name, $filesref, $dirref);
570		if ( $onecomponent{'directory'} eq "IGNORE_COMP" ) { next; }
571		$onecomponent{'attributes'} = get_file_component_attributes($name, $filesref, $allvariables);
572		$onecomponent{'condition'} = get_file_component_condition($name, $filesref);
573		$onecomponent{'keypath'} = $target_data->{$name}->{'key_path'};
574
575        push @table_data, \%onecomponent;
576	}
577
578	# Registry components
579	foreach my $name (@$allregistrycomponents)
580	{
581		my %onecomponent = ();
582
583        $onecomponent{'name'} = $name;
584		$onecomponent{'guid'} = $target_data->{$name}->{'component_id'};
585		$onecomponent{'directory'} = get_registry_component_directory();
586		$onecomponent{'attributes'} = get_registry_component_attributes($name, $allvariables);
587		$onecomponent{'condition'} = get_component_condition($name);
588		$onecomponent{'keypath'} = $target_data->{$name}->{'key_path'};
589
590		push(@table_data, \%onecomponent);
591	}
592
593    return \@table_data;
594}
595
596
597
598
599###################################################################
600# Creating the file Componen.idt dynamically
601# Content:
602# Component ComponentId Directory_ Attributes Condition KeyPath
603###################################################################
604
605
606sub	create_component_table ($$)
607{
608	my ($table_data, $basedir) = @_;
609
610	my @componenttable = ();
611
612	my ($oneline, $infoline);
613
614	installer::windows::idtglobal::write_idt_header(\@componenttable, "component");
615
616	foreach my $item (@$table_data)
617	{
618		$oneline = sprintf("%s\t%s\t%s\t%s\t%s\t%s\n",
619            $item->{'name'},
620            $item->{'guid'},
621            $item->{'directory'},
622            $item->{'attributes'},
623            $item->{'condition'},
624            $item->{'keypath'});
625		push(@componenttable, $oneline);
626	}
627
628	# Saving the file
629
630	my $componenttablename = $basedir . $installer::globals::separator . "Componen.idt";
631	installer::files::save_file($componenttablename ,\@componenttable);
632	$infoline = "Created idt file: $componenttablename\n";
633	$installer::logger::Lang->print($infoline);
634}
635
636
637
638
639####################################################################################
640# Returning a component for a scp module gid.
641# Pairs are saved in the files collector.
642####################################################################################
643
644sub get_component_name_from_modulegid
645{
646	my ($modulegid, $filesref) = @_;
647
648	my $componentname = "";
649
650	for ( my $i = 0; $i <= $#{$filesref}; $i++ )
651	{
652		my $onefile = ${$filesref}[$i];
653
654		if ( $onefile->{'modules'} )
655		{
656			my $filemodules = $onefile->{'modules'};
657			my $filemodulesarrayref = installer::converter::convert_stringlist_into_array_without_newline(\$filemodules, ",");
658
659			if (installer::existence::exists_in_array($modulegid, $filemodulesarrayref))
660			{
661				$componentname = $onefile->{'componentname'};
662				last;
663			}
664		}
665	}
666
667	return $componentname;
668}
669
670####################################################################################
671# Updating the file Environm.idt dynamically
672# Content:
673# Environment Name Value Component_
674####################################################################################
675
676sub set_component_in_environment_table
677{
678	my ($basedir, $filesref) = @_;
679
680	my $infoline = "";
681
682	my $environmentfilename = $basedir . $installer::globals::separator . "Environm.idt";
683
684	if ( -f $environmentfilename )	# only do something, if file exists
685	{
686		my $environmentfile = installer::files::read_file($environmentfilename);
687
688		for ( my $i = 3; $i <= $#{$environmentfile}; $i++ )	# starting in line 4 of Environm.idt
689		{
690			if ( ${$environmentfile}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
691			{
692				my $modulegid = $4; # in Environment table a scp module gid can be used as component replacement
693
694				my $componentname = get_component_name_from_modulegid($modulegid, $filesref);
695
696				if ( $componentname )	# only do something if a component could be found
697				{
698					$infoline = "Updated Environment table:\n";
699					$installer::logger::Lang->print($infoline);
700					$infoline = "Old line: ${$environmentfile}[$i]\n";
701					$installer::logger::Lang->print($infoline);
702
703					${$environmentfile}[$i] =~ s/$modulegid/$componentname/;
704
705					$infoline = "New line: ${$environmentfile}[$i]\n";
706					$installer::logger::Lang->print($infoline);
707
708				}
709			}
710		}
711
712		# Saving the file
713
714		installer::files::save_file($environmentfilename ,$environmentfile);
715		$infoline = "Updated idt file: $environmentfilename\n";
716		$installer::logger::Lang->print($infoline);
717
718	}
719}
720
721
722
723
724sub apply_component_translation ($@)
725{
726    my ($translation_map, @component_names) = @_;
727
728    my @translated_names = ();
729    foreach my $component_name (@component_names)
730    {
731        my $translated_name = $translation_map->{$component_name};
732        if (defined $translated_name)
733        {
734            push @translated_names, $translated_name;
735        }
736        else
737        {
738            push @translated_names, $component_name;
739        }
740    }
741
742    return @translated_names;
743}
744
745
7461;
747