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.
@@ -0,0 +1,239 @@
1
+ -------------------------------------------------------------------------------
2
+ Beyond Compare Snapshot Format Version 1.1
3
+ -------------------------------------------------------------------------------
4
+
5
+ Beyond Compare snapshots (.bcss) are binary files containing the file metadata
6
+ (names, sizes, last modified times) of a directory structure without storing
7
+ any of the file content. They are designed to be read sequentially. File
8
+ record sizes are variable, so there's no way to seek to arbitrary records
9
+ without reading all of the records before it.
10
+
11
+
12
+ ===========
13
+ File Header
14
+ ===========
15
+
16
+ Snapshots start with a fixed size header that contains an ID value, version
17
+ information, a creation date, and various flags, optionally followed by the
18
+ source folder's path:
19
+
20
+ - HEADER STRUCTURE -
21
+ [0..3] = 'BCSS'
22
+ [4] = Major version (UByte)
23
+ [5] = Minor version (UByte)
24
+ [6] = Minimum Supported Major Version (UByte)
25
+ [7] = Minimum Supported Minor Version (UByte)
26
+ [8..F] = Creation Time (FileTime)
27
+ [10..11] = Flags (UWord)
28
+
29
+ Bit : Meaning
30
+ 0 : Compressed
31
+ 1 : Source Path included
32
+ 2 : Reserved
33
+ 3 : UTF-8
34
+ 4-15 : Reserved
35
+
36
+ [12..13] = Path Length (UWord) | Optional
37
+ [14..N] = Path (char[]) |
38
+
39
+ Version Information:
40
+ The first two version bytes represent the actual major and minor versions
41
+ of the file, and reference a specific version of this specification. The
42
+ second pair of version bytes represent the minimum snapshot version which must
43
+ be supported in order to read the snapshot file. Version 1.1 can be read by
44
+ Version 1.0 applications, so currently Major/Minor should be set to 1.1 and
45
+ Minimum should be 1.0.
46
+
47
+ Flags:
48
+ Compressed: If set everything following the header is compressed as a raw
49
+ deflate stream, as defined by RFC 1951. It is the same compression used by
50
+ .zip and .gz archives.
51
+
52
+ Source Path included: If set the original folder's path is included
53
+ immediately after the header. This is only on part of the file besides the
54
+ fixed header that is not compressed.
55
+
56
+ UTF-8: If set the snapshot was compressed on a system where the default
57
+ character encoding is UTF-8 (Linux, OS X). Filenames, paths, and link targets
58
+ will all be stored as UTF-8. If this isn't set the paths are stored using the
59
+ original OS's ANSI codepage (Windows). In that case any paths may be stored a
60
+ second time as UTF-8 in extended headers.
61
+
62
+
63
+ ==========
64
+ Data Types
65
+ ==========
66
+
67
+ UByte:
68
+ Unsigned 8-bit value
69
+
70
+ UInt16:
71
+ Unsigned 16-bit value
72
+
73
+ Int32:
74
+ Signed 32-bit value
75
+
76
+ UInt32:
77
+ Unsigned 32-bit value
78
+
79
+ Int64:
80
+ Signed 64-bit value
81
+
82
+ char[]:
83
+ Variable length single-byte character array (ANSI or UTF-8).
84
+
85
+ FileTime:
86
+ Windows FILETIME structure. 64-bit value representing the number of
87
+ 100-nanosecond intervals since January 1, 1601 UTC. Stored in local time.
88
+
89
+ ShortString:
90
+ Variable length single-byte character string (ANSI or UTF-8). Not null
91
+ terminated.
92
+
93
+ Length : UByte
94
+ Data : char[Length]
95
+
96
+ FileExString:
97
+ Variable length single-byte character string (UTF-8). See "File Extended
98
+ Header" section for details.
99
+
100
+
101
+ ===============
102
+ Content Records
103
+ ===============
104
+
105
+ Immediately after the header the directory tree is stored as a series of
106
+ records. Directories are stored recursively: each one starts with the
107
+ directory header, then any files and subdirectories (and their children), then
108
+ the directory end record.
109
+
110
+ The ID_DIRECTORY record for the outer most (source) folder is not stored, so
111
+ the content stream actually starts with the first child, and continues until
112
+ it finds an unmatched ID_END_REC record. Anything following that is currently
113
+ ignored.
114
+
115
+ Each record starts with a single UByte ID value and then the data defined below.
116
+
117
+ ID_DIRECTORY (0x01)
118
+ Represents a directory on the system, or an expanded archive file.
119
+
120
+ Name : ShortString
121
+ Last Modified : FileTime
122
+ DOS Attributes : UInt32
123
+
124
+
125
+ ID_DIRECTORY_END (0xFF)
126
+ Represents the end of a directory listing. No data.
127
+
128
+
129
+ ID_FILE (0x02)
130
+ Represents a file on the system.
131
+
132
+ Name : ShortString
133
+ Last Modified : FileTime
134
+ DOS Attributes : UInt32
135
+ Size : Int32[+Int64]
136
+ If Size > 2GB, store as Int32(-1) followed by Int64
137
+ CRC32 : UInt32
138
+
139
+
140
+ ID_FILE_EX (0x03)
141
+ Represents a file on the system, with extended headers.
142
+
143
+ Name..CRC32 is the same as ID_FILE
144
+ ExtraLen : UInt16
145
+ ExtraData : Byte[ExtraLen]
146
+
147
+
148
+ ID_EXTENDED (0x04)
149
+ Extended headers
150
+
151
+ SubType : UByte
152
+ Length : UWord
153
+ Data : Byte[Length]
154
+
155
+
156
+ ========================
157
+ Extended Header Subtypes
158
+ ========================
159
+
160
+ Extended headers should be written in ascending numeric order. Once BC sees
161
+ an extended subtype that it doesn't undertand it stops processing ID_EXTENDED
162
+ headers until it finds one of ID_DIRECTORY/ID_DIRECTORY_END/ID_FILE/ID_FILE_EX.
163
+
164
+
165
+ EX_UTF8 (0x01)
166
+ UTF-8 encoded filename for the ID_DIRECTORY that immediately preceeded
167
+ this header. The length is given in the ID_EXTENDED header and the data is a
168
+ char[].
169
+ If the .bcss header flags indicate that the data is not UTF-8 and the
170
+ source path is included this can be included as the first record in the file
171
+ in order to give a UTF-8 version of the source path.
172
+
173
+
174
+ EX_DIRECTORY_EX (0x02)
175
+ Extended directory header for the ID_DIRECTORY that immediately preceeded
176
+ this header. Data is the record below, but Length may be larger to support
177
+ future expansion.
178
+
179
+ Flags : UByte
180
+ Bit : Meaning
181
+ 0 : Error - Contents not available. Flag as a load error in BC.
182
+
183
+
184
+ EX_RESYNC (0x03)
185
+ Works around a bug in Beyond Compare's parser in versions prior to 3.2.2.
186
+ If an ID_DIRECTORY is followed by any ID_EXTENDED headers besides EX_UTF8 or
187
+ EX_DIRECTORY_EX include one copy of this header before them.
188
+
189
+ Length : UWord = 0x0001
190
+ Data : Byte[1] = 0
191
+
192
+
193
+ EX_LINK_PATH (0x04)
194
+ UTF-8 encoded symbolic link path for the ID_DIRECTORY that immediately
195
+ preceeded this header. The length is given in the ID_EXTENDED header and the
196
+ data is a char[].
197
+
198
+
199
+ =====================
200
+ File Extended Headers
201
+ =====================
202
+
203
+ Like extended headers, file extended headers should be written in ascending
204
+ numeric order.
205
+
206
+ FILE_EX_VERSION (0x01)
207
+ String representation of an executable file's Major/Minor/Maint/Build
208
+ version (e.g., "2.11.28.3542").
209
+
210
+ Length : UByte
211
+ Data : char[Length]
212
+
213
+
214
+ FILE_EX_UTF8 (0x02)
215
+ UTF-8 encoded filename. Stored as a FileExString. Only used if the UTF-8
216
+ name doesn't match the ANSI encoded one or if the filename is longer than 255
217
+ characters.
218
+
219
+
220
+ FILE_EX_LINK_PATH (0x03)
221
+ UTF-8 encoded symbolic link path. Stored as a FileExString.
222
+
223
+
224
+ FileExString
225
+ ------------
226
+ Beyond Compare v2.4.1 and earlier will produce incorrect results if it
227
+ encounters a raw 0x01 byte in a file extended header. To prevent that most
228
+ strings in ID_FILE_EX extended headers are written like so:
229
+
230
+ Length : UByte[+UByte]
231
+ Data : char[Length]
232
+
233
+ If (Length <> 1) and (Length <= 127) then Length is 1 byte
234
+ Otherwise the Length is written as
235
+ Low : UByte(Length) OR 0x80
236
+ High : UByte(Length shr 7) OR 0x80
237
+
238
+ If an extended header must have a 0x01 in it (other than FILE_EX_VERSION),
239
+ increase the .bcss header's Minimum Supported Version to 1.1.
data/bin/bc3_merge.rb ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env ruby
2
+ =begin rdoc
3
+ bc3_merge.rb
4
+
5
+ Merge snapshots and build a new snapshot with all files.
6
+
7
+ Example:
8
+ bc3_merge.rb '*.bcss'
9
+ Takes all snapshots in the directory and build a new one.
10
+
11
+ =Options
12
+ ==Target snapshot
13
+ The filename for the target snapshot will be compound by
14
+ _snapshot_merge__ and a time stamp.
15
+
16
+ You may choose your own name:
17
+ bc3_merge.rb *.bcss -t merged_snapshot.bcss
18
+
19
+ ==Folder for insertion
20
+ If you add a snapshot, you must define, how you add the snapshot.
21
+
22
+ Each snapshot has a source path, e.g. c:/user/me/folder1.
23
+ There are three different possible behaviours how you
24
+ handle the source path of your snapshoot.
25
+
26
+ Attention!
27
+ When you use wildcards inside a shell, the may be expanded.
28
+ To move the wildcards to the script, you must mask your input
29
+
30
+ Wrong:
31
+ bc3_merge.rb -r *.bcss
32
+ Correct:
33
+ bc3_merge.rb -r '*.bcss'
34
+
35
+ ===Full source path
36
+ The full source path (starting from roor) will be added to the snapshot
37
+ bc3_merge.rb -r '*.bcss'
38
+ or
39
+ bc3_merge.rb -root '*.bcss'
40
+
41
+ ===Add with base path
42
+ The snapshot is added with the base directory name.
43
+
44
+ With source path _c:/user/me/folder1_, the snapshot would be added as _folder1_
45
+
46
+ bc3_merge *.bcss
47
+ or
48
+ bc3_merge -b '*.bcss'
49
+ or
50
+ bc3_merge --base '*.bcss'
51
+
52
+ ===Add without source path info
53
+ The snapshot is added directly to the target snapshot.
54
+ There is no source path information used.
55
+
56
+ bc3_merge -i '*.bcss'
57
+ or
58
+ bc3_merge --initial '*.bcss'
59
+
60
+ =Usage Examples
61
+ bc3_merge.rb my_folder1.bcss my_folder1.bcss
62
+ Takes the two snapshots and mix them.
63
+
64
+ bc3_merge.rb '*.bcss'
65
+ Takes all snapshots in the directory and build a new one.
66
+ =end
67
+
68
+ require 'optparse'
69
+ require 'bc3'
70
+ #~ $log.level = Log4r::DEBUG
71
+
72
+ #Collector
73
+ $options = {
74
+ target: Time.now.strftime("snapshot_merge_%Y-%m-%d_%H-%M-%S.bcss"),
75
+ source: Dir.pwd,
76
+ root: [],
77
+ base: [],
78
+ initial: [],
79
+ }
80
+
81
+ =begin
82
+ Create a Parser for command line
83
+ =end
84
+ opts = OptionParser.new()
85
+ opts.banner = "Usage: bc3_merge.rb [options] [source snapshots]" #Usage-zeile
86
+ opts.separator("Merge Beyond Compare Snapshots to a new snapshot")
87
+
88
+ =begin
89
+ Source path
90
+ =end
91
+ opts.on("-s", "--source SOURCE", "Set source path for the snapshot") { |v|
92
+ $options[:source] = v
93
+ }
94
+ =begin
95
+ Set target name.
96
+ =end
97
+ opts.on("-t", "--target TARGET", "Set target file name") { |v|
98
+ $options[:target] = v
99
+ }
100
+
101
+ =begin
102
+ Define source snapshots and how they are added.
103
+ =end
104
+ opts.on("-r", "--root SNAPSHOT_MASK", "Add snapshots with root-information") { |v|
105
+ $options[:root] << v
106
+ }
107
+
108
+ opts.on("-b", "--base SNAPSHOT_MASK", "Add snapshots with base-information") { |v|
109
+ $options[:base] << v
110
+ }
111
+
112
+ opts.on("-i", "--initial SNAPSHOT_MASK", "Add snapshots without source-information") { |v|
113
+ $options[:initial] << v
114
+ }
115
+
116
+ #Parsen der Parameter mit Exception bei ung�ltigen Parametern
117
+ begin
118
+ opts.parse!
119
+ # rescue OptionParser::MissingArgument => err
120
+ # rescue OptionParser::InvalidOption => err
121
+ rescue OptionParser::MissingArgument, OptionParser::InvalidOption => err
122
+ puts "Error:\t#{err}"
123
+ #Ausgabe der Schnittstelle
124
+ puts opts
125
+ end
126
+
127
+ snapshot = BC3::Snapshot.new( $options[:source] )
128
+
129
+ $options[:root].each{|mask|
130
+ puts "Add #{mask} in root-mode"
131
+ Dir[mask].each{|file|
132
+ snap = BC3::SnapshotParser.new(file)
133
+ snapshot << folder = BC3::Folder.new( snap.snapshot.path, snap.timestamp)
134
+ snap.snapshot.each{|key, x| folder << x }
135
+ }
136
+ }
137
+
138
+ ($options[:base] + ARGV).each{|mask|
139
+ puts "Add #{mask} in base-mode"
140
+ Dir[mask].each{|file|
141
+ snap = BC3::SnapshotParser.new(file)
142
+ snapshot << folder = BC3::Folder.new( File.basename(snap.snapshot.path), snap.timestamp )
143
+ snap.snapshot.each{|key, x| folder << x }
144
+ }
145
+ }
146
+
147
+ $options[:initial].each{|mask|
148
+ puts "Add #{mask} in raw-mode"
149
+ Dir[mask].each{|file|
150
+ BC3::SnapshotParser.new(file).snapshot.each{|key, x| snapshot << x }
151
+ }
152
+ }
153
+
154
+ #~ puts snapshot.each.keys
155
+ snapshot.save($options[:target])
Binary file
Binary file
@@ -0,0 +1,43 @@
1
+ $:.unshift('../lib')
2
+ require 'bc3'
3
+ require 'yaml'
4
+
5
+ test = BC3::Snapshot.newh(YAML.load(<<data
6
+ :snapshot: C:\\Temp
7
+ :content:
8
+ - :dirname: dir1
9
+ :content:
10
+ - :filename: file1.txt
11
+ :filesize: 32
12
+ data
13
+ ))
14
+
15
+ test << BC3::Folder.newh(YAML.load(<<data
16
+ :dirname: dir3
17
+ :content:
18
+ - :filename: file1.txt
19
+ :filesize: 32
20
+ data
21
+ ))
22
+
23
+ test << BC3::Folder.newh(YAML.load(<<data
24
+ :dirname: dir3
25
+ :content:
26
+ - :filename: file1.txt
27
+ :filesize: 32
28
+ - :filename: file2.txt
29
+ :filesize: 12
30
+ data
31
+ ))
32
+
33
+
34
+ #~ puts test.to_hash.to_yaml#Hash falsch (keine Aggregation)
35
+ test.save('results/test_combine_1.bcss')#bcss aggregiert selbst
36
+
37
+ test << BC3::File.new( filename: 'dir2', filesize: 17 )
38
+
39
+ test << BC3::File.new( filename: 'test.txt', filesize: 17 )
40
+ test << BC3::File.new( filename: 'test.txt', filesize: 17 )
41
+
42
+ puts test.to_hash.to_yaml#Hash falsch (keine Aggregation)
43
+ test.save('results/test_combine_2.bcss')#bcss aggregiert selbst
@@ -0,0 +1,6 @@
1
+ $:.unshift('../lib')
2
+ #Define directroy to be packed.
3
+ dir = 'folder1'
4
+ x = BC3::Snapshot.newd(dir)
5
+ x.save("results/#{dir}_compressed.bcss", true)
6
+ x.save("results/#{dir}_compressed.xxxx", true)