diff --git a/tools/README b/tools/README
index 1648412..c92eedd 100644
--- a/tools/README
+++ b/tools/README
@@ -19,3 +19,14 @@ This directory contains a set of tools for preparing auto-updates.
This is the simplest possible package configuration, where all files for
a release are placed in a single package. This means that the whole
package will need to be downloaded to install the update.
+
+ * auto-create-packages.rb
+
+ -v [-o ]
+
+ This tool generates a zip file from difference of old version and new version of an application files.
+ The zip file name contains versions like this: OldVersion_NewVersion.zip
+ The zip file contains generated package (out.zip) an file_list.xml and updater.exe and libbz2.dll
+ this file needed in user system for update.
+ Note: updater.exe is build in Windows 10 with Visual Studio 2015
+
\ No newline at end of file
diff --git a/tools/auto-create-packages.rb b/tools/auto-create-packages.rb
new file mode 100644
index 0000000..1649133
--- /dev/null
+++ b/tools/auto-create-packages.rb
@@ -0,0 +1,173 @@
+#!/usr/bin/ruby
+require 'fileutils.rb'
+require 'find'
+require 'optparse'
+########################################
+# copy 'src' to 'dest', preserving the attributes
+# of 'src'
+def copy_file(src, dest)
+ FileUtils.cp src, dest, :preserve => true
+end
+
+# Returns true if |src_file| and |dest_file| have the same contents, type
+# and permissions or false otherwise
+def compare_files(src_file, dest_file)
+ if File.ftype(src_file) != File.ftype(dest_file)
+ $stderr.puts "Type of file #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ if File.file?(src_file) && !FileUtils.identical?(src_file, dest_file)
+ $stderr.puts "Contents of file #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ src_stat = File.stat(src_file)
+ dest_stat = File.stat(dest_file)
+
+ if src_stat.mode != dest_stat.mode
+ $stderr.puts "Permissions of #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ return true
+end
+
+
+# Compares the contents of two directories and returns a map of (file path => change type)
+# for files and directories which differ between the two
+def compare_dirs(src_dir, dest_dir)
+ src_dir += '/' if !src_dir.end_with?('/')
+ dest_dir += '/' if !dest_dir.end_with?('/')
+
+ src_file_map = {}
+ Find.find(src_dir) do |src_file|
+ src_file = src_file[src_dir.length..-1]
+ src_file_map[src_file] = nil
+ end
+
+ change_map = {}
+ Find.find(dest_dir) do |dest_file|
+ dest_file = dest_file[dest_dir.length..-1]
+
+ if !src_file_map.include?(dest_file)
+ change_map[dest_file] = :deleted
+ elsif !compare_files("#{src_dir}/#{dest_file}", "#{dest_dir}/#{dest_file}")
+ change_map[dest_file] = :updated
+ end
+
+ src_file_map.delete(dest_file)
+ end
+
+ src_file_map.each do |file, val|
+ change_map[file] = :added
+ end
+
+ return change_map
+end
+
+def create_new_package(src_dir, dest_dir, package_dir)
+ deleted_files = []
+ change_map = compare_dirs(src_dir, dest_dir)
+ change_map.each do |file, type|
+ puts "#{file}, #{type}"
+ if(type == :added || type == :updated)
+ #puts "added...........updated"
+ copy_file("#{src_dir}/#{file}", package_dir)
+ elsif(type == :deleted)
+ deleted_files << file;
+ end
+ end
+ return deleted_files
+end
+
+def create_file(name, content)
+ File.open(name, 'w') do |file|
+ file.puts content
+ end
+ return name
+end
+puts "*********************"
+
+###########
+target_version = nil
+old_version = "x.x.x"
+OptionParser.new do |parser|
+ parser.banner = "#{$0} -v [-o ]"
+ parser.on("-v","--version [version]","Specifies the target version string for this update") do |version|
+ target_version = version
+ end
+ parser.on("-o","--oldversion [version]","Specifies the old version string for this update") do |oldversion|
+ old_version = oldversion
+ end
+end.parse!
+
+raise "Target version not specified (use -v option)" if !target_version
+
+if ARGV.length < 3
+ raise "Missing arguments ( #{$0} -v version ) "
+end
+
+input_old_dir = ARGV[0]
+input_new_dir = ARGV[1]
+output_dir = ARGV[2]
+
+
+dif_version_files = "temp"
+
+# Remove the temp package dirs if they
+# already exist
+FileUtils.rm_rf(dif_version_files)
+FileUtils.rm_rf(output_dir)
+# Create the install directory with the old app
+Dir.mkdir(dif_version_files)
+#Dir.mkdir(output_dir)
+
+# Create Version file
+create_file("#{dif_version_files}/version.ini", target_version)
+
+deleted_files = create_new_package(input_new_dir, input_old_dir, dif_version_files)
+
+###########
+puts "*********************"
+###########
+
+ENV["updater"] = "updater.exe"
+ENV["dependency"] = "libbz2.dll"
+
+ENV["platform"] = "MsWin32"
+ENV["version"] = target_version
+
+ENV["input"] = dif_version_files
+ENV["package"] = "config.js"
+ENV["output"] = output_dir
+ENV["uninstall"] = deleted_files.join(" *spliter* ")
+
+require_relative ("create-packages.rb")
+
+# Remove the temp package dir
+FileUtils.rm_rf(dif_version_files)
+
+# get the all of each output files path
+output_file_list = []
+output_file_list_for_delete = []
+output_zip_file = "#{old_version}_#{target_version}.zip"
+
+Find.find(output_dir) do |path|
+ next if (File.directory?(path) && !File.symlink?(path))
+ output_file_list << "\"#{strip_prefix_slash(strip_prefix(path, output_dir))}\""
+ output_file_list_for_delete << path
+end
+
+currentDir = Dir.pwd
+Dir.chdir(output_dir) do
+ if (!system("#{currentDir}/zip #{output_zip_file} #{output_file_list.join(" ")}"))
+ raise "Failed to generate package zip file"
+ else
+ puts "Generated package zip file : #{output_zip_file}"
+ end
+end
+
+output_file_list_for_delete.each do |file|
+ FileUtils.rm_rf(file)
+end
diff --git a/tools/create-packages.rb b/tools/create-packages.rb
index 583dce6..f4634b9 100755
--- a/tools/create-packages.rb
+++ b/tools/create-packages.rb
@@ -82,6 +82,14 @@ def strip_prefix(string,prefix)
return string.sub(prefix,"")
end
+def strip_prefix_slash(string)
+ prefix = "/"
+ if (string.start_with?(prefix))
+ return string.sub(prefix,"")
+ end
+ return string
+end
+
def file_sha1(path)
Digest::SHA1.file(path).to_s
end
@@ -95,6 +103,7 @@ class UpdateScriptGenerator
# package_config - The PackageConfig specifying the file -> package map
# for the application and other config options
# file_list - A list of all files in 'input_dir' which make up the install
+ # uninstall_file_list - A list of all files which make up the uninstall
# package_file_map - A map of (package name -> [paths of files in this package])
def initialize(target_version,
@@ -103,18 +112,22 @@ def initialize(target_version,
output_dir,
package_config,
file_list,
+ uninstall_file_list,
package_file_map)
@target_version = target_version
@platform = platform
@config = package_config
+ # List of files to uninstall in this version
+ @files_to_uninstall = uninstall_file_list
+
# List of files to install in this version
@files_to_install = []
file_list.each do |path|
file = UpdateScriptFile.new
file.path = strip_prefix(path,input_dir)
-
+ #puts file.path
if (File.basename(path) == @config.updater_binary)
# for the updater binary, use the possibly substituted
# version in the output directory
@@ -173,7 +186,8 @@ def to_xml()
update_elem.add_element deps_to_xml()
update_elem.add_element packages_to_xml()
update_elem.add_element install_to_xml()
-
+ update_elem.add_element uninstall_to_xml()
+
output = ""
doc.write output
return output
@@ -227,6 +241,16 @@ def install_to_xml()
return install_elem
end
+ def uninstall_to_xml()
+ uninstall_elem = REXML::Element.new("uninstall")
+ @files_to_uninstall.each do |file|
+ file_elem = REXML::Element.new("file")
+ uninstall_elem.add_element(file_elem)
+ file_elem.text = file
+ end
+ return uninstall_elem
+ end
+
def file_mode_string(mode)
mode_octal = (mode & 0777).to_s(8)
return "0#{mode_octal}"
@@ -234,7 +258,7 @@ def file_mode_string(mode)
end
class PackageConfig
- attr_reader :main_binary, :updater_binary
+ attr_reader :main_binary, :updater_binary, :updater_binary_dependency
def initialize(map_file)
@rule_map = {}
@@ -248,10 +272,11 @@ def initialize(map_file)
@main_binary = config_json["main-binary"]
@updater_binary = config_json["updater-binary"]
+ @updater_binary_dependency = config_json["updater-binary-dependency"]
end
def is_updater(file)
- return File.basename(file) == @updater_binary
+ return (File.basename(file) == @updater_binary && File.basename(file) == @updater_binary_dependency)
end
def package_for_file(file)
@@ -269,11 +294,28 @@ def package_for_file(file)
end
updater_binary_input_path = nil
+updater_dependency_input_path = nil
target_version = nil
target_platform = nil
zip_flags = nil
-OptionParser.new do |parser|
+updater_binary_input_path = ENV.fetch("updater", nil)
+updater_dependency_input_path = ENV.fetch("dependency", nil)
+target_version = ENV.fetch("version", nil)
+target_platform = ENV.fetch("platform", nil)
+
+input_dir = ENV.fetch("input", nil)
+package_map_file = ENV.fetch("package", nil)
+output_dir = ENV.fetch("output", nil)
+uninstall_file_string = ENV.fetch("uninstall", [])
+uninstall_file_list = []
+if(uninstall_file_string != nil && !uninstall_file_string.empty?)
+ uninstall_file_list = uninstall_file_string.split(" *spliter* ")
+end
+
+if (updater_binary_input_path == nil || target_version == nil || target_platform == nil)
+
+ OptionParser.new do |parser|
parser.banner = "#{$0} [options] "
parser.on("-u","--updater [updater binary]","Specifies the updater binary to use") do |updater|
updater_binary_input_path = updater
@@ -287,23 +329,29 @@ def package_for_file(file)
parser.on(nil,"--bzip2","Use bzip2 compression (requires that 'zip' supports the -Z bz2 argument)") do
zip_flags = "-Z bzip2"
end
-end.parse!
+ end.parse!
+
+ raise "Platform not specified (use -p option)" if !target_platform
+ raise "Target version not specified (use -v option)" if !target_version
+
+ if ARGV.length < 3
+ raise "Missing arguments"
+ end
-raise "Platform not specified (use -p option)" if !target_platform
-raise "Target version not specified (use -v option)" if !target_version
+ input_dir = ARGV[0]
+ package_map_file = ARGV[1]
+ output_dir = ARGV[2]
-if ARGV.length < 3
- raise "Missing arguments"
end
-input_dir = ARGV[0]
-package_map_file = ARGV[1]
-output_dir = ARGV[2]
+
+
FileUtils.mkpath(output_dir)
# get the details of each input file
input_file_list = []
+
Find.find(input_dir) do |path|
next if (File.directory?(path) && !File.symlink?(path))
input_file_list << path
@@ -328,11 +376,13 @@ def package_for_file(file)
package_file_map[package] = [] if !package_file_map[package]
package_file_map[package] << file
end
-
+def copy_file(src, dest)
+ FileUtils.cp src, dest, :preserve => true
+end
# generate each package
package_file_map.each do |package,files|
puts "Generating package #{package}"
-
+
quoted_files = []
files.each do |file|
# do not package the updater binary into a zip file -
@@ -343,7 +393,12 @@ def package_for_file(file)
end
next
end
- quoted_files << "\"#{strip_prefix(file,input_dir)}\""
+ quoted_files << "\"#{strip_prefix_slash(strip_prefix(file,input_dir))}\""
+ ######################################################
+ #puts "\".#{strip_prefix(file,input_dir)}\""
+ #output_path = File.expand_path(input_dir)
+ #quoted_files << "#{output_path}#{strip_prefix(file,input_dir)}"
+ ######################################################
end
if (quoted_files.empty?)
@@ -369,9 +424,23 @@ def package_for_file(file)
output_file = "#{output_path}/#{package}.zip"
File.unlink(output_file) if File.exist?(output_file)
-
+#######################################
+# Dir.chdir(input_dir) do
+# puts "zip #{zip_flags} #{output_file} #{quoted_file_list}"
+#
+# if (!system("zip #{zip_flags} #{output_file} #{quoted_file_list}"))
+# raise "Failed to generate package #{package}"
+# else
+# puts "Generated package #{package} : #{file_sha1(output_file)}"
+# end
+# end
+#puts Dir.pwd #current dir
+#copy_file("zip.exe", "#{File.expand_path(input_dir)}/zip.exe")
+#copy_file("zlib1.dll", "#{File.expand_path(input_dir)}/zlib1.dll")
+####################################
+ currentDir = Dir.pwd
Dir.chdir(input_dir) do
- if (!system("zip #{zip_flags} #{output_file} #{quoted_file_list}"))
+ if (!system("#{currentDir}/zip #{zip_flags} #{output_file} #{quoted_file_list}"))
raise "Failed to generate package #{package}"
else
puts "Generated package #{package} : #{file_sha1(output_file)}"
@@ -380,17 +449,18 @@ def package_for_file(file)
end
# copy the updater to the output directory
-puts "Using updater binary: #{updater_binary_input_path}"
-if !updater_binary_input_path
+puts "Using updater binary: #{updater_binary_input_path} and #{updater_dependency_input_path} "
+if (!updater_binary_input_path || !updater_dependency_input_path)
puts "Updater binary not found in input directory: #{input_dir}"
exit(1)
end
FileUtils.cp updater_binary_input_path, "#{output_dir}/#{File.basename(updater_binary_input_path)}", :preserve => true
+FileUtils.cp updater_dependency_input_path, "#{output_dir}/#{File.basename(updater_dependency_input_path)}", :preserve => true
# output the file_list.xml file
update_script = UpdateScriptGenerator.new(target_version,target_platform,input_dir,
- output_dir,package_config,input_file_list,package_file_map)
+ output_dir,package_config,input_file_list, uninstall_file_list,package_file_map)
output_xml_file = "#{output_dir}/file_list.unformatted.xml"
File.open(output_xml_file,'w') do |file|
file.write update_script.to_xml()
diff --git a/tools/tests/README.txt b/tools/tests/README.txt
new file mode 100644
index 0000000..a31540d
--- /dev/null
+++ b/tools/tests/README.txt
@@ -0,0 +1 @@
+This directory contains a set of tests for auto-create-packages.rb tool.
diff --git a/tools/tests/auto-create-packages.rb b/tools/tests/auto-create-packages.rb
new file mode 100644
index 0000000..1649133
--- /dev/null
+++ b/tools/tests/auto-create-packages.rb
@@ -0,0 +1,173 @@
+#!/usr/bin/ruby
+require 'fileutils.rb'
+require 'find'
+require 'optparse'
+########################################
+# copy 'src' to 'dest', preserving the attributes
+# of 'src'
+def copy_file(src, dest)
+ FileUtils.cp src, dest, :preserve => true
+end
+
+# Returns true if |src_file| and |dest_file| have the same contents, type
+# and permissions or false otherwise
+def compare_files(src_file, dest_file)
+ if File.ftype(src_file) != File.ftype(dest_file)
+ $stderr.puts "Type of file #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ if File.file?(src_file) && !FileUtils.identical?(src_file, dest_file)
+ $stderr.puts "Contents of file #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ src_stat = File.stat(src_file)
+ dest_stat = File.stat(dest_file)
+
+ if src_stat.mode != dest_stat.mode
+ $stderr.puts "Permissions of #{src_file} and #{dest_file} differ"
+ return false
+ end
+
+ return true
+end
+
+
+# Compares the contents of two directories and returns a map of (file path => change type)
+# for files and directories which differ between the two
+def compare_dirs(src_dir, dest_dir)
+ src_dir += '/' if !src_dir.end_with?('/')
+ dest_dir += '/' if !dest_dir.end_with?('/')
+
+ src_file_map = {}
+ Find.find(src_dir) do |src_file|
+ src_file = src_file[src_dir.length..-1]
+ src_file_map[src_file] = nil
+ end
+
+ change_map = {}
+ Find.find(dest_dir) do |dest_file|
+ dest_file = dest_file[dest_dir.length..-1]
+
+ if !src_file_map.include?(dest_file)
+ change_map[dest_file] = :deleted
+ elsif !compare_files("#{src_dir}/#{dest_file}", "#{dest_dir}/#{dest_file}")
+ change_map[dest_file] = :updated
+ end
+
+ src_file_map.delete(dest_file)
+ end
+
+ src_file_map.each do |file, val|
+ change_map[file] = :added
+ end
+
+ return change_map
+end
+
+def create_new_package(src_dir, dest_dir, package_dir)
+ deleted_files = []
+ change_map = compare_dirs(src_dir, dest_dir)
+ change_map.each do |file, type|
+ puts "#{file}, #{type}"
+ if(type == :added || type == :updated)
+ #puts "added...........updated"
+ copy_file("#{src_dir}/#{file}", package_dir)
+ elsif(type == :deleted)
+ deleted_files << file;
+ end
+ end
+ return deleted_files
+end
+
+def create_file(name, content)
+ File.open(name, 'w') do |file|
+ file.puts content
+ end
+ return name
+end
+puts "*********************"
+
+###########
+target_version = nil
+old_version = "x.x.x"
+OptionParser.new do |parser|
+ parser.banner = "#{$0} -v [-o ]"
+ parser.on("-v","--version [version]","Specifies the target version string for this update") do |version|
+ target_version = version
+ end
+ parser.on("-o","--oldversion [version]","Specifies the old version string for this update") do |oldversion|
+ old_version = oldversion
+ end
+end.parse!
+
+raise "Target version not specified (use -v option)" if !target_version
+
+if ARGV.length < 3
+ raise "Missing arguments ( #{$0} -v version ) "
+end
+
+input_old_dir = ARGV[0]
+input_new_dir = ARGV[1]
+output_dir = ARGV[2]
+
+
+dif_version_files = "temp"
+
+# Remove the temp package dirs if they
+# already exist
+FileUtils.rm_rf(dif_version_files)
+FileUtils.rm_rf(output_dir)
+# Create the install directory with the old app
+Dir.mkdir(dif_version_files)
+#Dir.mkdir(output_dir)
+
+# Create Version file
+create_file("#{dif_version_files}/version.ini", target_version)
+
+deleted_files = create_new_package(input_new_dir, input_old_dir, dif_version_files)
+
+###########
+puts "*********************"
+###########
+
+ENV["updater"] = "updater.exe"
+ENV["dependency"] = "libbz2.dll"
+
+ENV["platform"] = "MsWin32"
+ENV["version"] = target_version
+
+ENV["input"] = dif_version_files
+ENV["package"] = "config.js"
+ENV["output"] = output_dir
+ENV["uninstall"] = deleted_files.join(" *spliter* ")
+
+require_relative ("create-packages.rb")
+
+# Remove the temp package dir
+FileUtils.rm_rf(dif_version_files)
+
+# get the all of each output files path
+output_file_list = []
+output_file_list_for_delete = []
+output_zip_file = "#{old_version}_#{target_version}.zip"
+
+Find.find(output_dir) do |path|
+ next if (File.directory?(path) && !File.symlink?(path))
+ output_file_list << "\"#{strip_prefix_slash(strip_prefix(path, output_dir))}\""
+ output_file_list_for_delete << path
+end
+
+currentDir = Dir.pwd
+Dir.chdir(output_dir) do
+ if (!system("#{currentDir}/zip #{output_zip_file} #{output_file_list.join(" ")}"))
+ raise "Failed to generate package zip file"
+ else
+ puts "Generated package zip file : #{output_zip_file}"
+ end
+end
+
+output_file_list_for_delete.each do |file|
+ FileUtils.rm_rf(file)
+end
diff --git a/tools/tests/config.js b/tools/tests/config.js
new file mode 100644
index 0000000..a885a34
--- /dev/null
+++ b/tools/tests/config.js
@@ -0,0 +1,13 @@
+{
+ "packages" : {
+ "out" : [
+ ".*"
+ ]
+ },
+
+
+ "updater-binary" : "updater.exe",
+ "updater-binary-dependency" : "libbz2.dll",
+
+ "main-binary" : "out.zip"
+}
diff --git a/tools/tests/create-packages.rb b/tools/tests/create-packages.rb
new file mode 100644
index 0000000..f4634b9
--- /dev/null
+++ b/tools/tests/create-packages.rb
@@ -0,0 +1,474 @@
+#!/usr/bin/ruby
+
+require 'digest/sha1'
+require 'fileutils'
+require 'rubygems'
+require 'find'
+require 'json'
+require 'rexml/document'
+require 'optparse'
+
+# syntax:
+#
+# create-packages.rb
+#
+# Takes the set of files that make up a release and splits them up into
+# a set of .zip packages along with a file_list.xml file listing all
+# the files in the release and mapping them to their respective
+# packages.
+#
+# Outputs:
+#
+# /$PACKAGE_NAME.zip
+# These are packages containing the files in this version of the software.
+#
+# /file_list.xml
+# This file lists all the files contained in this version of the software and
+# the packages which they are contained in.
+#
+# /$UPDATER_BINARY
+# The standalone auto-update installer binary. This is not compressed so that
+# it can be downloaded and executed directly.
+#
+
+# Represents a group of updates in a release
+class UpdateScriptPackage
+ # name - The name of the package (without any extension)
+ # hash - The SHA-1 hash of the package
+ # size - The size of the package in bytes
+ attr_reader :name,:hash,:size
+ attr_writer :name,:hash,:size
+end
+
+# Represents a single file in a release
+class UpdateScriptFile
+ # path - The path of the file relative to the installation directory
+ # hash - The SHA-1 hash of the file
+ # permissions - The permissions of the file (See File::stat)
+ # size - The size of the file in bytes
+ # package - The name of the package containing this file
+ attr_reader :path,:hash,:permissions,:size,:package,:target,:is_main_binary
+ attr_writer :path,:hash,:permissions,:size,:package,:target,:is_main_binary
+end
+
+# Utility method - convert a hash map to an REXML element
+#
+# Hash keys are converted to elements and hash values either
+# to text contents or child elements (if the value is a Hash)
+#
+# 'root' - the root REXML::Element
+# 'map' - a hash mapping element names to text contents or
+# hash maps
+#
+def hash_to_xml(root,map)
+ map.each do |key,value|
+ element = REXML::Element.new(key)
+ if value.instance_of?(String)
+ element.text = value.dup
+ elsif value.instance_of?(Hash)
+ hash_to_xml(element,value)
+ elsif !value.nil?
+ raise "Unsupported value type #{value.class}"
+ end
+ root.add_element element
+ end
+ return root
+end
+
+def strip_prefix(string,prefix)
+ if (!string.start_with?(prefix))
+ raise "String does not start with prefix"
+ end
+ return string.sub(prefix,"")
+end
+
+def strip_prefix_slash(string)
+ prefix = "/"
+ if (string.start_with?(prefix))
+ return string.sub(prefix,"")
+ end
+ return string
+end
+
+def file_sha1(path)
+ Digest::SHA1.file(path).to_s
+end
+
+class UpdateScriptGenerator
+
+ # target_version - The version string for the build in this update
+ # platform - The platform which this build is for
+ # input_dir - The directory containing files that make up the install
+ # output_dir - The directory containing the generated packages
+ # package_config - The PackageConfig specifying the file -> package map
+ # for the application and other config options
+ # file_list - A list of all files in 'input_dir' which make up the install
+ # uninstall_file_list - A list of all files which make up the uninstall
+ # package_file_map - A map of (package name -> [paths of files in this package])
+
+ def initialize(target_version,
+ platform,
+ input_dir,
+ output_dir,
+ package_config,
+ file_list,
+ uninstall_file_list,
+ package_file_map)
+
+ @target_version = target_version
+ @platform = platform
+ @config = package_config
+
+ # List of files to uninstall in this version
+ @files_to_uninstall = uninstall_file_list
+
+ # List of files to install in this version
+ @files_to_install = []
+ file_list.each do |path|
+ file = UpdateScriptFile.new
+ file.path = strip_prefix(path,input_dir)
+ #puts file.path
+ if (File.basename(path) == @config.updater_binary)
+ # for the updater binary, use the possibly substituted
+ # version in the output directory
+ path = "#{output_dir}/#{@config.updater_binary}"
+ end
+
+ file.is_main_binary = (file.path == package_config.main_binary)
+
+ if (File.symlink?(path))
+ file.target = File.readlink(path)
+ else
+ file.hash = file_sha1(path)
+ file.permissions = File.stat(path).mode
+ file.size = File.size(path)
+ file.package = @config.package_for_file(file.path)
+
+ if (!file.package)
+ raise "Could not find package for file #{file.path}"
+ end
+ end
+
+ @files_to_install << file
+ end
+
+ # List of packages containing files for this version
+ @packages = []
+ package_file_map.each do |package_name,files|
+ if (package_name == @config.updater_binary)
+ path = "#{output_dir}/#{package_name}"
+ else
+ path = "#{output_dir}/#{package_name}.zip"
+ end
+
+ package = UpdateScriptPackage.new
+ package.name = package_name
+ package.size = File.size(path)
+ package.hash = file_sha1(path)
+ @packages << package
+ end
+ end
+
+ def to_xml()
+ doc = REXML::Document.new
+ update_elem = REXML::Element.new("update")
+ doc.add_element update_elem
+
+ version_elem = REXML::Element.new("targetVersion")
+ version_elem.text = @target_version
+
+ platform_elem = REXML::Element.new("platform")
+ platform_elem.text = @platform
+
+ update_elem.add_attribute("version","3")
+ update_elem.add_element version_elem
+ update_elem.add_element platform_elem
+ update_elem.add_element deps_to_xml()
+ update_elem.add_element packages_to_xml()
+ update_elem.add_element install_to_xml()
+ update_elem.add_element uninstall_to_xml()
+
+ output = ""
+ doc.write output
+ return output
+ end
+
+ def deps_to_xml()
+ deps_elem = REXML::Element.new("dependencies")
+ dependency = @config.updater_binary
+ dep_elem = REXML::Element.new("file")
+ dep_elem.text = dependency
+ deps_elem.add_element dep_elem
+ return deps_elem
+ end
+
+ def packages_to_xml()
+ packages_elem = REXML::Element.new("packages")
+ @packages.each do |package|
+ package_elem = REXML::Element.new("package")
+ packages_elem.add_element package_elem
+ hash_to_xml(package_elem,{
+ "name" => package.name,
+ "size" => package.size.to_s,
+ "hash" => package.hash
+ })
+ end
+ return packages_elem
+ end
+
+ def install_to_xml()
+ install_elem = REXML::Element.new("install")
+ @files_to_install.each do |file|
+ file_elem = REXML::Element.new("file")
+ install_elem.add_element(file_elem)
+
+ attributes = {"name" => file.path}
+ if (file.target)
+ attributes["target"] = file.target
+ else
+ attributes["size"] = file.size.to_s
+ attributes["permissions"] = file_mode_string(file.permissions)
+ attributes["hash"] = file.hash
+ attributes["package"] = file.package
+ end
+
+ if (file.is_main_binary)
+ attributes["is-main-binary"] = "true"
+ end
+
+ hash_to_xml(file_elem,attributes)
+ end
+ return install_elem
+ end
+
+ def uninstall_to_xml()
+ uninstall_elem = REXML::Element.new("uninstall")
+ @files_to_uninstall.each do |file|
+ file_elem = REXML::Element.new("file")
+ uninstall_elem.add_element(file_elem)
+ file_elem.text = file
+ end
+ return uninstall_elem
+ end
+
+ def file_mode_string(mode)
+ mode_octal = (mode & 0777).to_s(8)
+ return "0#{mode_octal}"
+ end
+end
+
+class PackageConfig
+ attr_reader :main_binary, :updater_binary, :updater_binary_dependency
+
+ def initialize(map_file)
+ @rule_map = {}
+ config_json = JSON.parse(File.read(map_file))
+ config_json["packages"].each do |package,rules|
+ rules.each do |rule|
+ rule_regex = Regexp.new("^#{rule}")
+ @rule_map[rule_regex] = package
+ end
+ end
+
+ @main_binary = config_json["main-binary"]
+ @updater_binary = config_json["updater-binary"]
+ @updater_binary_dependency = config_json["updater-binary-dependency"]
+ end
+
+ def is_updater(file)
+ return (File.basename(file) == @updater_binary && File.basename(file) == @updater_binary_dependency)
+ end
+
+ def package_for_file(file)
+ match = nil
+ @rule_map.each do |rule,package|
+ if (file =~ rule)
+ if match && match != package
+ raise "Multiple packages match file #{file} - found '#{match}' and '#{package}'"
+ end
+ match = package
+ end
+ end
+ return match
+ end
+end
+
+updater_binary_input_path = nil
+updater_dependency_input_path = nil
+target_version = nil
+target_platform = nil
+zip_flags = nil
+
+updater_binary_input_path = ENV.fetch("updater", nil)
+updater_dependency_input_path = ENV.fetch("dependency", nil)
+target_version = ENV.fetch("version", nil)
+target_platform = ENV.fetch("platform", nil)
+
+input_dir = ENV.fetch("input", nil)
+package_map_file = ENV.fetch("package", nil)
+output_dir = ENV.fetch("output", nil)
+uninstall_file_string = ENV.fetch("uninstall", [])
+uninstall_file_list = []
+if(uninstall_file_string != nil && !uninstall_file_string.empty?)
+ uninstall_file_list = uninstall_file_string.split(" *spliter* ")
+end
+
+if (updater_binary_input_path == nil || target_version == nil || target_platform == nil)
+
+ OptionParser.new do |parser|
+ parser.banner = "#{$0} [options] "
+ parser.on("-u","--updater [updater binary]","Specifies the updater binary to use") do |updater|
+ updater_binary_input_path = updater
+ end
+ parser.on("-v","--version [version]","Specifies the target version string for this update") do |version|
+ target_version = version
+ end
+ parser.on("-p","--platform [platform]","Specifies the target platform for this update") do |platform|
+ target_platform = platform
+ end
+ parser.on(nil,"--bzip2","Use bzip2 compression (requires that 'zip' supports the -Z bz2 argument)") do
+ zip_flags = "-Z bzip2"
+ end
+ end.parse!
+
+ raise "Platform not specified (use -p option)" if !target_platform
+ raise "Target version not specified (use -v option)" if !target_version
+
+ if ARGV.length < 3
+ raise "Missing arguments"
+ end
+
+ input_dir = ARGV[0]
+ package_map_file = ARGV[1]
+ output_dir = ARGV[2]
+
+end
+
+
+
+
+FileUtils.mkpath(output_dir)
+
+# get the details of each input file
+input_file_list = []
+
+Find.find(input_dir) do |path|
+ next if (File.directory?(path) && !File.symlink?(path))
+ input_file_list << path
+end
+
+# map each input file to a corresponding package
+
+# read the package map
+package_config = PackageConfig.new(package_map_file)
+
+# map of package name -> array of files
+package_file_map = {}
+
+input_file_list.each do |file|
+ next if File.symlink?(file)
+
+ relative_file = strip_prefix(file,input_dir)
+ package = package_config.package_for_file(relative_file)
+ if (!package)
+ raise "Unable to find package for file #{file}"
+ end
+ package_file_map[package] = [] if !package_file_map[package]
+ package_file_map[package] << file
+end
+def copy_file(src, dest)
+ FileUtils.cp src, dest, :preserve => true
+end
+# generate each package
+package_file_map.each do |package,files|
+ puts "Generating package #{package}"
+
+ quoted_files = []
+ files.each do |file|
+ # do not package the updater binary into a zip file -
+ # it must be downloaded uncompressed
+ if package_config.is_updater(file)
+ if (!updater_binary_input_path)
+ updater_binary_input_path = file
+ end
+ next
+ end
+ quoted_files << "\"#{strip_prefix_slash(strip_prefix(file,input_dir))}\""
+ ######################################################
+ #puts "\".#{strip_prefix(file,input_dir)}\""
+ #output_path = File.expand_path(input_dir)
+ #quoted_files << "#{output_path}#{strip_prefix(file,input_dir)}"
+ ######################################################
+ end
+
+ if (quoted_files.empty?)
+ puts "Skipping generation of empty package #{package}"
+ next
+ end
+
+ # sort the files in alphabetical order to ensure
+ # that if the input files for a package have the same
+ # name and content, the resulting package will have the
+ # same SHA-1
+ #
+ # This means that repeated runs of the package creation tool
+ # on the same set of files should generated packages with the
+ # same SHA-1
+ quoted_files.sort! do |a,b|
+ a <=> b
+ end
+
+ quoted_file_list = quoted_files.join(" ")
+
+ output_path = File.expand_path(output_dir)
+ output_file = "#{output_path}/#{package}.zip"
+
+ File.unlink(output_file) if File.exist?(output_file)
+#######################################
+# Dir.chdir(input_dir) do
+# puts "zip #{zip_flags} #{output_file} #{quoted_file_list}"
+#
+# if (!system("zip #{zip_flags} #{output_file} #{quoted_file_list}"))
+# raise "Failed to generate package #{package}"
+# else
+# puts "Generated package #{package} : #{file_sha1(output_file)}"
+# end
+# end
+#puts Dir.pwd #current dir
+#copy_file("zip.exe", "#{File.expand_path(input_dir)}/zip.exe")
+#copy_file("zlib1.dll", "#{File.expand_path(input_dir)}/zlib1.dll")
+####################################
+ currentDir = Dir.pwd
+ Dir.chdir(input_dir) do
+ if (!system("#{currentDir}/zip #{zip_flags} #{output_file} #{quoted_file_list}"))
+ raise "Failed to generate package #{package}"
+ else
+ puts "Generated package #{package} : #{file_sha1(output_file)}"
+ end
+ end
+end
+
+# copy the updater to the output directory
+puts "Using updater binary: #{updater_binary_input_path} and #{updater_dependency_input_path} "
+if (!updater_binary_input_path || !updater_dependency_input_path)
+ puts "Updater binary not found in input directory: #{input_dir}"
+ exit(1)
+end
+
+FileUtils.cp updater_binary_input_path, "#{output_dir}/#{File.basename(updater_binary_input_path)}", :preserve => true
+FileUtils.cp updater_dependency_input_path, "#{output_dir}/#{File.basename(updater_dependency_input_path)}", :preserve => true
+
+# output the file_list.xml file
+update_script = UpdateScriptGenerator.new(target_version,target_platform,input_dir,
+ output_dir,package_config,input_file_list, uninstall_file_list,package_file_map)
+output_xml_file = "#{output_dir}/file_list.unformatted.xml"
+File.open(output_xml_file,'w') do |file|
+ file.write update_script.to_xml()
+end
+
+# xmllint generates more readable formatted XML than REXML, so write unformatted
+# XML first and then format it with xmllint.
+system("xmllint --format #{output_xml_file} > #{output_dir}/file_list.xml")
+File.delete(output_xml_file)
+
+
diff --git a/tools/tests/iconv.dll b/tools/tests/iconv.dll
new file mode 100644
index 0000000..18c109d
Binary files /dev/null and b/tools/tests/iconv.dll differ
diff --git a/tools/tests/libbz2.dll b/tools/tests/libbz2.dll
new file mode 100644
index 0000000..2a69485
Binary files /dev/null and b/tools/tests/libbz2.dll differ
diff --git a/tools/tests/libxml2.dll b/tools/tests/libxml2.dll
new file mode 100644
index 0000000..8171d1b
Binary files /dev/null and b/tools/tests/libxml2.dll differ
diff --git a/tools/tests/test_p1.bat b/tools/tests/test_p1.bat
new file mode 100644
index 0000000..6c634a5
--- /dev/null
+++ b/tools/tests/test_p1.bat
@@ -0,0 +1 @@
+auto-create-packages.rb test_v1 test_v2 test_p1 -v 2.0.1 -o 2.0.0
\ No newline at end of file
diff --git a/tools/tests/test_p2.bat b/tools/tests/test_p2.bat
new file mode 100644
index 0000000..8153b74
--- /dev/null
+++ b/tools/tests/test_p2.bat
@@ -0,0 +1 @@
+auto-create-packages.rb test_v2 test_v3 test_p2 -v 2.0.2 -o 2.0.1
\ No newline at end of file
diff --git a/tools/tests/test_p3.bat b/tools/tests/test_p3.bat
new file mode 100644
index 0000000..adcd79a
--- /dev/null
+++ b/tools/tests/test_p3.bat
@@ -0,0 +1 @@
+auto-create-packages.rb test_v3 test_v4 test_p3 -v 2.0.3 -o 2.0.2
\ No newline at end of file
diff --git a/tools/tests/test_v1/test0.txt b/tools/tests/test_v1/test0.txt
new file mode 100644
index 0000000..fa76637
--- /dev/null
+++ b/tools/tests/test_v1/test0.txt
@@ -0,0 +1 @@
+test file 0
\ No newline at end of file
diff --git a/tools/tests/test_v1/test1.txt b/tools/tests/test_v1/test1.txt
new file mode 100644
index 0000000..337dfff
--- /dev/null
+++ b/tools/tests/test_v1/test1.txt
@@ -0,0 +1 @@
+test file 1
\ No newline at end of file
diff --git a/tools/tests/test_v2/test0.txt b/tools/tests/test_v2/test0.txt
new file mode 100644
index 0000000..fa76637
--- /dev/null
+++ b/tools/tests/test_v2/test0.txt
@@ -0,0 +1 @@
+test file 0
\ No newline at end of file
diff --git a/tools/tests/test_v2/test1.txt b/tools/tests/test_v2/test1.txt
new file mode 100644
index 0000000..337dfff
--- /dev/null
+++ b/tools/tests/test_v2/test1.txt
@@ -0,0 +1 @@
+test file 1
\ No newline at end of file
diff --git a/tools/tests/test_v2/test3.txt b/tools/tests/test_v2/test3.txt
new file mode 100644
index 0000000..1fdd994
--- /dev/null
+++ b/tools/tests/test_v2/test3.txt
@@ -0,0 +1 @@
+test file 3
\ No newline at end of file
diff --git a/tools/tests/test_v3/test0.txt b/tools/tests/test_v3/test0.txt
new file mode 100644
index 0000000..fa76637
--- /dev/null
+++ b/tools/tests/test_v3/test0.txt
@@ -0,0 +1 @@
+test file 0
\ No newline at end of file
diff --git a/tools/tests/test_v3/test1.txt b/tools/tests/test_v3/test1.txt
new file mode 100644
index 0000000..337dfff
--- /dev/null
+++ b/tools/tests/test_v3/test1.txt
@@ -0,0 +1 @@
+test file 1
\ No newline at end of file
diff --git a/tools/tests/test_v3/test3.txt b/tools/tests/test_v3/test3.txt
new file mode 100644
index 0000000..ef9cf10
--- /dev/null
+++ b/tools/tests/test_v3/test3.txt
@@ -0,0 +1 @@
+test file 3 - modified
\ No newline at end of file
diff --git a/tools/tests/test_v3/test4.txt b/tools/tests/test_v3/test4.txt
new file mode 100644
index 0000000..947aec5
--- /dev/null
+++ b/tools/tests/test_v3/test4.txt
@@ -0,0 +1 @@
+test file 4
\ No newline at end of file
diff --git a/tools/tests/test_v3/test5.txt b/tools/tests/test_v3/test5.txt
new file mode 100644
index 0000000..90619bf
--- /dev/null
+++ b/tools/tests/test_v3/test5.txt
@@ -0,0 +1 @@
+test file 5
\ No newline at end of file
diff --git a/tools/tests/test_v4/test0.txt b/tools/tests/test_v4/test0.txt
new file mode 100644
index 0000000..fa76637
--- /dev/null
+++ b/tools/tests/test_v4/test0.txt
@@ -0,0 +1 @@
+test file 0
\ No newline at end of file
diff --git a/tools/tests/test_v4/test1.txt b/tools/tests/test_v4/test1.txt
new file mode 100644
index 0000000..337dfff
--- /dev/null
+++ b/tools/tests/test_v4/test1.txt
@@ -0,0 +1 @@
+test file 1
\ No newline at end of file
diff --git a/tools/tests/test_v4/test3.txt b/tools/tests/test_v4/test3.txt
new file mode 100644
index 0000000..5f6dfd5
--- /dev/null
+++ b/tools/tests/test_v4/test3.txt
@@ -0,0 +1 @@
+test file 3 - modified 2
\ No newline at end of file
diff --git a/tools/tests/updater.exe b/tools/tests/updater.exe
new file mode 100644
index 0000000..11fb4a9
Binary files /dev/null and b/tools/tests/updater.exe differ
diff --git a/tools/tests/xmlcatalog.exe b/tools/tests/xmlcatalog.exe
new file mode 100644
index 0000000..e256289
Binary files /dev/null and b/tools/tests/xmlcatalog.exe differ
diff --git a/tools/tests/xmllint.exe b/tools/tests/xmllint.exe
new file mode 100644
index 0000000..1cc18e6
Binary files /dev/null and b/tools/tests/xmllint.exe differ
diff --git a/tools/tests/zip.exe b/tools/tests/zip.exe
new file mode 100644
index 0000000..53bd3bc
Binary files /dev/null and b/tools/tests/zip.exe differ