bc3 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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