#**************************************************************
#  
#  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::msiglobal;

use Cwd;
use Digest::MD5;
use installer::converter;
use installer::exiter;
use installer::files;
use installer::globals;
use installer::logger;
use installer::pathanalyzer;
use installer::remover;
use installer::scriptitems;
use installer::systemactions;
use installer::worker;
use installer::windows::idtglobal;
use installer::windows::language;

###########################################################################
# Generating the header of the ddf file.
# The usage of ddf files is needed, because makecab.exe can only include
# one sourcefile into a cab file
###########################################################################

sub write_ddf_file_header
{
	my ($ddffileref, $cabinetfile, $installdir) = @_;
	
	my $oneline;
	
	$oneline = ".Set CabinetName1=" . $cabinetfile . "\n";
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set ReservePerCabinetSize=128\n";	# This reserves space for a digital signature.
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set MaxDiskSize=2147483648\n";		# This allows the .cab file to get a size of 2 GB.
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set CompressionType=LZX\n";
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set Compress=ON\n";
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set CompressionLevel=$installer::globals::cabfilecompressionlevel\n";
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set Cabinet=ON\n";
	push(@{$ddffileref} ,$oneline);
	$oneline = ".Set DiskDirectoryTemplate=" . $installdir . "\n";
	push(@{$ddffileref} ,$oneline);
}

##########################################################################
# Lines in ddf files must not contain more than 256 characters
##########################################################################

