bc3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bc3/helper.rb ADDED
@@ -0,0 +1,101 @@
1
+ module BC3
2
+ =begin rdoc
3
+ Define DOS-Attributs.
4
+
5
+ http://www.xxcopy.com/xxcopy06.htm
6
+ Bit 0 Read-Only
7
+ Bit 1 Hidden
8
+ Bit 2 System
9
+ Bit 3 Volume Label
10
+ Bit 4 Directory
11
+ Bit 5 Archive
12
+
13
+ http://www.computerhope.com/attribhl.htm
14
+ Bit Positions Hex Description
15
+ 0 0 0 0 0 0 0 1 01h Read Only file
16
+ 0 0 0 0 0 0 1 0 02h Hidden file
17
+ 0 0 0 0 0 1 0 0 04h System file
18
+ 0 0 0 0 1 0 0 0 08h Volume Label
19
+ 0 0 0 1 0 0 0 0 10h Subdirectory
20
+ 0 0 1 0 0 0 0 0 20h Archive
21
+ 0 1 0 0 0 0 0 0 40h Reserved
22
+ 1 0 0 0 0 0 0 0 80h Reserved
23
+
24
+ Usage:
25
+ attrib = Attrib::ReadOnly | Attrib::Hidden
26
+ =end
27
+ module Attrib
28
+ ReadOnly = 1
29
+ Hidden = 2
30
+ System = 4
31
+ VolumeLabel = 8
32
+ Directory = 16
33
+ Archive = 32
34
+ end
35
+
36
+ =begin rdoc
37
+ Helper methods to be included to File and Folder.
38
+ =end
39
+ module Helper
40
+ =begin rdoc
41
+ Calculate the CRC32 for a string.
42
+
43
+ Based on http://www.ruby-forum.com/topic/179452
44
+ (with minor ruby 1.9 adaptions).
45
+ =end
46
+ def self.crc32(c)
47
+ #Alternative solution:
48
+ #~ require 'zlib'
49
+ #~ return Zlib.crc32(c, 0)
50
+
51
+ #Encoding of c should be binary or similar.
52
+ n = c.length
53
+ r = 0xFFFFFFFF
54
+ c.each_byte do |i|
55
+ r ^= i
56
+ 8.times do
57
+ if (r & 1)!=0
58
+ r = (r>>1) ^ 0xEDB88320
59
+ else
60
+ r >>= 1
61
+ end
62
+ end
63
+ end
64
+ r ^ 0xFFFFFFFF
65
+ end
66
+
67
+ =begin rdoc
68
+ Return a Number (e.g. AD epoch) as a binary string with 8 bytes in little endian order.
69
+
70
+ With
71
+ String#<< (Integer)
72
+ the integer is a codepoint, the character is added.
73
+ AD's epoch isn't a codepoint, but a binary data.
74
+ So we need a little conversion, to get
75
+ AD's epoch (Bignum) as an bit sequence.
76
+ =end
77
+ def fixnum2int64( int )
78
+ bindata = ''.force_encoding('BINARY')
79
+ #Ugly code, but it works ;-)
80
+ #.reverse to get "little endian"
81
+ ('%064b' % int).scan(/(\d{8})/).flatten.reverse.each{|b|
82
+ bindata << b.to_i(2)
83
+ }
84
+ raise ArgumentError unless bindata.size == 8 #int was too big
85
+ bindata
86
+ end
87
+
88
+ =begin rdoc
89
+ Same as Helper#fixnum2int64, but as 4 bytes string.
90
+ =end
91
+ def fixnum2int32( int )
92
+ bindata = ''.force_encoding('BINARY')
93
+ ('%032b' % int).scan(/(\d{8})/).flatten.reverse.each{|b|
94
+ bindata << b.to_i(2)
95
+ }
96
+ raise ArgumentError unless bindata.size == 4 #int was too big
97
+ bindata
98
+ end
99
+
100
+ end #Helper
101
+ end #BC3
data/lib/bc3/parse.rb ADDED
@@ -0,0 +1,312 @@
1
+ =begin rdoc
2
+
3
+ =end
4
+ $:.unshift('..')
5
+ require 'bc3'
6
+
7
+ require "zlib"
8
+ module BC3
9
+ =begin rdoc
10
+ Parser for a given bcss-file.
11
+
12
+ =end
13
+ class SnapshotParser
14
+ =begin rdoc
15
+
16
+ =end
17
+ def initialize( filename )
18
+ rawdata = nil
19
+ @log = $log #fixme replace with sublogger
20
+ @log.info("Read and parse #{filename}")
21
+ ::File.open( filename, 'rb' ){|f|
22
+ rawdata = f.read()
23
+ }
24
+
25
+ =begin
26
+ - HEADER STRUCTURE -
27
+ [0..3] = 'BCSS'
28
+ [4] = Major version (UByte)
29
+ [5] = Minor version (UByte)
30
+ [6] = Minimum Supported Major Version (UByte)
31
+ [7] = Minimum Supported Minor Version (UByte)
32
+ [8..F] = Creation Time (FileTime)
33
+ [10..11] = Flags (UWord)
34
+
35
+ Bit : Meaning
36
+ 0 : Compressed
37
+ 1 : Source Path included
38
+ 2 : Reserved
39
+ 3 : UTF-8
40
+ 4-15 : Reserved
41
+
42
+ [12..13] = Path Length (UWord) | Optional
43
+ [14..N] = Path (char[]) |
44
+ =end
45
+ #~ header = rawdata[0..17]
46
+ @timestamp = Time.now
47
+ @timestamp, tail = parse_filetime(rawdata[8,8])
48
+ #Analyse flags - byte position 16/hex10
49
+ @compressed = rawdata[16].getbyte(0) & 1 != 0
50
+ @sourcepath = rawdata[16].getbyte(0) & 2 != 0
51
+ @reserved = rawdata[16].getbyte(0) & 4 != 0
52
+ @utf = rawdata[16].getbyte(0) & 8 != 0
53
+ if rawdata[17] != "\x0"
54
+ @log.warn("2nd flag byte is filled")
55
+ end
56
+
57
+ #Analyse Source path
58
+
59
+ #Delete second length parameter for source path
60
+ if rawdata.slice!(19) != "\x0"
61
+ @log.warn("Path > 255 not supported")
62
+ raise "Path > 255 not supported"
63
+ end
64
+ path, body = parse_shortstring(rawdata[18..-1])
65
+ if @compressed
66
+ =begin
67
+ Flags:
68
+ Compressed: If set everything following the header is compressed as a raw
69
+ deflate stream, as defined by RFC 1951. It is the same compression used by
70
+ .zip and .gz archives.
71
+
72
+ Code from http://www.ruby-forum.com/topic/136825
73
+ =end
74
+ @log.debug("uncompress body data")
75
+ begin
76
+ body= Zlib::Inflate.inflate(body); #Unclear problem
77
+ rescue Zlib::DataError
78
+ @log.debug("Zlib::DataError occured - try with raw deflate")
79
+ #no luck with Zlib decompression. Let's try with raw deflate,
80
+ #like some broken browsers do.
81
+ body= Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
82
+ end
83
+ end
84
+
85
+ @snapshot = Snapshot.new(path, @timestamp)
86
+
87
+ parse_body(body)
88
+ end #initialize
89
+ #Snapshot-object, result of the parsing.
90
+ attr_reader :snapshot
91
+ attr_reader :timestamp
92
+
93
+ =begin
94
+ Parse the body data.
95
+
96
+ This method will change the given parameter.
97
+ =end
98
+ def parse_body(body)
99
+ folderstack = [ @snapshot ]
100
+ while ! body.empty?
101
+ =begin
102
+ Each record starts with a single UByte ID value and then the data defined below.
103
+ =end
104
+ case last_flag = body.slice!(0)
105
+ =begin
106
+ ID_DIRECTORY (0x01)
107
+ Represents a directory on the system, or an expanded archive file.
108
+
109
+ Name : ShortString
110
+ Last Modified : FileTime
111
+ DOS Attributes : UInt32
112
+ =end
113
+ when "\x01" #folder
114
+ dirname, tail = parse_shortstring(body)
115
+ filetime, tail = parse_filetime(tail)
116
+ attributes, tail = parse_dosattributes(tail)
117
+ folder = Folder.newh(
118
+ dirname: dirname,
119
+ timestamp: filetime,
120
+ attributes: attributes
121
+ )
122
+ folderstack.last << folder
123
+ folderstack << folder
124
+ =begin
125
+ ID_FILE (0x02)
126
+ Represents a file on the system.
127
+
128
+ Name : ShortString
129
+ Last Modified : FileTime
130
+ DOS Attributes : UInt32
131
+ Size : Int32[+Int64]
132
+ If Size > 2GB, store as Int32(-1) followed by Int64
133
+ CRC32 : UInt32
134
+ =end
135
+ when "\x02" #file
136
+ filename, tail = parse_shortstring(body)
137
+ filetime, tail = parse_filetime(tail)
138
+ attributes, tail = parse_dosattributes(tail)
139
+ filesize, tail = parse_uint32(tail)
140
+ crc32, tail = parse_uint32(tail)
141
+ folderstack.last << File.new(
142
+ filename: filename,
143
+ timestamp: filetime,
144
+ attributes: attributes,
145
+ filesize: filesize,
146
+ crc: crc32
147
+ )
148
+ =begin
149
+ ID_FILE_EX (0x03)
150
+ Represents a file on the system, with extended headers.
151
+
152
+ Name..CRC32 is the same as ID_FILE
153
+ ExtraLen : UInt16
154
+ ExtraData : Byte[ExtraLen]
155
+
156
+ =end
157
+ when "\x03" #file
158
+ filename, tail = parse_shortstring(body)
159
+ filetime, tail = parse_filetime(tail)
160
+ attributes, tail = parse_dosattributes(tail)
161
+ filesize, tail = parse_uint32(tail)
162
+ crc32, tail = parse_uint32(tail)
163
+ extradata, tail = parse_longstring(tail)
164
+ extradata = parse_file_extended_headers(extradata)
165
+ unless extradata #Skip at prob
166
+ @log.warn("Skip #{filename}")
167
+ next
168
+ end
169
+ folderstack.last << File.new({
170
+ filename: filename,
171
+ timestamp: filetime,
172
+ attributes: attributes,
173
+ filesize: filesize,
174
+ crc: crc32,
175
+ }.merge(extradata)
176
+ )
177
+ =begin
178
+ ID_DIRECTORY_END (0xFF)
179
+ Represents the end of a directory listing. No data.
180
+ =end
181
+ when "\xff" #end of folder
182
+ folderstack.pop
183
+ else
184
+ @log.fatal("Undefined body-parse element #{last_flag.inspect}")
185
+ p body
186
+ body.slice!(0..-1) #close further pasring
187
+ end
188
+ end
189
+ if folderstack.size > 1
190
+ @log.error("Folders in Folderstack not closed correct")
191
+ p folderstack.size
192
+ end
193
+ end
194
+ =begin rdoc
195
+ Get a "shortstring".
196
+
197
+ 1 Byte with length, then the string.
198
+
199
+ Return shortstring and rest of string.
200
+ =end
201
+ def parse_shortstring( string )
202
+ #Get length of path
203
+ pathsize = string.slice!(0).bytes.first
204
+ # + rawdata[19].bytes.first * 255 #--test it
205
+ return [string.slice!(0,pathsize), string]
206
+ end
207
+ =begin rdoc
208
+ Get a "longstring".
209
+
210
+ 2 Bytes with length, then the string.
211
+ The length is including the 2 bytes for the length
212
+
213
+ Return longstring and rest of string.
214
+ =end
215
+ def parse_longstring( string )
216
+ stringsize = string.slice!(0).bytes.first - 2
217
+ if string.slice!(0) != "\x0"
218
+ @log.warn("longstring > 255 not supported")
219
+ raise "longstring > 255 not supported"
220
+ end
221
+ return [string.slice!(0,stringsize), string]
222
+ end
223
+ =begin rdoc
224
+ Get Unsigned 32-bit number
225
+ =end
226
+ def parse_uint32( string )
227
+ num = string.slice!(0,4).reverse.each_byte.map{|x| '%08b' % x}.join.to_i(2)
228
+ return [num, string]
229
+ end
230
+
231
+ =begin rdoc
232
+ Get a "filetime".
233
+
234
+ FileTime:
235
+ Windows FILETIME structure. 64-bit value representing the number of
236
+ 100-nanosecond intervals since January 1, 1601 UTC. Stored in local time.
237
+
238
+ Return time and rest of string.
239
+ =end
240
+ def parse_filetime( string )
241
+ ad_time = string.slice!(0,8) #Integer with filetime
242
+ time = Time.ad2time(ad_time.reverse.each_byte.map{|x| '%08b' % x}.join.to_i(2))
243
+ return [time, string]
244
+ end
245
+ =begin rdoc
246
+ Get DOS-attributes.
247
+
248
+ =end
249
+ def parse_dosattributes( string )
250
+ #Get length of path
251
+ attributes = string.slice!(0).bytes.first
252
+ string.slice!(0,3) #skip next 3 bytes
253
+ return [attributes, string]
254
+ end
255
+ =begin
256
+ =====================
257
+ File Extended Headers
258
+ =====================
259
+
260
+ Like extended headers, file extended headers should be written in ascending
261
+ numeric order.
262
+
263
+ FILE_EX_VERSION (0x01)
264
+ String representation of an executable file's Major/Minor/Maint/Build
265
+ version (e.g., "2.11.28.3542").
266
+
267
+ Length : UByte
268
+ Data : char[Length]
269
+
270
+
271
+ FILE_EX_UTF8 (0x02)
272
+ UTF-8 encoded filename. Stored as a FileExString. Only used if the UTF-8
273
+ name doesn't match the ANSI encoded one or if the filename is longer than 255
274
+ characters.
275
+
276
+
277
+ FILE_EX_LINK_PATH (0x03)
278
+ UTF-8 encoded symbolic link path. Stored as a FileExString.
279
+ =end
280
+ def parse_file_extended_headers(extradata_string)
281
+ extradata = {}
282
+ case flag = extradata_string.slice!(0)
283
+ when "\x01"
284
+ extradata[:version] = parse_shortstring( extradata_string )
285
+ when "\x02"
286
+ @log.warn("Undefined extra data handling for UTF-8 encoded filename")
287
+ return false #fixme
288
+ when "\x03"
289
+ @log.warn("Undefined extra data handling for UTF-8 encoded symbolic")
290
+ return false #fixme
291
+ else
292
+ #fixme handling extradata_string
293
+ @log.warn("Undefined extra data handling #{flag.inspect} <#{extradata_string.inspect}>")
294
+ end
295
+ unless extradata_string.empty?
296
+ @log.warn("Undefined extra data handling <#{extradata_string.inspect}>")
297
+ p extradata_string
298
+ end
299
+ extradata
300
+ end
301
+ end #SnapshotParser
302
+ def to_hash; @snapshot.to_hash; end
303
+ end
304
+
305
+ if $0 == __FILE__
306
+ require 'yaml'
307
+ #~ x = BC3::SnapshotParser.new('../../examples/results/testdir_2011-01-16.bcssx' )
308
+ x = BC3::SnapshotParser.new('../../examples/results/bc3_2011-01-16.bcss' )
309
+ #~ x = BC3::SnapshotParser.new('../../Uncompressed Sample/Uncompressed Sample.bcss' )
310
+ puts x.snapshot.to_hash.to_yaml
311
+ x.snapshot.save('../../Uncompressed Sample/Uncompressed Sample_reconstructed.bcss')
312
+ end
@@ -0,0 +1,264 @@
1
+ require 'bc3/helper'
2
+ module BC3
3
+ =begin rdoc
4
+ Container for a snapshot.
5
+ =end
6
+ class Snapshot
7
+ include Helper
8
+ =begin rdoc
9
+ =end
10
+ def initialize( path, timestamp = Time.now )
11
+ $log.debug("Create Snapshot #{path}")
12
+ @path = path
13
+ @timestamp = timestamp || Time.now
14
+
15
+ $log.debug("Create base folder for snapshot #{path}")
16
+ @basefolder = Folder.new('SnapshotRoot', @timestamp)
17
+ end
18
+ =begin rdoc
19
+ Create a snapshot from a hash.
20
+
21
+ A snapsot-hash must contain:
22
+ * snapshot - dirname of the snapshot
23
+ * content - array of folders (see Folder.newh) and files (File.new)
24
+ * timestamp (optional)
25
+ =end
26
+ def self.newh( data )
27
+ $log.info("Build Snapshot from hash")
28
+ raise ArgumentError, "No hash given" unless data.is_a?(Hash)
29
+ raise ArgumentError, "snapshot name missing" unless data.has_key?(:snapshot)
30
+ raise ArgumentError, "content missing" unless data.has_key?(:content)
31
+ raise ArgumentError, "Content is no array" unless data[:content].is_a?(Array)
32
+
33
+ snapshot = new( data[:snapshot], data[:timestamp] )
34
+ data[:content].each{| element |
35
+ if element.has_key?(:dirname)
36
+ snapshot << Folder.newh(element)
37
+ elsif element.has_key?(:filename)
38
+ snapshot << File.new(element)
39
+ else
40
+ raise ArgumentError, "element without dir/filename"
41
+ end
42
+ }
43
+ snapshot
44
+ end #newh
45
+ =begin rdoc
46
+ Create a snapshot from a directory.
47
+
48
+ =end
49
+ def self.newd( dirname )
50
+ $log.info("Build Snapshot from directory #{dirname}")
51
+
52
+ #~ raise ArgumentError, "No hash given" unless data.is_a?(Hash)
53
+ snapshot = new( ::File.expand_path("./#{dirname}") )
54
+ Dir.chdir(dirname){
55
+ Dir['*'].each{|f|
56
+ if ::File.directory?(f)
57
+ snapshot << Folder.new_by_dirname(f)
58
+ elsif ::File.exist?(f)
59
+ snapshot << File.new_by_filename(f)
60
+ else
61
+ raise ArgumentError, "#{f} not found in #{dirname}"
62
+ end
63
+ }
64
+ }
65
+ snapshot
66
+ end #newh
67
+
68
+ #homepath of the snapshot
69
+ attr_reader :path
70
+ #Time stamp from snapshot. Default 'now'
71
+ attr_reader :timestamp
72
+
73
+ =begin rdoc
74
+ Add content (folders/files) to snapshot.
75
+ =end
76
+ def << (content)
77
+ @basefolder << content
78
+ end
79
+ =begin rdoc
80
+ Loop on content of the folder.
81
+
82
+ Options see BC3::Folder#each
83
+ =end
84
+ def each(*options)
85
+ if block_given?
86
+ @basefolder.each(*options){|key, content| yield key, content }
87
+ else
88
+ @basefolder.each
89
+ end
90
+ end
91
+
92
+ =begin rdoc
93
+ =end
94
+ def save( filename, compressed = nil )
95
+ $log.debug("Prepare snapshot for #{filename}")
96
+ #Check if compressed or uncompressed output wanted
97
+ compressed = ( filename =~ /\.bcssx/ ) if compressed.nil?
98
+ #Must be binary, else a \n get's \r\n under windows.
99
+ ::File.open(filename,'wb'){|f|
100
+ f << bcss( compressed )
101
+ }
102
+ $log.info("Saved snapshot as #{filename}")
103
+ end
104
+ =begin rdoc
105
+ Collect the data in a hash.
106
+
107
+ Usefull in combination with yaml:
108
+ require 'bc3'
109
+ require 'yaml'
110
+ #...
111
+ snapshot = snapshot.new(...)
112
+ #...
113
+ puts snapshot.to_hash.to_yaml
114
+ =end
115
+ def to_hash()
116
+ {
117
+ snapshot: @path,
118
+ timestamp: @timestamp,
119
+ content: @basefolder.each.values.map{| x | x.to_hash }
120
+ }
121
+ end
122
+ =begin rdoc
123
+ Prepare a snapshot (bcss-file).
124
+
125
+ Only uncompressed structure.
126
+
127
+ ===Beyond Compare Snapshot Format Version 1.1
128
+ Beyond Compare snapshots (.bcss) are binary files containing the file metadata
129
+ (names, sizes, last modified times) of a directory structure without storing
130
+ any of the file content. They are designed to be read sequentially. File
131
+ record sizes are variable, so there's no way to seek to arbitrary records
132
+ without reading all of the records before it.
133
+
134
+ =end
135
+ def bcss( compressed = false )
136
+ bcss = "".force_encoding('BINARY')
137
+ bcss << bcss_header( compressed )
138
+ if compressed
139
+ $log.debug("Compress bcss-data")
140
+ $log.fatal("Compress bcss-data not supported - only for test purposes")
141
+ =begin
142
+ Flags:
143
+ Compressed: If set everything following the header is compressed as a raw
144
+ deflate stream, as defined by RFC 1951. It is the same compression used by
145
+ .zip and .gz archives.
146
+ =end
147
+ #see for truncations http://www.ruby-forum.com/topic/101078
148
+ # http://ilovett.com/blog/programming/ruby-zlib-deflate
149
+ #~ puts "%-2i %s" % [ 99, bcss_data.inspect ]
150
+ -1.upto(9){|i|
151
+ puts "%-2i %s" % [ i, Zlib::Deflate.deflate( bcss_data, i )[2..-5].inspect ]
152
+ }
153
+ bcss << Zlib::Deflate.deflate( bcss_data)[2..-5]
154
+ #~ bcss << Zlib::Deflate.new(nil, -Zlib::MAX_WBITS).deflate(bcss_data, Zlib::FINISH)
155
+ else #uncompressed
156
+ bcss << bcss_data
157
+ end
158
+ bcss << 255
159
+
160
+ bcss
161
+ end
162
+
163
+ =begin rdoc
164
+ Create the header data for bcss-file
165
+
166
+ Snapshots start with a fixed size header that contains an ID value, version
167
+ information, a creation date, and various flags, optionally followed by the
168
+ source folder's path:
169
+
170
+ - HEADER STRUCTURE -
171
+ [0..3] = 'BCSS'
172
+ [4] = Major version (UByte)
173
+ [5] = Minor version (UByte)
174
+ [6] = Minimum Supported Major Version (UByte)
175
+ [7] = Minimum Supported Minor Version (UByte)
176
+ [8..F] = Creation Time (FileTime)
177
+ [10..11] = Flags (UWord)
178
+
179
+ Bit : Meaning
180
+ 0 : Compressed
181
+ 1 : Source Path included
182
+ 2 : Reserved
183
+ 3 : UTF-8
184
+ 4-15 : Reserved
185
+
186
+ [12..13] = Path Length (UWord) | Optional
187
+ [14..N] = Path (char[]) |
188
+
189
+ Version Information:
190
+ The first two version bytes represent the actual major and minor versions
191
+ of the file, and reference a specific version of this specification. The
192
+ second pair of version bytes represent the minimum snapshot version which must
193
+ be supported in order to read the snapshot file. Version 1.1 can be read by
194
+ Version 1.0 applications, so currently Major/Minor should be set to 1.1 and
195
+ Minimum should be 1.0.
196
+
197
+ Flags:
198
+ Compressed: If set everything following the header is compressed as a raw
199
+ deflate stream, as defined by RFC 1951. It is the same compression used by
200
+ .zip and .gz archives.
201
+
202
+ Source Path included: If set the original folder's path is included
203
+ immediately after the header. This is only on part of the file besides the
204
+ fixed header that is not compressed.
205
+
206
+ UTF-8: If set the snapshot was compressed on a system where the default
207
+ character encoding is UTF-8 (Linux, OS X). Filenames, paths, and link targets
208
+ will all be stored as UTF-8. If this isn't set the paths are stored using the
209
+ original OS's ANSI codepage (Windows). In that case any paths may be stored a
210
+ second time as UTF-8 in extended headers.
211
+
212
+ =end
213
+ def bcss_header( compressed )
214
+ header = "".force_encoding('BINARY')
215
+ header << 'BCSS'
216
+ header << 1 #Major version (UByte)
217
+ header << 1 #Minor version (UByte)
218
+ header << 1 #Minimum Supported Major Version (UByte)
219
+ header << 0 #Minimum Supported Minor Version (UByte)
220
+
221
+ #[8..F] = Creation Time (FileTime)
222
+ #Windows FILETIME structure. 64-bit value representing the number of
223
+ #100-nanosecond intervals since January 1, 1601 UTC. Stored in local time.
224
+ #8 Byte long
225
+ #~ header << "%x" % Time.now.time2ad #-> bignum too big to convert into `unsigned long' (RangeError)
226
+ header << fixnum2int64(@timestamp.time2ad)
227
+ #~ header << "\x70\x57\x5C\x25\x69\xB2\xCB\x01" #Data from example
228
+
229
+ # [10..11] = Flags (UWord)
230
+
231
+ #~ Bit : Meaning
232
+ #~ 0 : Compressed
233
+ #~ 1 : Source Path included
234
+ #~ 2 : Reserved
235
+ #~ 3 : UTF-8
236
+ #~ 4-15 : Reserved
237
+ flag = 0 #no flag set
238
+ flag += 2 #Source Path included
239
+ flag += 1 if compressed
240
+ header << flag
241
+ header << 0
242
+
243
+ # [12..13] = Path Length (UWord) | Optional
244
+ header << @path.size
245
+ header << 0 #fixme if path > 255
246
+ raise "too long path" if @path.size > 155 #fixme
247
+ # [14..N] = Path (char[]) |
248
+ header << @path
249
+
250
+ header
251
+ end #header
252
+ =begin rdoc
253
+ Return the data part of the snapshot.
254
+ This part may be packed.
255
+ =end
256
+ def bcss_data()
257
+ data = "".force_encoding('BINARY')
258
+ @basefolder.each{|key, folder|
259
+ data << folder.bcss
260
+ }
261
+ data
262
+ end
263
+ end #Snapshot
264
+ end #module BC3