diff --git a/bin/xtminfo b/bin/xtminfo index 09f6364..230ed2c 100755 --- a/bin/xtminfo +++ b/bin/xtminfo @@ -1,84 +1,82 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby require 'optparse' +require 'xtmfile/header' -require 'rubygems' -require 'bindata' +module XtmFile + class XtmInfo + class XtmInfoArgumentError < ArgumentError ; end -require 'xtmfile/xtmheader' + attr_reader :opts -class XtmInfo - class XtmInfoArgumentError < ArgumentError ; end - - attr_reader :opts - - - def initialize args - @args = [] - @input_xtm = nil - parse_command_line args - end - - def parse_command_line args - @args = args.clone - @opts = OptionParser.new do |opts| - opts.banner = "Usage: #{File.basename $0} [options]\n" - - opts.separator "" - opts.separator "Mandatory options" - - opts.on("-i", "--input FILE", "Input XTM file") do |r| - @input_xtm = r - end - - opts.separator "" - opts.separator "General options:" - - opts.on("-h", "--help", "Show this help") do |h| - @help = h - end - opts.on("-v", "--verbose", "Show warnings too") do |v| - @verbose = v - end - opts.separator "" + def initialize args + @args = [] + @input_xtm = nil + parse_command_line args end - end - def validate! - @opts.parse! + def parse_command_line args + @args = args.clone + @opts = OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename $0} [options]\n" - raise XtmInfoArgumentError, "No input XTM file specified!" if @input_xtm.nil? - end + opts.separator "" + opts.separator "Mandatory options" + opts.on("-i", "--input FILE", "Input XTM file") do |r| + @input_xtm = r + end - def run - validate! + opts.separator "" + opts.separator "General options:" - File.open( @input_xtm, "rb" ) do |io| - header = XtmHeader::read io - puts header + opts.on("-h", "--help", "Show this help") do |h| + @help = h + end + opts.on("-v", "--verbose", "Show warnings too") do |v| + @verbose = v + end + opts.separator "" + end end - end - def self.main args - xj = nil - begin - xj = XtmInfo.new args - xj.run - exit 0 - rescue XtmInfoArgumentError => e - STDERR.puts "%s" % xj.opts - STDERR.puts "error: %s" % e.message + def validate! + @opts.parse! - exit 1 - rescue SystemExit => e - raise e - rescue Exception => e - STDERR.puts "error: %s" % e.message - exit 1 + raise XtmInfoArgumentError, "No input XTM file specified!" if @input_xtm.nil? + end + + + def run + validate! + + File.open( @input_xtm, "rb" ) do |io| + header = Header::read io + puts header.to_summary_string + end + end + + def self.main args + xj = nil + begin + xj = XtmInfo.new args + xj.run + exit 0 + rescue XtmInfoArgumentError => e + STDERR.puts "%s" % xj.opts + STDERR.puts "error: %s" % e.message + + exit 1 + rescue SystemExit => e + raise e + rescue Exception => e + STDERR.puts "error: %s" % e.message + STDERR.puts e.backtrace + exit 1 + end end end end -XtmInfo.main ARGV +XtmFile::XtmInfo.main ARGV diff --git a/bin/xtmjoin b/bin/xtmjoin index 9f20c10..9ca02d1 100755 --- a/bin/xtmjoin +++ b/bin/xtmjoin @@ -5,15 +5,15 @@ require 'optparse' require 'rubygems' require 'bindata' -require 'xtmfile/xtmheader' - +require 'xtmfile/header' +require 'xtmfile/joiner' +require 'xtmfile/splitter' class XtmJoin class XtmJoinArgumentError < ArgumentError ; end attr_reader :opts - BUFFER_MAX_SIZE = 4096 * 4096 def initialize args @args = [] @@ -27,18 +27,24 @@ class XtmJoin opts.banner = "Usage: #{File.basename $0} [options]\n" opts.separator "" - opts.separator "Mandatory options" + opts.separator "Options" - opts.on("-i", "--input FILE", "Input XTM file") do |r| + opts.on("-i", "--input FILE", "Input XTM file (mandatory)") do |r| @input_filename = r end - opts.separator "" - opts.separator "General options:" + opts.on("-o", "--output FILE", "Output file (optional)") do |o| + @output_filename = o + end + + opts.on("-m", "--md5", "Verify MD5 sums") do |m| + @verify_md5 = m + end opts.on("-h", "--help", "Show this help") do |h| @help = h end + opts.on("-v", "--verbose", "Show warnings too") do |v| @verbose = v end @@ -56,73 +62,25 @@ class XtmJoin @input_filename = File.expand_path @input_filename end - def run validate! - - output_file = nil - # initial file - - in_xtm = File.open @input_filename, "rb" - header = XtmHeader::read in_xtm - - output_file = header.filename_str - puts "Writing data to %s" % output_file - + # FIXME: prevent overwriting - out_xtm = File.open output_file, "wb" + + xtmj = XtmFile::Joiner.new @input_filename, @output_filename + puts "Writing data to %s" % xtmj.output_filename - cur_xtm = @input_filename - is_first = true - cur_size = header.filesize - print "\x1b[s" - while cur_size > 0 do - unless is_first then - cur_xtm = _nextfile cur_xtm - puts "Opening %s" % cur_xtm - in_xtm = File.open cur_xtm, "rb" - end - while cur_size > 0 and (not in_xtm.eof?) do - pcent = ((header.filesize - cur_size) * 100 / header.filesize) - - STDOUT.print "\x1b[uProgress : %s %" % pcent + xtmj.start do |event, info| + case event + when XtmFile::Joiner::EVENT_OPENFILE then + puts "Opening %s" % info + when XtmFile::Joiner::EVENT_PROGRESS then + print "\x1b[uProgress : %s %" % info STDOUT.flush - - read_size = if cur_size > BUFFER_MAX_SIZE then BUFFER_MAX_SIZE - else cur_size - end - buffer = in_xtm.read read_size - cur_size = cur_size - buffer.length - out_xtm.write buffer.slice(0, buffer.length) end - is_first = false - in_xtm.close end - out_xtm.close - STDOUT.puts "" - # remaining files - end - - def _nextfile curfile - result = nil - cur_idx = 0 - cur_len = 0 - case curfile - when /\.([0-9]+)\.xtm$/ then - cur_idx = $1.to_i - cur_len = $1.length - next_idx = cur_idx + 1 - result = curfile.gsub(/\.([0-9]+)\.xtm$/, ".%0#{cur_len}d.xtm" % next_idx) - when /\.xtm\.([0-9]+)$/ then - cur_idx = $1.to_i - cur_len = $1.length - next_idx = cur_idx + 1 - result = curfile.gsub(/\.xtm\.([0-9]+)$/, ".xtm.%0#{cur_len}d" % next_idx) - else - raise "Unable to detect a naming patterng for file sequence!" - end - return result + STDOUT.puts "" end def self.main args diff --git a/xtmfile/header.rb b/xtmfile/header.rb new file mode 100644 index 0000000..35b1825 --- /dev/null +++ b/xtmfile/header.rb @@ -0,0 +1,91 @@ + +require 'rubygems' +gem 'bindata', '~> 1.2.1' +require 'bindata' + +module XtmFile + + # + # Offset | Information | Size (bytes) + # -------------------------------------------- + # 0 | Software name length | 1 + # 1 | Software name | 20 + # 21 | Version length | 1 + # 22 | Version | 4 + # 26 | Not used | 10 + # 36 | Date | 4 + # 40 | Filename length | 1 + # 41 | Filename | 50 + # 91 | MD5 Enable | 1 + # 92 | File parts count | 4 + # 96 | Original file size | 8 + # + class Header < BinData::Record + endian :little + + uint8 :progname_len, :value => lambda { progname_str.length } + string :progname_str, :length => 20 + + uint8 :version_len, :value => lambda { version_str.length } + string :version_str, :length => 4 + + string :unused, :length => 10 + + uint32le :date + + uint8 :filename_len + string :filename_str, :read_length => :filename_len + # fake padding + string :filename_str_pad, :read_length => lambda { 50 - filename_len } + + uint8 :md5 + + uint32 :filecount + uint64 :filesize + + def to_full_string + r = [] + r << "[%03d] Progname len : %s" % [self.progname_len.offset, self.progname_len] + r << "[%03d] Progname str : %s" % [self.progname_str.offset, self.progname_str] + r << "[%03d] Version len : %s" % [self.version_len.offset, self.version_len] + r << "[%03d] Version str : %s" % [self.version_str.offset, self.version_str] + r << "[%03d] Unused : %s" % [self.unused.offset, self.unused] + r << "[%03d] Date : %s" % [self.date.offset, self.date] + r << "[%03d] Filename len : %s" % [self.filename_len.offset, self.filename_len] + r << "[%03d] Filename str : %s" % [self.filename_str.offset, self.filename_str] + r << "[%03d] md5 : %s" % [self.md5.offset, self.md5] + r << "[%03d] Filecount : %d" % [self.filecount.offset, self.filecount] + r << "[%03d] Filesize : %s" % [self.filesize.offset, self.filesize] + return r.join("\n") + end + + + def to_summary_string + r = [] + r << " Software : %s (%s)" % [self.progname_str,self.version_str] + r << " Date : %s" % self.date + r << "Filename str : %s" % self.filename_str + r << " md5 : %s" % ( self.md5 ? 'enabled' : 'disabled' ) + r << " Filecount : %d" % self.filecount + r << " Filesize : %s" % _readable_file_size( self.filesize, 2 ) + return r.join("\n") + end + + private + + GIGA_SIZE = 1073741824.0 + MEGA_SIZE = 1048576.0 + KILO_SIZE = 1024.0 + def _readable_file_size size, precision + + case + when size == 1 : "1 Byte" + when size < KILO_SIZE : "%d Bytes" % size + when size < MEGA_SIZE : "%.#{precision}f KB" % (size / KILO_SIZE) + when size < GIGA_SIZE : "%.#{precision}f MB" % (size / MEGA_SIZE) + else "%.#{precision}f GB" % (size / GIGA_SIZE) + end + end + + end +end diff --git a/xtmfile/joiner.rb b/xtmfile/joiner.rb new file mode 100644 index 0000000..a7bfa2a --- /dev/null +++ b/xtmfile/joiner.rb @@ -0,0 +1,89 @@ + +require 'xtmfile/header' + +module XtmFile + + class Joiner + + attr_reader :header, :input_filename, :output_filename + + # yield this event + filename when opening a new file + EVENT_OPENFILE = :file + # yield this event + percent on joining progress + EVENT_PROGRESS = :progress + + BUFFER_MAX_SIZE = 4096 * 4096 + + def initialize input, output=nil + @input_filename = input + @output_filename = output + + File.open @input_filename, "rb" do |in_xtm| + @header = Header::read in_xtm + end + if @output_filename.nil? then + @output_filename = @header.filename_str + end + end + + def start &blk + in_xtm = File.open @input_filename, "rb" + in_xtm.seek @header.num_bytes, IO::SEEK_SET + + yield EVENT_OPENFILE, @input_filename if block_given? + + + out_xtm = File.open @output_filename, "wb" + + cur_xtm = @input_filename + is_first = true + cur_size = @header.filesize + print "\x1b[s" + while cur_size > 0 do + unless is_first then + cur_xtm = _nextfile cur_xtm + yield EVENT_OPENFILE, cur_xtm if block_given? + in_xtm = File.open cur_xtm, "rb" + end + while cur_size > 0 and (not in_xtm.eof?) do + pcent = (( @header.filesize - cur_size) * 100 / @header.filesize) + yield EVENT_PROGRESS, pcent if block_given? + + read_size = if cur_size > BUFFER_MAX_SIZE then BUFFER_MAX_SIZE + else cur_size + end + buffer = in_xtm.read read_size + cur_size = cur_size - buffer.length + out_xtm.write buffer.slice(0, buffer.length) + end + is_first = false + in_xtm.close + end + out_xtm.close + + # remaining files + end + + def _nextfile curfile + result = nil + cur_idx = 0 + cur_len = 0 + case curfile + when /\.([0-9]+)\.xtm$/ then + cur_idx = $1.to_i + cur_len = $1.length + next_idx = cur_idx + 1 + result = curfile.gsub(/\.([0-9]+)\.xtm$/, ".%0#{cur_len}d.xtm" % next_idx) + when /\.xtm\.([0-9]+)$/ then + cur_idx = $1.to_i + cur_len = $1.length + next_idx = cur_idx + 1 + result = curfile.gsub(/\.xtm\.([0-9]+)$/, ".xtm.%0#{cur_len}d" % next_idx) + else + raise "Unable to detect a naming patterng for file sequence!" + end + return result + end + end +end + diff --git a/xtmfile/splitter.rb b/xtmfile/splitter.rb new file mode 100644 index 0000000..9030105 --- /dev/null +++ b/xtmfile/splitter.rb @@ -0,0 +1,19 @@ +module XtmFile +class Splitter +attr_reader :parts, :parts_size +def initialize input_filename +@input_filename = input_filename +@parts = 0 +@parts_size = 0 +end + +def parts= count +end + +def parts_size= size +end + +def start +end +end +end diff --git a/xtmfile/xtmheader.rb b/xtmfile/xtmheader.rb deleted file mode 100644 index 0f00b2a..0000000 --- a/xtmfile/xtmheader.rb +++ /dev/null @@ -1,52 +0,0 @@ - -class XtmHeader < BinData::Record - # Offset Information Taille (octets) - # 0 Longueur du nom du programme 1 - # 1 *Nom du programme 20 - # 21 Longueur de la version 1 - # 22 Version 4 - # 26 Not used 10 - # 36 Date 4 - # 40 Longueur du nom du fichier original 1 - # 41 Nom du fichier original 50 - # 91 **Hash MD5 1 - # 92 Nombre de fichiers 4 - # 96 Taille du fichier original 8 - - endian :little - - uint8 :progname_len, :value => lambda { progname_str.length } - string :progname_str, :length => 20 - - uint8 :version_len, :value => lambda { version_str.length } - string :version_str, :length => 4 - - string :unused, :length => 10 - - uint32le :date - - uint8 :filename_len - string :filename_str, :read_length => :filename_len - string :filename_str_pad, :read_length => lambda { 50 - filename_len } - - uint8 :md5 - - uint32 :filecount - uint64 :filesize - - def to_s - r = [] - r << ( "Progname len : %s [%s]" % [self.progname_len, self.progname_len.offset] ) - r << ( "Progname str : %s [%s]" % [self.progname_str, self.progname_str.offset] ) - r << ( " Version len : %s [%s]" % [self.version_len, self.version_len.offset] ) - r << ( " Version str : %s [%s]" % [self.version_str, self.version_str.offset] ) - r << ( " Unused : %s [%s]" % [self.unused, self.unused.offset] ) - r << ( " Date : %s [%s]" % [self.date, self.date.offset] ) - r << ( "Filename len : %s [%s]" % [self.filename_len, self.filename_len.offset] ) - r << ( "Filename str : %s [%s]" % [self.filename_str, self.filename_str.offset] ) - r << ( " md5 : %s [%s]" % [self.md5, self.md5.offset] ) - r << ( " Filecount : %d [%s]" % [self.filecount, self.filecount.offset] ) - r << ( " Filesize : %s [%s]" % [self.filesize, self.filesize.offset] ) - return r.join("\n") - end -end