sub check_ddf_file
{
	my ( $ddffile, $ddffilename ) = @_;

	my $maxlength = 0;
	my $maxline = 0;
	my $linelength = 0;
	my $linenumber = 0;

	for ( my $i = 0; $i <= $#{$ddffile}; $i++ )
	{
		my $oneline = ${$ddffile}[$i];
		
		$linelength = length($oneline);
		$linenumber = $i + 1;
		
		if ( $linelength > 256 )
		{
			installer::exiter::exit_program("ERROR \"$ddffilename\" line $linenumber: Lines in ddf files must not contain more than 256 characters!", "check_ddf_file");
		}
		
		if ( $linelength > $maxlength )
		{
			$maxlength = $linelength;
			$maxline = $linenumber;
		}
	}
	
	my $infoline = "Check of ddf file \"$ddffilename\": Maximum length \"$maxlength\" in line \"$maxline\" (allowed line length: 256 characters)\n"; 
	$installer::logger::Lang->print($infoline);
}

##########################################################################
# Lines in ddf files must not be longer than 256 characters.
# Therefore it can be useful to use relative pathes. Then it is
# necessary to change into temp directory before calling 
# makecab.exe.
##########################################################################

sub make_relative_ddf_path
{
	my ( $sourcepath ) = @_;

	my $windowstemppath = $installer::globals::temppath;

	if ( $^O =~ /cygwin/i )
	{
		$windowstemppath = $installer::globals::cyg_temppath;
	}
	
	$sourcepath =~ s/\Q$windowstemppath\E//;
	$sourcepath =~ s/^\\//;
			
	return $sourcepath;
}

##########################################################################
# Returning the order of the sequences in the files array.
##########################################################################

sub get_sequenceorder
{
	my ($filesref) = @_;

	my %order = ();
	
	for ( my $i = 0; $i <= $#{$filesref}; $i++ )
	{
		my $onefile = ${$filesref}[$i];
		if ( ! $onefile->{'assignedsequencenumber'} ) { installer::exiter::exit_program("ERROR: No sequence number assigned to $onefile->{'gid'} ($onefile->{'uniquename'})!", "get_sequenceorder"); }
		$order{$onefile->{'assignedsequencenumber'}} = $i;
	}
	
	return \%order;
}

##########################################################################
# Generation the list, in which the source of the files is connected
# with the cabinet destination file. Because more than one file needs
# to be included into a cab file, this has to be done via ddf files.
##########################################################################

sub generate_cab_file_list
{
	my ($filesref, $installdir, $ddfdir, $allvariables) = @_;
	
	my @cabfilelist = ();
		
	installer::logger::include_header_into_logfile("Generating ddf files");

	$installer::logger::Lang->add_timestamp("Performance Info: ddf file generation start");

	if ( $^O =~ /cygwin/i ) { installer::worker::generate_cygwin_pathes($filesref); }

	if (( $installer::globals::fix_number_of_cab_files ) && ( $installer::globals::updatedatabase ))
	{
		my $sequenceorder = get_sequenceorder($filesref);
		
		my $counter = 1;
		my $currentcabfile = "";
		
		while ( ( exists($sequenceorder->{$counter}) ) || ( exists($installer::globals::allmergemodulefilesequences{$counter}) ) ) # Taking care of files from merge modules
		{
#			if ( exists($installer::globals::allmergemodulefilesequences{$counter}) )
#			{
#				# Skipping this sequence, it is not included in $filesref, because it is assigned to a file from a merge module.\n";
#				$counter++;
#				next;	
#			}

			my $onefile = ${$filesref}[$sequenceorder->{$counter}];
			$counter++;

			my $cabinetfile = $onefile->{'cabinet'};
			my $sourcepath =  $onefile->{'sourcepath'};
			if ( $^O =~ /cygwin/i ) { $sourcepath = $onefile->{'cyg_sourcepath'}; }
			my $uniquename =  $onefile->{'uniquename'};

			my $styles = "";
			my $doinclude = 1;
			if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; };
			if ( $styles =~ /\bDONT_PACK\b/ ) { $doinclude = 0; }

			# to avoid lines with more than 256 characters, it can be useful to use relative pathes
			if ( $allvariables->{'RELATIVE_PATHES_IN_DDF'} ) { $sourcepath = make_relative_ddf_path($sourcepath); }

			my @ddffile = ();

			write_ddf_file_header(\@ddffile, $cabinetfile, $installdir);
	
			my $ddfline = "\"" . $sourcepath . "\"" . " " . $uniquename . "\n";
			if ( $doinclude ) { push(@ddffile, $ddfline); }

			my $nextfile = "";
			if ( ${$filesref}[$sequenceorder->{$counter}] ) { $nextfile = ${$filesref}[$sequenceorder->{$counter}]; }

			my $nextcabinetfile = "";
		
			if ( $nextfile->{'cabinet'} ) { $nextcabinetfile = $nextfile->{'cabinet'}; }
		
			while ( $nextcabinetfile eq $cabinetfile )
			{
				$sourcepath =  $nextfile->{'sourcepath'};
				if ( $^O =~ /cygwin/i ) { $sourcepath = $nextfile->{'cyg_sourcepath'}; }
				# to avoid lines with more than 256 characters, it can be useful to use relative pathes
				if ( $allvariables->{'RELATIVE_PATHES_IN_DDF'} ) { $sourcepath = make_relative_ddf_path($sourcepath); }
				$uniquename =  $nextfile->{'uniquename'};
				my $localdoinclude = 1;
				my $nextfilestyles = "";				
				if ( $nextfile->{'Styles'} ) { $nextfilestyles = $nextfile->{'Styles'}; }
				if ( $nextfilestyles =~ /\bDONT_PACK\b/ ) { $localdoinclude = 0; }
				$ddfline = "\"" . $sourcepath . "\"" . " " . $uniquename . "\n";
				if ( $localdoinclude ) { push(@ddffile, $ddfline); }
				$counter++;											# increasing the counter!
				$nextfile = "";
				$nextcabinetfile = "_lastfile_";
				if (( exists($sequenceorder->{$counter}) ) && ( ${$filesref}[$sequenceorder->{$counter}] ))
				{
					$nextfile = ${$filesref}[$sequenceorder->{$counter}];
					$nextcabinetfile = $nextfile->{'cabinet'};
				}
			}
		
			# creating the DDF file

			my $ddffilename = $cabinetfile;
			$ddffilename =~ s/.cab/.ddf/;
			$ddfdir =~ s/\Q$installer::globals::separator\E\s*$//;
			$ddffilename = $ddfdir . $installer::globals::separator . $ddffilename;

			installer::files::save_file($ddffilename ,\@ddffile);
			my $infoline = "Created ddf file: $ddffilename\n"; 
			$installer::logger::Lang->print($infoline);

			# lines in ddf files must not be longer than 256 characters
			check_ddf_file(\@ddffile, $ddffilename);

			# Writing the makecab system call

			my $oneline = "makecab.exe /V3 /F " . $ddffilename . " 2\>\&1 |" . "\n";
		
			push(@cabfilelist, $oneline);

			# collecting all ddf files
			push(@installer::globals::allddffiles, $ddffilename);
		}		
	}
	elsif ( $installer::globals::fix_number_of_cab_files )
	{
		for ( my $i = 0; $i <= $#{$filesref}; $i++ )
		{	
			my $onefile = ${$filesref}[$i];
			my $cabinetfile = $onefile->{'cabinet'};
			my $sourcepath =  $onefile->{'sourcepath'};
			if ( $^O =~ /cygwin/i ) { $sourcepath = $onefile->{'cyg_sourcepath'}; }
			my $uniquename =  $onefile->{'uniquename'};

			my $styles = "";
			my $doinclude = 1;
			if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; };
			if ( $styles =~ /\bDONT_PACK\b/ ) { $doinclude = 0; }
			

			# to avoid lines with more than 256 characters, it can be useful to use relative pathes
			if ( $allvariables->{'RELATIVE_PATHES_IN_DDF'} ) { $sourcepath = make_relative_ddf_path($sourcepath); }

			# all files with the same cabinetfile are directly behind each other in the files collector

			my @ddffile = ();

			write_ddf_file_header(\@ddffile, $cabinetfile, $installdir);
	
			my $ddfline = "\"" . $sourcepath . "\"" . " " . $uniquename . "\n";
			if ( $doinclude ) { push(@ddffile, $ddfline); }

			my $nextfile = ${$filesref}[$i+1];
			my $nextcabinetfile = "";
		
			if ( $nextfile->{'cabinet'} ) { $nextcabinetfile = $nextfile->{'cabinet'}; }
		
			while ( $nextcabinetfile eq $cabinetfile )
			{
				$sourcepath =  $nextfile->{'sourcepath'};
				if ( $^O =~ /cygwin/i ) { $sourcepath = $nextfile->{'cyg_sourcepath'}; }
				# to avoid lines with more than 256 characters, it can be useful to use relative pathes
				if ( $allvariables->{'RELATIVE_PATHES_IN_DDF'} ) { $sourcepath = make_relative_ddf_path($sourcepath); }
				$uniquename =  $nextfile->{'uniquename'};
				my $localdoinclude = 1;
				my $nextfilestyles = "";				
				if ( $nextfile->{'Styles'} ) { $nextfilestyles = $nextfile->{'Styles'}; }
				if ( $nextfilestyles =~ /\bDONT_PACK\b/ ) { $localdoinclude = 0; }
				$ddfline = "\"" . $sourcepath . "\"" . " " . $uniquename . "\n";
				if ( $localdoinclude ) { push(@ddffile, $ddfline); }
				$i++;											# increasing the counter!
				$nextfile = ${$filesref}[$i+1];
				if ( $nextfile ) { $nextcabinetfile = $nextfile->{'cabinet'}; }
				else { $nextcabinetfile = "_lastfile_"; }
			}
		
			# creating the DDF file

			my $ddffilename = $cabinetfile;
			$ddffilename =~ s/.cab/.ddf/;
			$ddfdir =~ s/\Q$installer::globals::separator\E\s*$//;
			$ddffilename = $ddfdir . $installer::globals::separator . $ddffilename;

			installer::files::save_file($ddffilename ,\@ddffile);
			my $infoline = "Created ddf file: $ddffilename\n"; 
			$installer::logger::Lang->print($infoline);

			# lines in ddf files must not be longer than 256 characters
			check_ddf_file(\@ddffile, $ddffilename);

			# Writing the makecab system call

			my $oneline = "makecab.exe /V3 /F " . $ddffilename . " 2\>\&1 |" . "\n";
		
			push(@cabfilelist, $oneline);

			# collecting all ddf files
			push(@installer::globals::allddffiles, $ddffilename);
		}
	}
	elsif ( $installer::globals::one_cab_file )
	{
		my @ddffile = ();
		
		my $cabinetfile = "";

		for ( my $i = 0; $i <= $#{$filesref}; $i++ )
		{
			my $onefile = ${$filesref}[$i];
			$cabinetfile = $onefile->{'cabinet'};
			my $sourcepath =  $onefile->{'sourcepath'};
			if ( $^O =~ /cygwin/i ) { $sourcepath = $onefile->{'cyg_sourcepath'}; }
			my $uniquename =  $onefile->{'uniquename'};

			# to avoid lines with more than 256 characters, it can be useful to use relative pathes
			if ( $allvariables->{'RELATIVE_PATHES_IN_DDF'} ) { $sourcepath = make_relative_ddf_path($sourcepath); }

			if ( $i == 0 ) { write_ddf_file_header(\@ddffile, $cabinetfile, $installdir); }

			my $styles = "";
			my $doinclude = 1;
			if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; };
			if ( $styles =~ /\bDONT_PACK\b/ ) { $doinclude = 0; }

			my $ddfline = "\"" . $sourcepath . "\"" . " " . $uniquename . "\n";
			if ( $doinclude ) { push(@ddffile, $ddfline); }
		}
		
		# creating the DDF file

		my $ddffilename = $cabinetfile;
		$ddffilename =~ s/.cab/.ddf/;
		$ddfdir =~ s/[\/\\]\s*$//;
		$ddffilename = $ddfdir . $installer::globals::separator . $ddffilename;

		installer::files::save_file($ddffilename ,\@ddffile);
		my $infoline = "Created ddf file: $ddffilename\n"; 
		$installer::logger::Lang->print($infoline);

		# lines in ddf files must not be longer than 256 characters
		check_ddf_file(\@ddffile, $ddffilename);

		# Writing the makecab system call

		my $oneline = "makecab.exe /F " . $ddffilename . "\n";
		
		push(@cabfilelist, $oneline);

		# collecting all ddf files
		push(@installer::globals::allddffiles, $ddffilename);
	}
	else
	{
		installer::exiter::exit_program("ERROR: No cab file specification in globals.pm !", "create_media_table");		
	}

	$installer::logger::Lang->add_timestamp("Performance Info: ddf file generation end");
		
	return \@cabfilelist;	# contains all system calls for packaging process
}

########################################################################
# Returning the file sequence of a specified file.
########################################################################

sub get_file_sequence
{
	my ($filesref, $uniquefilename) = @_;

	my $sequence = "";
	my $found_sequence = 0;

	for ( my $i = 0; $i <= $#{$filesref}; $i++ )
	{
		my $onefile = ${$filesref}[$i];
		my $uniquename = $onefile->{'uniquename'};
		
		if ( $uniquename eq $uniquefilename )
		{
			$sequence = $onefile->{'sequencenumber'};			
			$found_sequence = 1;
			last;
		}
	}
	
	if ( ! $found_sequence ) { installer::exiter::exit_program("ERROR: No sequence found for $uniquefilename !", "get_file_sequence"); }
	
	return $sequence;
}

########################################################################
# For update and patch reasons the pack order needs to be saved.
# The pack order is saved in the ddf files; the names and locations
# of the ddf files are saved in @installer::globals::allddffiles.
# The outputfile "packorder.txt" can be saved in
# $installer::globals::infodirectory . 
########################################################################

sub save_packorder
{
	installer::logger::include_header_into_logfile("Saving pack order");

	$installer::logger::Lang->add_timestamp("Performance Info: saving pack order start");

	my $packorderfilename = "packorder.txt";
	$packorderfilename = $installer::globals::infodirectory . $installer::globals::separator . $packorderfilename;

	my @packorder = ();
	
	my $headerline = "\# Syntax\: Filetable_Sequence Cabinetfilename Physical_FileName Unique_FileName\n\n"; 
	push(@packorder, $headerline);
	
	for ( my $i = 0; $i <= $#installer::globals::allddffiles; $i++ )
	{
		my $ddffilename = $installer::globals::allddffiles[$i];
		my $ddffile = installer::files::read_file($ddffilename);
		my $cabinetfile = "";

		for ( my $j = 0; $j <= $#{$ddffile}; $j++ )
		{
			my $oneline = ${$ddffile}[$j];
			
			# Getting the Cabinet file name
			
			if ( $oneline =~ /^\s*\.Set\s+CabinetName.*\=(.*?)\s*$/ ) { $cabinetfile = $1; }
			if ( $oneline =~ /^\s*\.Set\s+/ ) { next; } 
			
			if ( $oneline =~ /^\s*\"(.*?)\"\s+(.*?)\s*$/ )
			{
				my $sourcefile = $1;
				my $uniquefilename = $2;
				
				installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$sourcefile);
				
				# Using the hash created in create_files_table for performance reasons to get the sequence number
				my $filesequence = "";
				if ( exists($installer::globals::uniquefilenamesequence{$uniquefilename}) ) { $filesequence = $installer::globals::uniquefilenamesequence{$uniquefilename}; }
				else { installer::exiter::exit_program("ERROR: No sequence number value for $uniquefilename !", "save_packorder"); }
				
				my $line = $filesequence . "\t" . $cabinetfile . "\t" . $sourcefile . "\t" . $uniquefilename . "\n";
				push(@packorder, $line);
			}
		}
	}
	
	installer::files::save_file($packorderfilename ,\@packorder);

	$installer::logger::Lang->add_timestamp("Performance Info: saving pack order end");
}

#################################################################
# Returning the name of the msi database
#################################################################

sub get_msidatabasename
{
	my ($allvariableshashref, $language) = @_;
	
	my $databasename = $allvariableshashref->{'PRODUCTNAME'} . $allvariableshashref->{'PRODUCTVERSION'};
	$databasename = lc($databasename);
	$databasename =~ s/\.//g;
	$databasename =~ s/\-//g;
	$databasename =~ s/\s//g;

	# possibility to overwrite the name with variable DATABASENAME
	if ( $allvariableshashref->{'DATABASENAME'} )
	{
		$databasename = $allvariableshashref->{'DATABASENAME'};
	}
	
	if ( $language )
	{
		if (!($language eq ""))
		{
			$databasename .= "_$language";
		}
	}
	
	$databasename .= ".msi";
	
	return $databasename;
}

#################################################################
# Creating the msi database
# This works only on Windows
#################################################################

sub create_msi_database
{
	my ($idtdirbase ,$msifilename) = @_;			

	# -f : path containing the idt files
	# -d : msi database, including path
	# -c : create database
	# -i : include the following tables ("*" includes all available tables)

	my $msidb = "msidb.exe";	# Has to be in the path
	my $extraslash = "";		# Has to be set for non-ActiveState perl

	installer::logger::include_header_into_logfile("Creating msi database");

	$idtdirbase = installer::converter::make_path_conform($idtdirbase);

	$msifilename = installer::converter::make_path_conform($msifilename);

	if ( $^O =~ /cygwin/i ) {
		# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
		$idtdirbase =~ s/\//\\\\/g;
		$msifilename =~ s/\//\\\\/g;
		$extraslash = "\\";
	}
	my $systemcall = $msidb . " -f " . $idtdirbase . " -d " . $msifilename . " -c " . "-i " . $extraslash . "*";

	my $returnvalue = system($systemcall);

	my $infoline = "Systemcall: $systemcall\n";
	$installer::logger::Lang->print($infoline);
		
	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $msidb!\n";
		$installer::logger::Lang->print($infoline);
	}
	else
	{
		$infoline = "Success: Executed $msidb successfully!\n";
		$installer::logger::Lang->print($infoline);
	}
}

#####################################################################
# Returning the value from sis.mlf for Summary Information Stream
#####################################################################

sub get_value_from_sis_lng
{
	my ($language, $languagefile, $searchstring) = @_;
	
	my $language_block = installer::windows::idtglobal::get_language_block_from_language_file($searchstring, $languagefile);
	my $newstring = installer::windows::idtglobal::get_language_string_from_language_block($language_block, $language, $searchstring);
	$newstring = "\"" . $newstring . "\"";
	
	return $newstring;
}

#################################################################
# Returning the msi version for the Summary Information Stream
#################################################################

sub get_msiversion_for_sis
{
	my $msiversion = "200";
	return $msiversion;	
}

#################################################################
# Returning the word count for the Summary Information Stream
#################################################################

sub get_wordcount_for_sis
{
	my $wordcount = "0";
	return $wordcount;	
}

#################################################################
# Returning the codepage for the Summary Information Stream
#################################################################

sub get_codepage_for_sis
{
	my ( $language ) = @_;

	my $codepage = installer::windows::language::get_windows_encoding($language);

	# Codepage 65001 does not work in Summary Information Stream
	if ( $codepage == 65001 ) { $codepage = 0; }

	# my $codepage = "1252";	# determine dynamically in a function
	# my $codepage = "65001";		# UTF-8
	return $codepage;	
}

#################################################################
# Returning the template for the Summary Information Stream
#################################################################

sub get_template_for_sis
{
	my ( $language, $allvariables ) = @_;
	
	my $windowslanguage = installer::windows::language::get_windows_language($language); 
	
	my $architecture = "Intel";

	# Adding 256, if this is a 64 bit installation set.
	if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) { $architecture = "x64"; }

	my $value = "\"" . $architecture . ";" . $windowslanguage;	# adding the Windows language
	
	$value = $value . "\"";						# adding ending '"'
	
	return $value ;	
}

#################################################################
# Returning the PackageCode for the Summary Information Stream
#################################################################

sub get_packagecode_for_sis
{
	# always generating a new package code for each package

	my $guidref = get_guid_list(1, 1);	# only one GUID shall be generated

	${$guidref}[0] =~ s/\s*$//;		# removing ending spaces

	my $guid = "\{" . ${$guidref}[0] . "\}";

	my $infoline = "PackageCode: $guid\n";
	$installer::logger::Lang->print($infoline);

	return $guid;	
}

#################################################################
# Returning the title for the Summary Information Stream
#################################################################

sub get_title_for_sis
{
	my ( $language, $languagefile, $searchstring ) = @_;

	my $title = get_value_from_sis_lng($language, $languagefile, $searchstring );

	return $title;
}

#################################################################
# Returning the author for the Summary Information Stream
#################################################################

sub get_author_for_sis
{
	my $author = $installer::globals::longmanufacturer;

	$author = "\"" . $author . "\"";

	return $author;
}

#################################################################
# Returning the subject for the Summary Information Stream
#################################################################

sub get_subject_for_sis
{
	my ( $allvariableshashref ) = @_;
	
	my $subject = $allvariableshashref->{'PRODUCTNAME'} . " " . $allvariableshashref->{'PRODUCTVERSION'};

	$subject = "\"" . $subject . "\"";

	return $subject;
}

#################################################################
# Returning the comment for the Summary Information Stream
#################################################################

sub get_comment_for_sis
{
	my ( $language, $languagefile, $searchstring ) = @_;

	my $comment = get_value_from_sis_lng($language, $languagefile, $searchstring );

	return $comment;
}

#################################################################
# Returning the keywords for the Summary Information Stream
#################################################################

sub get_keywords_for_sis
{
	my ( $language, $languagefile, $searchstring ) = @_;

	my $keywords = get_value_from_sis_lng($language, $languagefile, $searchstring );

	return $keywords;
}

######################################################################
# Returning the application name for the Summary Information Stream
######################################################################

sub get_appname_for_sis
{
	my ( $language, $languagefile, $searchstring ) = @_;

	my $appname = get_value_from_sis_lng($language, $languagefile, $searchstring );

	return $appname;
}

######################################################################
# Returning the security for the Summary Information Stream
######################################################################

sub get_security_for_sis
{
	my $security = "0";
	return $security;
}

#################################################################
# Writing the Summary information stream into the msi database
# This works only on Windows
#################################################################

sub write_summary_into_msi_database
{
	my ($msifilename, $language, $languagefile, $allvariableshashref) = @_;

	# -g : requrired msi version
	# -c : codepage
	# -p : template

	installer::logger::include_header_into_logfile("Writing summary information stream");

	my $msiinfo = "msiinfo.exe";	# Has to be in the path
	
	my $sislanguage = "en-US";	# title, comment, keyword and appname alway in english
		
	my $msiversion = get_msiversion_for_sis();
	my $codepage = get_codepage_for_sis($language);
	my $template = get_template_for_sis($language, $allvariableshashref);
	my $guid = get_packagecode_for_sis();
	my $title = get_title_for_sis($sislanguage,$languagefile, "OOO_SIS_TITLE");
	my $author = get_author_for_sis();
	my $subject = get_subject_for_sis($allvariableshashref);
	my $comment = get_comment_for_sis($sislanguage,$languagefile, "OOO_SIS_COMMENT");
	my $keywords = get_keywords_for_sis($sislanguage,$languagefile, "OOO_SIS_KEYWORDS");
	my $appname = get_appname_for_sis($sislanguage,$languagefile, "OOO_SIS_APPNAME");
	my $security = get_security_for_sis();
	my $wordcount = get_wordcount_for_sis();
	
	$msifilename = installer::converter::make_path_conform($msifilename);
	
	my $systemcall = $msiinfo . " " . $msifilename . " -g " . $msiversion . " -c " . $codepage 
					. " -p " . $template . " -v " . $guid . " -t " . $title . " -a " . $author
					. " -j " . $subject . " -o " . $comment . " -k " . $keywords . " -n " . $appname
					. " -u " . $security . " -w " . $wordcount;

	my $returnvalue = system($systemcall);

	my $infoline = "Systemcall: $systemcall\n";
	$installer::logger::Lang->print($infoline);
		
	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $msiinfo!\n";
		$installer::logger::Lang->print($infoline);
	}
	else
	{
		$infoline = "Success: Executed $msiinfo successfully!\n";
		$installer::logger::Lang->print($infoline);
	}	
}

#########################################################################
# For more than one language in the installation set:
# Use one database and create Transformations for all other languages
#########################################################################

sub create_transforms
{
	my ($languagesarray, $defaultlanguage, $installdir, $allvariableshashref) = @_;	

	installer::logger::include_header_into_logfile("Creating Transforms");

	my $msitran = "msitran.exe";	# Has to be in the path
	
	$installdir = installer::converter::make_path_conform($installdir);
	
	# Syntax for creating a transformation
	# msitran.exe -g <baseDB> <referenceDB> <transformfile> [<errorhandling>}
	
	my $basedbname = get_msidatabasename($allvariableshashref, $defaultlanguage);	
	$basedbname = $installdir . $installer::globals::separator . $basedbname;

	my $errorhandling = "f";	# Suppress "change codepage" error

	# Iterating over all files
	
	foreach ( @{$languagesarray} )
	{
		my $onelanguage = $_;
	
		if ( $onelanguage eq $defaultlanguage ) { next; }
	
		my $referencedbname = get_msidatabasename($allvariableshashref, $onelanguage);	
		$referencedbname = $installdir . $installer::globals::separator . $referencedbname;
		
		my $transformfile = $installdir . $installer::globals::separator . "trans_" . $onelanguage . ".mst";

		my $systemcall = $msitran . " " . " -g " . $basedbname . " " . $referencedbname . " " . $transformfile . " " . $errorhandling; 

		my $returnvalue = system($systemcall);

		my $infoline = "Systemcall: $systemcall\n";
		$installer::logger::Lang->print($infoline);
		
		# Problem: msitran.exe in version 4.0 always returns "1", even if no failure occured.
		# Therefore it has to be checked, if this is version 4.0. If yes, if the mst file
		# exists and if it is larger than 0 bytes. If this is true, then no error occured.
		# File Version of msitran.exe: 4.0.6000.16384 has checksum: "b66190a70145a57773ec769e16777b29".
		# Same for msitran.exe from wntmsci12: "aa25d3445b94ffde8ef0c1efb77a56b8"
		
		if ($returnvalue)
		{
			$infoline = "WARNING: Returnvalue of $msitran is not 0. Checking version of $msitran!\n";
			$installer::logger::Lang->print($infoline);

			open(FILE, "<$installer::globals::msitranpath") or die "ERROR: Can't open $installer::globals::msitranpath for creating file hash";
			binmode(FILE);
			my $digest = Digest::MD5->new->addfile(*FILE)->hexdigest;			
			close(FILE);

			my @problemchecksums = ("b66190a70145a57773ec769e16777b29", "aa25d3445b94ffde8ef0c1efb77a56b8");
			my $isproblemchecksum = 0;
			
			foreach my $problemchecksum ( @problemchecksums )
			{
				$infoline = "Checksum of problematic MsiTran.exe: $problemchecksum\n";
				$installer::logger::Lang->print($infoline);
				$infoline = "Checksum of used MsiTran.exe: $digest\n";
				$installer::logger::Lang->print($infoline);
				if ( $digest eq $problemchecksum ) { $isproblemchecksum = 1; }
			}

			if ( $isproblemchecksum )
			{
				# Check existence of mst
				if ( -f $transformfile )
				{
					$infoline = "File $transformfile exists.\n";
					$installer::logger::Lang->print($infoline);
					my $filesize = ( -s $transformfile );
					$infoline = "Size of $transformfile: $filesize\n";
					$installer::logger::Lang->print($infoline);

					if ( $filesize > 0 )
					{
						$infoline = "Info: Returnvalue $returnvalue of $msitran is no problem :-) .\n";
						$installer::logger::Lang->print($infoline);
						$returnvalue = 0; # reset the error	
					}
					else
					{
						$infoline = "Filesize indicates that an error occured.\n";
						$installer::logger::Lang->print($infoline);
					}
				}
				else
				{
					$infoline = "File $transformfile does not exist -> An error occured.\n";
					$installer::logger::Lang->print($infoline);					
				}
			}
			else
			{
				$infoline = "This is not a problematic version of msitran.exe. Therefore the error is not caused by problematic msitran.exe.\n";
				$installer::logger::Lang->print($infoline);				
			}
		}		
		
		if ($returnvalue)
		{
			$infoline = "ERROR: Could not execute $msitran!\n";
			$installer::logger::Lang->print($infoline);
		}
		else	
		{
			$infoline = "Success: Executed $msitran successfully!\n";
			$installer::logger::Lang->print($infoline);
		}	

		# The reference database can be deleted
		
		my $result = unlink($referencedbname);		
		# $result contains the number of deleted files
		
		if ( $result == 0 )
		{
			$infoline = "ERROR: Could not remove file $$referencedbname !\n";
			$installer::logger::Lang->print($infoline);
			installer::exiter::exit_program($infoline, "create_transforms");
		}
	}
}

#########################################################################
# The default language msi database does not need to contain
# the language in the database name. Therefore the file
# is renamed. Example: "openofficeorg20_01.msi" to "openofficeorg20.msi"
#########################################################################

sub rename_msi_database_in_installset
{
	my ($defaultlanguage, $installdir, $allvariableshashref) = @_;	

	installer::logger::include_header_into_logfile("Renaming msi database");

	my $olddatabasename = get_msidatabasename($allvariableshashref, $defaultlanguage);	
	$olddatabasename = $installdir . $installer::globals::separator . $olddatabasename;

	my $newdatabasename = get_msidatabasename($allvariableshashref);	

	$installer::globals::shortmsidatabasename = $newdatabasename;

	$newdatabasename = $installdir . $installer::globals::separator . $newdatabasename;

	installer::systemactions::rename_one_file($olddatabasename, $newdatabasename);
	
	$installer::globals::msidatabasename = $newdatabasename;
}

#########################################################################
# Adding the language to the name of the msi databasename,
# if this is required (ADDLANGUAGEINDATABASENAME)
#########################################################################

sub add_language_to_msi_database
{
	my ($defaultlanguage, $installdir, $allvariables) = @_;

	my $languagestring = $defaultlanguage;
	if ( $allvariables->{'USELANGUAGECODE'} ) { $languagestring = installer::windows::language::get_windows_language($defaultlanguage); } 
	my $newdatabasename = $installer::globals::shortmsidatabasename;
	$newdatabasename =~ s/\.msi\s*$/_$languagestring\.msi/;
	$installer::globals::shortmsidatabasename = $newdatabasename;
	$newdatabasename = $installdir . $installer::globals::separator . $newdatabasename;

	my $olddatabasename = $installer::globals::msidatabasename;
	
	installer::systemactions::rename_one_file($olddatabasename, $newdatabasename);
	
	$installer::globals::msidatabasename = $newdatabasename;	
}

##########################################################################
# Writing the databasename into the setup.ini.
##########################################################################

sub put_databasename_into_setupini
{
	my ($setupinifile, $allvariableshashref) = @_;
	
	my $databasename = get_msidatabasename($allvariableshashref);
	my $line = "database=" . $databasename . "\n";
	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the required msi version into setup.ini
##########################################################################

sub put_msiversion_into_setupini
{
	my ($setupinifile) = @_;
	
	my $msiversion = "2.0";	
	my $line = "msiversion=" . $msiversion . "\n";
	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the productname into setup.ini
##########################################################################

sub put_productname_into_setupini
{
	my ($setupinifile, $allvariableshashref) = @_;
	
	my $productname = $allvariableshashref->{'PRODUCTNAME'};	
	my $line = "productname=" . $productname . "\n";
	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the productcode into setup.ini
##########################################################################

sub put_productcode_into_setupini
{
	my ($setupinifile) = @_;
	
	my $productcode = $installer::globals::productcode;	
	my $line = "productcode=" . $productcode . "\n";
	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the ProductVersion from Property table into setup.ini
##########################################################################

sub put_productversion_into_setupini
{
	my ($setupinifile) = @_;
	
	my $line = "productversion=" . $installer::globals::msiproductversion . "\n";	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the key for Minor Upgrades into setup.ini
##########################################################################

sub put_upgradekey_into_setupini
{
	my ($setupinifile) = @_;
	
	if ( $installer::globals::minorupgradekey ne "" )
	{
		my $line = "upgradekey=" . $installer::globals::minorupgradekey . "\n";	
		push(@{$setupinifile}, $line);
	}
}

##########################################################################
# Writing the number of languages into setup.ini
##########################################################################

sub put_languagecount_into_setupini
{
	my ($setupinifile, $languagesarray) = @_;
	
	my $languagecount = $#{$languagesarray} + 1;
	my $line = "count=" . $languagecount . "\n";
	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the defaultlanguage into setup.ini
##########################################################################

sub put_defaultlanguage_into_setupini
{
	my ($setupinifile, $defaultlanguage) = @_;
	
	my $windowslanguage = installer::windows::language::get_windows_language($defaultlanguage);
	my $line = "default=" . $windowslanguage . "\n";	
	push(@{$setupinifile}, $line);
}

##########################################################################
# Writing the information about transformations into setup.ini
##########################################################################

sub put_transforms_into_setupini
{
	my ($setupinifile, $onelanguage, $counter) = @_;
		
	my $windowslanguage = installer::windows::language::get_windows_language($onelanguage);
	my $transformfilename = "trans_" . $onelanguage . ".mst";
	
	my $line = "lang" . $counter . "=" . $windowslanguage . "," . $transformfilename . "\n";
		
	push(@{$setupinifile}, $line);
}

###################################################
# Including Windows line ends in ini files
# Profiles on Windows shall have \r\n line ends
###################################################

sub include_windows_lineends
{
	my ($onefile) = @_;

	for ( my $i = 0; $i <= $#{$onefile}; $i++ )
	{
		${$onefile}[$i] =~ s/\r?\n$/\r\n/;
	}
}

##########################################################################
# Generation the file setup.ini, that is used by the loader setup.exe.
##########################################################################

sub create_setup_ini
{
	my ($languagesarray, $defaultlanguage, $installdir, $allvariableshashref) = @_;	

	installer::logger::include_header_into_logfile("Creating setup.ini");

	my $setupinifilename = $installdir . $installer::globals::separator . "setup.ini";
	
	my @setupinifile = ();
	my $setupinifile = \@setupinifile;
	
	my $line = "\[setup\]\n";
	push(@setupinifile, $line);

	put_databasename_into_setupini($setupinifile, $allvariableshashref);
	put_msiversion_into_setupini($setupinifile);
	put_productname_into_setupini($setupinifile, $allvariableshashref);
	put_productcode_into_setupini($setupinifile);
	put_productversion_into_setupini($setupinifile);
	put_upgradekey_into_setupini($setupinifile);
	
	$line = "\[languages\]\n";
	push(@setupinifile, $line);

	put_languagecount_into_setupini($setupinifile, $languagesarray);
	put_defaultlanguage_into_setupini($setupinifile, $defaultlanguage);

	if ( $#{$languagesarray} > 0 )	# writing the transforms information
	{
		my $counter = 1;

		for ( my $i = 0; $i <= $#{$languagesarray}; $i++ )
		{
			if ( ${$languagesarray}[$i] eq $defaultlanguage ) { next; }

			put_transforms_into_setupini($setupinifile, ${$languagesarray}[$i], $counter);
			$counter++;
		}	
	}

	if ( $installer::globals::iswin && $installer::globals::plat =~ /cygwin/i)		# Windows line ends only for Cygwin
	{
		include_windows_lineends($setupinifile);
	}

	installer::files::save_file($setupinifilename, $setupinifile);	

	$infoline = "Generated file $setupinifilename !\n";
	$installer::logger::Lang->print($infoline);
}

#################################################################
# Copying the files defined as ScpActions into the 
# installation set.
#################################################################

sub copy_scpactions_into_installset
{
	my ($defaultlanguage, $installdir, $allscpactions) = @_;	

	installer::logger::include_header_into_logfile("Copying ScpAction files into installation set");

	for ( my $i = 0; $i <= $#{$allscpactions}; $i++ )
	{
		my $onescpaction = ${$allscpactions}[$i];
		
		if ( $onescpaction->{'Name'} eq "loader.exe" ) { next; }	# do not copy this ScpAction loader
		
		# only copying language independent files or files with the correct language (the defaultlanguage)
		
		my $filelanguage = $onescpaction->{'specificlanguage'};
		
		if ( ($filelanguage eq $defaultlanguage) || ($filelanguage eq "") )
		{ 
			my $sourcefile = $onescpaction->{'sourcepath'};
			my $destfile = $installdir . $installer::globals::separator . $onescpaction->{'DestinationName'};

			installer::systemactions::copy_one_file($sourcefile, $destfile);
		}	
	}
}

#################################################################
# Copying the files for the Windows installer into the 
# installation set (setup.exe).
#################################################################

sub copy_windows_installer_files_into_installset
{
	my ($installdir, $includepatharrayref, $allvariables) = @_;

    installer::logger::include_header_into_logfile("Copying Windows installer files into installation set");
	
	@copyfile = ();
	push(@copyfile, "loader2.exe");
	
	if ( $allvariables->{'NOLOADERREQUIRED'} ) { @copyfile = (); }
	
	for ( my $i = 0; $i <= $#copyfile; $i++ )
	{
		my $filename = $copyfile[$i];
		my $sourcefileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$filename, $includepatharrayref, 1);
	
		if ( ! -f $$sourcefileref ) { installer::exiter::exit_program("ERROR: msi file not found: $$sourcefileref !", "copy_windows_installer_files_into_installset"); }

		my $destfile;
		if ( $copyfile[$i] eq "loader2.exe" ) { $destfile = "setup.exe"; }	# renaming the loader
		else { $destfile = $copyfile[$i]; }

		$destfile = $installdir . $installer::globals::separator . $destfile;

		installer::systemactions::copy_one_file($$sourcefileref, $destfile);		
	}
}

#################################################################
# Copying MergeModules for the Windows installer into the 
# installation set. The list of MergeModules is located
# in %installer::globals::copy_msm_files
#################################################################

sub copy_merge_modules_into_installset
{
	my ($installdir) = @_;

	installer::logger::include_header_into_logfile("Copying Merge files into installation set");
	
	my $cabfile;
	foreach $cabfile ( keys  %installer::globals::copy_msm_files )
	{
		my $sourcefile  = $installer::globals::copy_msm_files{$cabfile};
		my $destfile = $installdir . $installer::globals::separator . $cabfile;

		installer::systemactions::copy_one_file($sourcefile, $destfile);
	}
}

#################################################################
# Copying the child projects into the 
# installation set
#################################################################

sub copy_child_projects_into_installset
{
	my ($installdir, $allvariables) = @_;

	my $sourcefile = "";
	my $destdir = "";

	# adding Java

	if ( $allvariables->{'JAVAPRODUCT'} )
	{
		$sourcefile = $installer::globals::javafile->{'sourcepath'};
		$destdir = $installdir . $installer::globals::separator . $installer::globals::javafile->{'Subdir'};
		if ( ! -d $destdir) { installer::systemactions::create_directory($destdir); }
		installer::systemactions::copy_one_file($sourcefile, $destdir);
	}

	if ( $allvariables->{'UREPRODUCT'} )
	{
		$sourcefile = $installer::globals::urefile->{'sourcepath'};
		$destdir = $installdir . $installer::globals::separator . $installer::globals::urefile->{'Subdir'};
		if ( ! -d $destdir) { installer::systemactions::create_directory($destdir); }
		installer::systemactions::copy_one_file($sourcefile, $destdir);
	}
}

#################################################################
# Getting a list of GUID using uuidgen.exe.
# This works only on Windows
#################################################################

sub get_guid_list
{
	my ($number, $log) = @_;

	if ( $log ) { installer::logger::include_header_into_logfile("Generating $number GUID"); }
	
	my $uuidgen = "uuidgen.exe";		# Has to be in the path
	
	# "-c" for uppercase output
	
	# my $systemcall = "$uuidgen -n$number -c |";
	my $systemcall = "$uuidgen -n$number |";
	open (UUIDGEN, "$systemcall" ) or die("uuidgen is missing.");
	my @uuidlist = <UUIDGEN>;
	close (UUIDGEN);

	my $infoline = "Systemcall: $systemcall\n";
	if ( $log ) { $installer::logger::Lang->print($infoline); }
	
	my $comparenumber = $#uuidlist + 1;	
	
	if ( $comparenumber == $number )
	{
		$infoline = "Success: Executed $uuidgen successfully!\n";
		if ( $log ) { $installer::logger::Lang->print($infoline); }
	}
	else
	{
		$infoline = "ERROR: Could not execute $uuidgen successfully!\n";
		if ( $log ) { $installer::logger::Lang->print($infoline); }
	}	

	# uppercase, no longer "-c", because this is only supported in uuidgen.exe v.1.01
	for ( my $i = 0; $i <= $#uuidlist; $i++ ) { $uuidlist[$i] = uc($uuidlist[$i]); }

	return \@uuidlist;
}

#################################################################
# Calculating a GUID with a string using md5.
#################################################################

sub calculate_guid
{
	my ( $string ) = @_;
	
	my $guid = "";
	
    my $md5 = Digest::MD5->new;
    $md5->add($string);
    my $digest = $md5->hexdigest;
    $digest = uc($digest);

	# my $id = pack("A32", $digest);
	my ($first, $second, $third, $fourth, $fifth) = unpack ('A8 A4 A4 A4 A12', $digest);
	$guid = "$first-$second-$third-$fourth-$fifth";
	
	return $guid;
}

#################################################################
# Calculating a ID with a string using md5 (very fast).
#################################################################

sub calculate_id
{
	my ( $string, $length ) = @_;
	
	my $id = "";
	
    my $md5 = Digest::MD5->new;
    $md5->add($string);
    my $digest = lc($md5->hexdigest);
	$id = substr($digest, 0, $length);
	
	return $id;
}

#################################################################
# Filling the component hash with the values of the 
# component file.
#################################################################

sub fill_component_hash
{
	my ($componentfile) = @_;

	my %components = ();

	for ( my $i = 0; $i <= $#{$componentfile}; $i++ )
	{
		my $line = ${$componentfile}[$i];
		
		if ( $line =~ /^\s*(.*?)\t(.*?)\s*$/ )
		{
			my $key = $1;
			my $value = $2;
		
			$components{$key} = $value;
		}
	}
	
	return \%components;
}

#################################################################
# Creating a new component file, if new guids were generated.
#################################################################

sub create_new_component_file
{
	my ($componenthash) = @_;

	my @componentfile = ();
	
	my $key;
	
	foreach $key (keys %{$componenthash})
	{
		my $value = $componenthash->{$key};
		my $input = "$key\t$value\n";
		push(@componentfile ,$input);
	}

	return \@componentfile;
}

#################################################################
# Filling real component GUID into the component table.
# This works only on Windows
#################################################################

sub set_uuid_into_component_table
{
	my ($idtdirbase, $allvariables) = @_;
	
	my $componenttablename  = $idtdirbase . $installer::globals::separator . "Componen.idt";
	
	my $componenttable = installer::files::read_file($componenttablename);
	
	# For update and patch reasons (small update) the GUID of an existing component must not change!
	# The collection of component GUIDs is saved in the directory $installer::globals::idttemplatepath in the file "components.txt"
	
	my $infoline = "";
	my $counter = 0;
	# my $componentfile = installer::files::read_file($installer::globals::componentfilename);
	# my $componenthash = fill_component_hash($componentfile);
	
	for ( my $i = 3; $i <= $#{$componenttable}; $i++ )	# ignoring the first three lines
	{
		my $oneline = ${$componenttable}[$i];
		my $componentname = "";
		if ( $oneline =~ /^\s*(\S+?)\t/ ) { $componentname = $1; }

		my $uuid = "";

	#	if ( $componenthash->{$componentname} )
	#	{
	#		$uuid = $componenthash->{$componentname};
	#	}
	#	else
	#	{

			if ( exists($installer::globals::calculated_component_guids{$componentname}))
			{
				$uuid = $installer::globals::calculated_component_guids{$componentname};				
			}
			else
			{
				# Calculating new GUID with the help of the component name.
				my $useooobaseversion = 1;
				if ( exists($installer::globals::base_independent_components{$componentname})) { $useooobaseversion = 0; }
				my $sourcestring = $componentname;

				if ( $useooobaseversion )
				{
					if ( ! exists($allvariables->{'OOOBASEVERSION'}) ) { installer::exiter::exit_program("ERROR: Could not find variable \"OOOBASEVERSION\" (required value for GUID creation)!", "set_uuid_into_component_table"); }
					$sourcestring = $sourcestring . "_" . $allvariables->{'OOOBASEVERSION'};
				}
				$uuid = calculate_guid($sourcestring);
				$counter++;

				# checking, if there is a conflict with an already created guid
				if ( exists($installer::globals::allcalculated_guids{$uuid}) ) { installer::exiter::exit_program("ERROR: \"$uuid\" was already created before!", "set_uuid_into_component_table"); }
				$installer::globals::allcalculated_guids{$uuid} = 1;
				$installer::globals::calculated_component_guids{$componentname} = $uuid;

				# Setting new uuid
				# $componenthash->{$componentname} = $uuid;

				# Setting flag			
				# $installer::globals::created_new_component_guid = 1;	# this is very important!
			}
	#	}

		${$componenttable}[$i] =~ s/COMPONENTGUID/$uuid/;
	}
	
	installer::files::save_file($componenttablename, $componenttable);
	
#	if ( $installer::globals::created_new_component_guid )
#	{
#		# create new component file!
#		$componentfile = create_new_component_file($componenthash);
#		installer::worker::sort_array($componentfile);
#
#		# To avoid conflict the components file cannot be saved at the same place
#		# All important data have to be saved in the directory: $installer::globals::infodirectory
#		my $localcomponentfilename = $installer::globals::componentfilename;
#		installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$localcomponentfilename);
#		$localcomponentfilename = $installer::globals::infodirectory . $installer::globals::separator . $localcomponentfilename;		
#		installer::files::save_file($localcomponentfilename, $componentfile);
#
#		# installer::files::save_file($installer::globals::componentfilename, $componentfile);	# version using new file in solver
#
#		$infoline = "COMPONENTCODES: Created $counter new GUIDs for components ! \n";
#		$installer::logger::Lang->print($infoline);
#	}
#	else
#	{
#		$infoline = "SUCCESS COMPONENTCODES: All component codes exist! \n";
#		$installer::logger::Lang->print($infoline);
#	}

}

#########################################################################
# Adding final 64 properties into msi database, if required.
# RegLocator : +16 in type column to search in 64 bit registry.
# All conditions: "VersionNT" -> "VersionNT64" (several tables).
# Already done: "+256" in Attributes column of table "Component".
# Still following: Setting "x64" instead of "Intel" in Summary 
# Information Stream of msi database in "get_template_for_sis". 
#########################################################################

sub prepare_64bit_database
{
	my ($basedir, $allvariables) = @_;
	
	my $infoline = "";

	if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 ))
	{ 
		# 1. Beginning with table "RegLocat.idt". Adding "16" to the type.

		my $reglocatfile = "";
		my $reglocatfilename = $basedir . $installer::globals::separator . "RegLocat.idt";
	
		if ( -f $reglocatfilename )
		{
			my $saving_required = 0;
			$reglocatfile = installer::files::read_file($reglocatfilename);

			for ( my $i = 3; $i <= $#{$reglocatfile}; $i++ ) 	# ignoring the first three lines
			{		
				my $oneline = ${$reglocatfile}[$i];

				if ( $oneline =~ /^\s*\#/ ) { next; }	# this is a comment line
				if ( $oneline =~ /^\s*$/ ) { next; }
				
				if ( $oneline =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(\d+)\s*$/ )
				{
					# Syntax: Signature_ Root Key Name Type
					my $sig = $1;
					my $root = $2;
					my $key = $3;
					my $name = $4;
					my $type = $5;
					
					$type = $type + 16;
					
					my $newline = $sig . "\t" . $root . "\t" . $key . "\t" . $name . "\t" . $type . "\n";
					${$reglocatfile}[$i] = $newline;
					
					$saving_required = 1;
				}
			}
			
			if ( $saving_required )
			{
				# Saving the files
				installer::files::save_file($reglocatfilename ,$reglocatfile);
				$infoline = "Making idt file 64 bit conform: $reglocatfilename\n"; 
				$installer::logger::Lang->print($infoline);
			}
		}
		
		# 2. Replacing all occurences of "VersionNT" by "VersionNT64"
		
		my @versionnt_files = ("Componen.idt", "InstallE.idt", "InstallU.idt", "LaunchCo.idt");
		
		foreach my $onefile ( @versionnt_files )
		{
			my $fullfilename = $basedir . $installer::globals::separator . $onefile;
			
			if ( -f $fullfilename )
			{
				my $saving_required = 0;
				$filecontent = installer::files::read_file($fullfilename);		

				for ( my $i = 3; $i <= $#{$filecontent}; $i++ ) 	# ignoring the first three lines
				{
					my $oneline = ${$filecontent}[$i];
					
					if ( $oneline =~ /\bVersionNT\b/ )
					{
						${$filecontent}[$i] =~ s/\bVersionNT\b/VersionNT64/g;
						$saving_required = 1;
					}			
				}
			
				if ( $saving_required )
				{
					# Saving the files
					installer::files::save_file($fullfilename ,$filecontent);
					$infoline = "Making idt file 64 bit conform: $fullfilename\n"; 
					$installer::logger::Lang->print($infoline);			
				}
			}
		}
	}

}

#################################################################
# Include all cab files into the msi database.
# This works only on Windows
#################################################################

sub include_cabs_into_msi
{
	my ($installdir) = @_;

	installer::logger::include_header_into_logfile("Including cabs into msi database");

	my $from = cwd();
	my $to = $installdir;

	chdir($to);

	my $infoline = "Changing into directory: $to";
	$installer::logger::Lang->print($infoline);

	my $msidb = "msidb.exe";	# Has to be in the path
	my $extraslash = "";		# Has to be set for non-ActiveState perl

	my $msifilename = $installer::globals::msidatabasename;

	$msifilename = installer::converter::make_path_conform($msifilename);

	# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
	$msifilename =~ s/\//\\\\/g;
	$extraslash = "\\";

	my $allcabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir);

	for ( my $i = 0; $i <= $#{$allcabfiles}; $i++ )
	{
		my $systemcall = $msidb . " -d " . $msifilename . " -a " . ${$allcabfiles}[$i];

		my $returnvalue = system($systemcall);

		$infoline = "Systemcall: $systemcall\n";
		$installer::logger::Lang->print($infoline);
		
		if ($returnvalue)
		{
			$infoline = "ERROR: Could not execute $systemcall !\n";
			$installer::logger::Lang->print($infoline);
		}
		else
		{
			$infoline = "Success: Executed $systemcall successfully!\n";
			$installer::logger::Lang->print($infoline);
		}
		
		# deleting the cab file

		unlink(${$allcabfiles}[$i]);

		$infoline = "Deleted cab file: ${$allcabfiles}[$i]\n";
		$installer::logger::Lang->print($infoline);		
	}

	$infoline = "Changing back into directory: $from";
	$installer::logger::Lang->print($infoline);

	chdir($from);
}

#################################################################
# Executing the created batch file to pack all files.
# This works only on Windows
#################################################################

sub execute_packaging
{
	my ($localpackjobref, $loggingdir, $allvariables) = @_;

	installer::logger::include_header_into_logfile("Packaging process");

	$installer::logger::Lang->add_timestamp("Performance Info: Execute packaging start");

	my $infoline = "";
	my $from = cwd();
	my $to = $loggingdir;

	chdir($to);
	$infoline = "chdir: $to \n";
	$installer::logger::Lang->print($infoline);	

	# if the ddf file contains relative pathes, it is necessary to change into the temp directory
	if ( $allvariables->{'RELATIVE_PATHES_IN_DDF'} )
	{
		$to = $installer::globals::temppath;
		chdir($to);
		$infoline = "chdir: $to \n";
		$installer::logger::Lang->print($infoline);	
	}

	# changing the tmp directory, because makecab.exe generates temporary cab files
	my $origtemppath = "";
	if ( $ENV{'TMP'} ) { $origtemppath = $ENV{'TMP'}; }
	$ENV{'TMP'} = $installer::globals::temppath;	# setting TMP to the new unique directory!

	my $maxmakecabcalls = 3;
	my $allmakecabcalls = $#{$localpackjobref} + 1;

	for ( my $i = 0; $i <= $#{$localpackjobref}; $i++ )
	{	
		my $systemcall = ${$localpackjobref}[$i];
		
		my $callscounter = $i + 1;

		$installer::logger::Info->printf("... makecab.exe (%s/%s) ... \n", $callscounter, $allmakecabcalls);

		# my $returnvalue = system($systemcall);

		for ( my $n = 1; $n <= $maxmakecabcalls; $n++ )
		{			
			my @ddfoutput = ();

			$infoline = "Systemcall: $systemcall";
			$installer::logger::Lang->print($infoline);

			open (DDF, "$systemcall");
			while (<DDF>) {push(@ddfoutput, $_); }
			close (DDF);

			my $returnvalue = $?;	# $? contains the return value of the systemcall

			if ($returnvalue)
			{
				if ( $n < $maxmakecabcalls )
				{
                    $installer::logger::Info->printf("makecab_error (Try %s): Trying again\n", $n);
                    $installer::logger::Lang->printf("makecab_error (Try %s): Trying again\n", $n);
				}
				else
				{
                    $installer::logger::Info->printf("ERROR (Try %s): Abort packing \n", $n);
                    $installer::logger::Lang->printf("ERROR (Try %s): Abort packing \n", $n);
				}

				for ( my $m = 0; $m <= $#ddfoutput; $m++ )
				{
					if ( $ddfoutput[$m] =~ /(ERROR\:.*?)\s*$/ )
					{
						$infoline = $1 . "\n";
						if ( $n < $maxmakecabcalls )
                        {
                            $infoline =~ s/ERROR\:/makecab_error\:/i;
                        }
						$installer::logger::Info->print($infoline);
						$installer::logger::Lang->print($infoline);
					}
				}
				
				if ( $n == $maxmakecabcalls ) { installer::exiter::exit_program("ERROR: \"$systemcall\"!", "execute_packaging"); }
			}
			else
			{
				$infoline = "Success (Try $n): $systemcall";
				$installer::logger::Lang->print($infoline);
				last;
			}
		}
	}

	$installer::logger::Lang->add_timestamp("Performance Info: Execute packaging end");

	# setting back to the original tmp directory
	$ENV{'TMP'} = $origtemppath;

	chdir($from);
	$infoline = "chdir: $from \n";
	$installer::logger::Lang->print($infoline);	
}

###############################################################
# Setting the global variables ProductCode and the UpgradeCode
###############################################################

sub set_global_code_variables
{
	my ( $languagesref, $languagestringref, $allvariableshashref, $alloldproperties ) = @_;

	# In the msi template directory a files "codes.txt" has to exist, in which the ProductCode
	# and the UpgradeCode for the product are defined.
	# The name "codes.txt" can be overwritten in Product definition with CODEFILENAME . 
	# Default $installer::globals::codefilename is defined in parameter.pm.
		
	if ( $allvariableshashref->{'CODEFILENAME'} )
	{
		$installer::globals::codefilename = $installer::globals::idttemplatepath  . $installer::globals::separator . $allvariableshashref->{'CODEFILENAME'};
		installer::files::check_file($installer::globals::codefilename);
	} 

	my $infoline = "Using Codes file: $installer::globals::codefilename \n";
	$installer::logger::Lang->print($infoline);

	my $codefile = installer::files::read_file($installer::globals::codefilename);

	my $isopensource = 0;
	if ( $allvariableshashref->{'OPENSOURCE'} ) { $isopensource = $allvariableshashref->{'OPENSOURCE'}; } 

	my $onelanguage = "";
	 
	if ( $#{$languagesref} > 0 )	# more than one language
	{
		if (( $installer::globals::added_english ) && ( $#{$languagesref} == 1 )) # only multilingual because of added English
		{
			$onelanguage = ${$languagesref}[1];  # setting the first language, that is not english
		}
		else
		{
			if (( ${$languagesref}[1] =~ /jp/ ) ||
				( ${$languagesref}[1] =~ /ko/ ) ||
				( ${$languagesref}[1] =~ /zh/ ))
			{
				$onelanguage = "multiasia";
			}
			else
			{
				$onelanguage = "multiwestern";
			}
		}
	}
	else	# only one language
	{
		$onelanguage = ${$languagesref}[0];
	}

	# ProductCode must not change, if Windows patches shall be applied
	if ( $installer::globals::updatedatabase )
	{
		$installer::globals::productcode = $alloldproperties->{'ProductCode'};
	}
	elsif ( $installer::globals::prepare_winpatch )
	{	
		# ProductCode has to be specified in each language
		my $searchstring = "PRODUCTCODE";
		my $codeblock = installer::windows::idtglobal::get_language_block_from_language_file($searchstring, $codefile);
		$installer::globals::productcode = installer::windows::idtglobal::get_code_from_code_block($codeblock, $onelanguage);
	} else {
		my $guidref = get_guid_list(1, 1);	# only one GUID shall be generated
		${$guidref}[0] =~ s/\s*$//;		# removing ending spaces
		$installer::globals::productcode = "\{" . ${$guidref}[0] . "\}";
	}

	if ( $installer::globals::patch ) # patch upgrade codes are defined in soffice.lst
	{
		if ( $allvariableshashref->{'PATCHUPGRADECODE'} ) { $installer::globals::upgradecode = $allvariableshashref->{'PATCHUPGRADECODE'}; }
		else { installer::exiter::exit_program("ERROR: PATCHUPGRADECODE not defined in list file!", "set_global_code_variables"); }
	}
	else
	{ 
		# UpgradeCode can take english as default, if not defined in specified language

		$searchstring = "UPGRADECODE";	# searching in the codes.txt file
		$codeblock = installer::windows::idtglobal::get_language_block_from_language_file($searchstring, $codefile);
		$installer::globals::upgradecode = installer::windows::idtglobal::get_language_string_from_language_block($codeblock, $onelanguage, "");
	}
	
	# if (( $installer::globals::productcode eq "" ) && ( ! $isopensource )) { installer::exiter::exit_program("ERROR: ProductCode for language $onelanguage not defined in $installer::globals::codefilename !", "set_global_code_variables"); }
	if ( $installer::globals::upgradecode eq "" ) { installer::exiter::exit_program("ERROR: UpgradeCode not defined in $installer::globals::codefilename !", "set_global_code_variables"); }	

	$infoline = "Setting ProductCode to: $installer::globals::productcode \n";
	$installer::logger::Lang->print($infoline);
	$infoline = "Setting UpgradeCode to: $installer::globals::upgradecode \n";
	$installer::logger::Lang->print($infoline);

	# Adding both variables into the variables array 

	$allvariableshashref->{'PRODUCTCODE'} = $installer::globals::productcode;
	$allvariableshashref->{'UPGRADECODE'} = $installer::globals::upgradecode;

	$infoline = "Defined variable PRODUCTCODE: $installer::globals::productcode \n";
	$installer::logger::Lang->print($infoline);

	$infoline = "Defined variable UPGRADECODE: $installer::globals::upgradecode \n";
	$installer::logger::Lang->print($infoline);

}

###############################################################
# Setting the product version used in property table and
# upgrade table. Saving in global variable $msiproductversion
###############################################################

sub set_msiproductversion
{
	my ( $allvariables ) = @_;

	my $productversion = $allvariables->{'PRODUCTVERSION'};

	if (( $productversion =~ /^\s*\d+\s*$/ ) && ( $productversion > 255 )) { $productversion = $productversion%256; }

	if ( $productversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ )
	{
		$productversion = $1 . "\." . $2 . $3 . "\." . $installer::globals::buildid;		
	}
	elsif  ( $productversion =~ /^\s*(\d+)\.(\d+)\s*$/ )
	{
		$productversion = $1 . "\." . $2 . "\." . $installer::globals::buildid;	
	}
	else
	{
		my $productminor = "00";
		if (( $allvariables->{'PACKAGEVERSION'} ) && ( $allvariables->{'PACKAGEVERSION'} ne "" ))
		{
			if ( $allvariables->{'PACKAGEVERSION'} =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ ) { $productminor = $2; }
		}
				
		$productversion = $productversion . "\." . $productminor . "\." . $installer::globals::buildid;	
	}
	
	$installer::globals::msiproductversion = $productversion;
	
	# Setting $installer::globals::msimajorproductversion, to differ between old version in upgrade table
	
	if ( $installer::globals::msiproductversion =~ /^\s*(\d+)\./ )
	{
		my $major = $1;
		$installer::globals::msimajorproductversion = $major . "\.0\.0";
	}
}

#################################################################################
# Including the msi product version into the bootstrap.ini, Windows only
#################################################################################

sub put_msiproductversion_into_bootstrapfile
{
	my ($filesref) = @_;

	for ( my $i = 0; $i <= $#{$filesref}; $i++ )
	{
		my $onefile = ${$filesref}[$i];

		if ( $onefile->{'gid'} eq "gid_Profile_Version_Ini" )
		{
			my $file = installer::files::read_file($onefile->{'sourcepath'});

			for ( my $j = 0; $j <= $#{$file}; $j++ )
			{
				${$file}[$j] =~ s/\<msiproductversion\>/$installer::globals::msiproductversion/;
			}

			installer::files::save_file($onefile->{'sourcepath'}, $file);

			last;
		}
	}
}

####################################################################################
# Updating the file Property.idt dynamically
# Content: 
# Property Value
####################################################################################

sub update_reglocat_table
{
	my ($basedir, $allvariables) = @_;

	my $reglocatfilename = $basedir . $installer::globals::separator . "RegLocat.idt";
	
	# Only do something, if this file exists
	
	if ( -f $reglocatfilename )
	{
		my $reglocatfile = installer::files::read_file($reglocatfilename);
		
		my $layername = "";
		if ( $allvariables->{'REGISTRYLAYERNAME'} )
		{
			$layername = $allvariables->{'REGISTRYLAYERNAME'};
		}
		else
		{
			for ( my $i = 0; $i <= $#{$reglocatfile}; $i++ )
			{
				if ( ${$reglocatfile}[$i] =~ /\bLAYERNAMETEMPLATE\b/ )
				{
					installer::exiter::exit_program("ERROR: Variable \"REGISTRYLAYERNAME\" has to be defined", "update_reglocat_table");
				}
			}
		} 

		if ( $layername ne "" )
		{
			# Updating the layername in 

			for ( my $i = 0; $i <= $#{$reglocatfile}; $i++ )
			{
				${$reglocatfile}[$i] =~ s/\bLAYERNAMETEMPLATE\b/$layername/;
			}

			# Saving the file
			installer::files::save_file($reglocatfilename ,$reglocatfile);
			my $infoline = "Updated idt file: $reglocatfilename\n"; 
			$installer::logger::Lang->print($infoline);
		}
	}
}



####################################################################################
# Updating the file RemoveRe.idt dynamically (RemoveRegistry.idt)
# The name of the component has to be replaced.
####################################################################################

sub update_removere_table
{
	my ($basedir) = @_;

	my $removeregistryfilename = $basedir . $installer::globals::separator . "RemoveRe.idt";
	
	# Only do something, if this file exists
	
	if ( -f $removeregistryfilename )
	{
		my $removeregistryfile = installer::files::read_file($removeregistryfilename);

		for ( my $i = 0; $i <= $#{$removeregistryfile}; $i++ )
		{
			for ( my $i = 0; $i <= $#{$removeregistryfile}; $i++ )
			{
				${$removeregistryfile}[$i] =~ s/\bREGISTRYROOTCOMPONENT\b/$installer::globals::registryrootcomponent/;
			}
		}

		# Saving the file
		installer::files::save_file($removeregistryfilename ,$removeregistryfile);
		my $infoline = "Updated idt file: $removeregistryfilename \n"; 
		$installer::logger::Lang->print($infoline);
	}
}

##########################################################################
# Reading saved mappings in Files.idt and Director.idt.
# This is required, if installation sets shall be created,
# that can be used for creation of msp files.
##########################################################################

sub read_saved_mappings
{
	installer::logger::include_header_into_logfile("Reading saved mappings from older installation sets:");

	$installer::logger::Lang->add_timestamp("Performance Info: Reading saved mappings start");
	
	if ( $installer::globals::previous_idt_dir )
	{
		my @errorlines = ();
		my $errorstring = "";
		my $error_occured = 0;
		my $file_error_occured = 0;
		my $dir_error = 0;
		
		my $idtdir = $installer::globals::previous_idt_dir;
		$idtdir =~ s/\Q$installer::globals::separator\E\s*$//;

		# Reading File.idt

		my $idtfile = $idtdir . $installer::globals::separator . "File.idt";
        $installer::logger::Global->print("\n");
        $installer::logger::Global->printf("Analyzing file: %s\n", $idtfile);
		if ( ! -f $idtfile )
        {
            $installer::logger::Global->printf("Warning: File %s does not exist!\n", $idtfile);
        }
		
		my $n = 0;
		open (F, "<$idtfile") || installer::exiter::exit_program("ERROR: Cannot open file $idtfile for reading", "read_saved_mappings");
		<F>; <F>; <F>;
		while (<F>)
		{
			m/^([^\t]+)\t([^\t]+)\t((.*)\|)?([^\t]*)/;
			print "OUT1: \$1: $1, \$2: $2, \$3: $3, \$4: $4, \$5: $5\n";
			next if ("$1" eq "$5") && (!defined($3));
			my $lc1 = lc($1);
			
			if ( exists($installer::globals::savedmapping{"$2/$5"}))
			{
				if ( ! $file_error_occured )
				{
					$errorstring = "\nErrors in $idtfile: \n";
					push(@errorlines, $errorstring);
				}
				$errorstring = "Duplicate savedmapping{" . "$2/$5}\n";
				push(@errorlines, $errorstring);
				$error_occured = 1;
				$file_error_occured = 1;
			}
			
			if ( exists($installer::globals::savedrevmapping{$lc1}))
			{
				if ( ! $file_error_occured )
				{
					$errorstring = "\nErrors in $idtfile: \n";
					push(@errorlines, $errorstring);
				}
				$errorstring = "Duplicate savedrevmapping{" . "$lc1}\n";
				push(@errorlines, $errorstring);
				$error_occured = 1;
				$file_error_occured = 1;
			}
			
			my $shortname = $4 || '';

			# Don't reuse illegal 8.3 mappings that we used to generate in 2.0.4
			if (index($shortname, '.') > 8 ||
			    (index($shortname, '.') == -1 && length($shortname) > 8))
			{
			    $shortname = '';
			}

			if (( $shortname ne '' ) && ( index($shortname, '~') > 0 ) && ( exists($installer::globals::savedrev83mapping{$shortname}) ))
			{
				if ( ! $file_error_occured )
				{
					$errorstring = "\nErrors in $idtfile: \n";
					push(@errorlines, $errorstring);
				}
				$errorstring = "Duplicate savedrev83mapping{" . "$shortname}\n";
				push(@errorlines, $errorstring);
				$error_occured = 1;
				$file_error_occured = 1;
			}

			$installer::globals::savedmapping{"$2/$5"} = "$1;$shortname";
			$installer::globals::savedrevmapping{lc($1)} = "$2/$5";
			$installer::globals::savedrev83mapping{$shortname} = "$2/$5" if $shortname ne '';
			$n++;
		}

		close (F);

        $installer::logger::Global->printf("Read %s old file table key or 8.3 name mappings from %s\n",
            $n, $idtfile);

		# Reading Director.idt
		
		$idtfile = $idtdir . $installer::globals::separator . "Director.idt";
        $installer::logger::Global->print("\n");
        $installer::logger::Global->printf("Analyzing file %s\n", $idtfile);
		if ( ! -f $idtfile )
        {
            $installer::logger::Global->printf("Warning: File %s does not exist!\n", $idtfile);
        }

		$n = 0;
		open (F, "<$idtfile") || installer::exiter::exit_program("ERROR: Cannot open file $idtfile for reading", "read_saved_mappings");
		<F>; <F>; <F>;
		while (<F>)
		{
			m/^([^\t]+)\t([^\t]+)\t(([^~]+~\d.*)\|)?([^\t]*)/;
			next if (!defined($3));
			my $lc1 = lc($1);

			print "OUT2: \$1: $1, \$2: $2, \$3: $3\n";

			if ( exists($installer::globals::saved83dirmapping{$1}) )
			{
				if ( ! $dir_error_occured )
				{
					$errorstring = "\nErrors in $idtfile: \n";
					push(@errorlines, $errorstring);
				}
				$errorstring = "Duplicate saved83dirmapping{" . "$1}\n";
				push(@errorlines, $errorstring);
				$error_occured = 1;
				$dir_error_occured = 1;
			}

			$installer::globals::saved83dirmapping{$1} = $4;
			$n++;
		}
		close (F);

        $installer::logger::Global->printf("Read %s old directory 8.3 name mappings from %s\n",
            $n, $idtfile);

		# Analyzing errors
		
		if ( $error_occured )
		{
			for my $line (@errorlines)
			{
                $installer::logger::Info->print($line);
                $installer::logger::Global->print($line);
			}
			installer::exiter::exit_program("ERROR: Duplicate entries in saved mappings!", "read_saved_mappings");			
		}
	} else {
		installer::exiter::exit_program("ERROR: Windows patch shall be prepared, but environment variable PREVIOUS_IDT_DIR is not set!", "read_saved_mappings");			
	}

	$installer::logger::Lang->add_timestamp("Performance Info: Reading saved mappings end");
}

1;

