rubyyabt 0.0.5

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,224 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'timeout'
6
+ #noinspection RubyResolve
7
+ require 'thread'
8
+ #noinspection RubyResolve
9
+ require 'classes/proxy_http_cache_hash'
10
+
11
+ $myDEBUG = false if not $myDEBUG
12
+ $myVERBOSE = false if not $myVERBOSE
13
+ $options = nil unless $options
14
+
15
+ class ProxyHTTP
16
+ def initialize()
17
+ # Defines the subdirs for each type of data
18
+ @types = {
19
+ :chunk => 'chunks',
20
+ :file => 'files',
21
+ :backup => 'backups',
22
+ :root => 'meta',
23
+ :cache => 'cache'
24
+ }
25
+ @@valid_caches = [:chunk, :file]
26
+ $options[:target] += "/" if $options[:target][-1] != "/"
27
+ # Calculate the user agent for HTTP requests
28
+ $options[:user_agent] = $options[:program_name] + " " + $options[:version].join(".")
29
+ @mutex = Mutex.new
30
+ @url = URI.parse($options[:target])
31
+ @http = Net::HTTP.new(@url.host, @url.port)
32
+ @http.use_ssl = true if @url.scheme = 'https'
33
+ @http.open_timeout = 15
34
+ @http.read_timeout = 280
35
+ if $options[:ssl_cert_dir] then
36
+ #noinspection RubyResolve
37
+ @http.ca_path = $options[:ssl_cert_dir]
38
+ #noinspection RubyResolve
39
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
40
+ #noinspection RubyResolve
41
+ @http.verify_depth = 5
42
+ end
43
+ @cui = Cui.instance
44
+ @cache = ProxyHTTPCache_Hash.new
45
+ end
46
+
47
+ def caching?()
48
+ return true
49
+ end
50
+
51
+ def dump_cache()
52
+ return @cache.dump
53
+ end
54
+
55
+ def load_cache(cache_data)
56
+ #noinspection RubyResolve
57
+ begin
58
+ @cache = Marshal.load(cache_data)
59
+ if not @cache.kind_of?(ProxyHTTPCache_Hash) then
60
+ @cui.error("Cache is not compatible. Starting with an empty cache.")
61
+ @cache = ProxyHTTPCache_Hash.new
62
+ end
63
+ rescue Exception # Just start out with an empty cache
64
+ @cache = ProxyHTTPCache_Hash.new
65
+ end
66
+
67
+ end
68
+
69
+ #noinspection RubyScope
70
+ def request(http_request, timeout = 300, tries = 20)
71
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Trying to lock ProxyHTTP request mutex in thread #{Thread.current.inspect}") if $myDEBUG
72
+ @mutex.synchronize {
73
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Got ProxyHTTP request mutex in thread #{Thread.current.inspect}") if $myDEBUG
74
+ response = ''
75
+ while tries > 0 do
76
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Got #{tries} tries left for request #{http_request.inspect} #{http_request.path}, timeout #{timeout}") if $myDEBUG
77
+ begin
78
+ Timeout::timeout(timeout) {
79
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Sending request #{http_request.inspect} for #{http_request.path}....") if $myDEBUG
80
+ response = @http.request(http_request)
81
+ }
82
+ rescue Timeout::Error
83
+ tries -= 1
84
+ @cui.error("Timeout during request. #{tries} tries left")
85
+ retry if tries > 0
86
+ raise
87
+ rescue Exception => ex
88
+ @cui.error("Caught exception #{ex} with message #{ex.message} during request. #{tries} tries left") if $myVERBOSE
89
+ tries -= 1
90
+ retry if tries > 0
91
+ raise
92
+ end
93
+ code = response.code.to_i
94
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Response code: #{code}") if $myDEBUG
95
+ return response if (code >= 200) and (code < 300)
96
+ if (code >= 400) and (code < 500) # No retries for these errors...
97
+ # Yeehaw! we need an exception for code 401 since humyo sometimes replies with it with correct authorization
98
+ tries = 0 if (code != 401)
99
+ end
100
+ tries -= 1
101
+ @cui.error("DEBUG[#{Thread.current.inspect}]: #{tries} tries left for this request. Retrying after sleep if > 0") if $myDEBUG
102
+ sleep(rand) if tries > 0 # Sleep up to one second before retrying
103
+ end
104
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Raising exception for failed request: #{response.code} #{response.message}") if $myDEBUG
105
+ raise 'HTTP request failed: ' + response.code + ' ' + response.message
106
+ }
107
+ end
108
+
109
+ #noinspection RubyUnusedLocalVariable
110
+ def read(target_dir, type, file)
111
+ subdir_type = type
112
+ subdir_type = (type.to_s + file[0..2]).to_sym if type == :chunk
113
+ subdir_type = (type.to_s + file[0..2]).to_sym if type == :file
114
+ subdir = ''
115
+ subdir = file[0..2] + '/' if type == :chunk
116
+ subdir = file[0..2] + '/' if type == :file
117
+ uri = @url.merge(@types[type] + '/').merge(subdir).merge(file)
118
+ get = Net::HTTP::Get.new(uri.request_uri)
119
+ get.initialize_http_header({"User-Agent" => $options[:user_agent]})
120
+ get.basic_auth($options[:username], $options[:password])
121
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Reading from #{uri.to_s}") if $myDEBUG
122
+ begin
123
+ response = request(get, $options[:http_timeout])
124
+ rescue Exception => ex
125
+ @cui.error(ex.message + ' at URL: ' + uri.to_s)
126
+ raise
127
+ else
128
+ return response.body
129
+ end
130
+ end
131
+
132
+ #noinspection RubyUnusedLocalVariable
133
+ def write(target_dir, type, file, data)
134
+ subdir_type = type
135
+ subdir_type = (type.to_s + file[0..2]).to_sym if type == :chunk
136
+ subdir_type = (type.to_s + file[0..2]).to_sym if type == :file
137
+ subdir = ''
138
+ subdir = file[0..2] + '/' if type == :chunk
139
+ subdir = file[0..2] + '/' if type == :file
140
+ uri = @url.merge(@types[type] + '/').merge(subdir).merge(file)
141
+ put = Net::HTTP::Put.new(uri.request_uri)
142
+ put.initialize_http_header({"User-Agent" => $options[:user_agent], "Content-Type" => "application/octet-stream"})
143
+ put.basic_auth($options[:username], $options[:password])
144
+ put.body = data
145
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Writing to #{uri.to_s}") if $myDEBUG
146
+ begin
147
+ response = request(put, 180)
148
+ rescue RuntimeError => ex
149
+ if ex.message == "HTTP request failed: 409 Conflict" then
150
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Caught a 409 Conflict error") if $myDEBUG
151
+ if (subdir_type != type) then
152
+ begin
153
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Trying to create the directory #{@url.merge(@types[type] + '/').to_s}") if $myDEBUG
154
+ mkdir(@url.merge(@types[type] + '/'))
155
+ rescue RuntimeError => rt
156
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Caught exception during mkdir: #{rt.message}") if $myDEBUG
157
+ raise if rt.message[0..23] != "HTTP request failed: 405"
158
+ end
159
+ end
160
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Trying to create the directory #{@url.merge(@types[type] + '/').merge(subdir).to_s}") if $myDEBUG
161
+ mkdir(@url.merge(@types[type] + '/').merge(subdir))
162
+ end
163
+ retry
164
+ rescue Exception => ex
165
+ @cui.error(ex.message + ' at URL: ' + uri.to_s)
166
+ raise
167
+ else
168
+ @cache.add(type, file) if @@valid_caches.include?(type)
169
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Added #{file} to cache list for #{subdir_type}") if $myDEBUG
170
+ return true
171
+ end
172
+ end
173
+
174
+ def mkdir(uri)
175
+ mkcol = Net::HTTP::Mkcol.new(uri.request_uri)
176
+ mkcol.initialize_http_header({"User-Agent" => $options[:user_agent]})
177
+ mkcol.basic_auth($options[:username], $options[:password])
178
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Trying to create directory #{uri.to_s}") if $myDEBUG
179
+ begin
180
+ request(mkcol)
181
+ rescue Exception => ex
182
+ # Check if it's just a 405 that happens when the directory already exists...
183
+ return if ex.message[0..23] == "HTTP request failed: 405"
184
+ @cui.error(ex.message + ' at URL: ' + uri.to_s)
185
+ raise
186
+ end
187
+ end
188
+
189
+ def exists?(target_dir, type, file)
190
+ return @cache.search(type, file) if @@valid_caches.include?(type)
191
+ return false # TODO: Need to do a real check on the server instead...
192
+ end
193
+
194
+ def list(uri)
195
+ propfind = Net::HTTP::Propfind.new(uri.request_uri)
196
+ propfind.initialize_http_header({"User-Agent" => $options[:user_agent], "Depth" => "1", "Content-Type" => "application/octet-stream"})
197
+ propfind.basic_auth($options[:username], $options[:password])
198
+ propfind.body = '<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><propname/></propfind>'
199
+ @cui.error("Retrieving file list for #{uri.to_s}...") if $myDEBUG
200
+ begin
201
+ response = request(propfind, 30)
202
+ rescue RuntimeError => ex
203
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Caught exception #{ex} with message #{ex.message} during list") if $myDEBUG
204
+ return Array.new if ex.message[0..23] == "HTTP request failed: 404"
205
+ @cui.error("Caught exception #{ex.message} for URL #{uri.to_s} with request #{propfind.inspect}") if $myVERBOSE
206
+ rescue Exception => ex
207
+ @cui.error(ex.message + ' at URL: ' + uri.to_s)
208
+ raise
209
+ else
210
+ @cui.error("DEBUG[#{Thread.current.inspect}]: Parsing result for list...") if $myDEBUG
211
+ regexp = Regexp.new('<D:href>([^<]*)</D:href>', Regexp::IGNORECASE)
212
+ list = response.body.scan(regexp)
213
+ list.each_index { | i |
214
+ list[i] = list[i][0]
215
+ list[i] = list[i][(uri.request_uri.length)..(list[i].length)]
216
+ list[i] = nil if list[i] == ""
217
+ }
218
+ list.compact!
219
+ list.sort!
220
+ @cui.error("Found #{list.count} files") if $myDEBUG
221
+ list
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #noinspection RubyResolve
4
+ require 'classes/Chunk'
5
+ #noinspection RubyResolve
6
+ require 'classes/Source'
7
+ #noinspection RubyResolve
8
+ require 'classes/Target'
9
+ require 'digest/md5'
10
+ #noinspection RubyResolve
11
+ require 'digest/sha2'
12
+ require 'time'
13
+
14
+ $myDEBUG = false if not $myDEBUG
15
+ $myVERBOSE = false if not $myVERBOSE
16
+ $options = nil unless $options
17
+
18
+ class SMGFile
19
+ attr_reader :directory, :name, :type
20
+
21
+ def initialize(file = nil, md5 = nil, sha256 = nil)
22
+ @cui = Cui.instance
23
+ @source = Source.instance
24
+ @target = Target.instance
25
+ @is_complete = false
26
+ @filename = file
27
+ @md5 = nil
28
+ @sha256 = nil
29
+ @size = 0
30
+ @mtime = 0
31
+ @meta_md5 = md5
32
+ @meta_sha256 = sha256
33
+ @chunks = Array.new
34
+ end
35
+
36
+ def backup!()
37
+ # Check the file type
38
+ case @source.ftype(@filename)
39
+ when 'file'
40
+ backup_file()
41
+ when 'directory'
42
+ backup_dir()
43
+ when 'characterSpecial'
44
+ # backup_dev(@filename) TODO
45
+ when 'blockSpecial'
46
+ # backup_dev(@filename) TODO
47
+ when 'fifo'
48
+ # backup_fifo(@filename) TODO
49
+ when 'link'
50
+ backup_link()
51
+ when 'socket'
52
+ # backup_socket(@filename) TODO
53
+ when 'unknown'
54
+ @cui.message("[E] Unsupported file type for: #{@filename}")
55
+ end
56
+ end
57
+
58
+ def backup_file()
59
+ @cui.current_file_name(@filename)
60
+ fd = @source.open_read(@filename)
61
+ stat = fd.lstat
62
+ ftype = stat.ftype
63
+ mtime = stat.mtime.utc.rfc2822
64
+ mode = "%o" % stat.mode
65
+ uid = stat.uid.to_s
66
+ gid = stat.gid.to_s
67
+ expected_size = stat.size
68
+ @cui.current_file_size(expected_size)
69
+ size = 0
70
+ md5hashing = Digest::MD5.new
71
+ sha256hashing = Digest::SHA2.new(256)
72
+ @cui.message("DEBUG[#{Thread.current.inspect}: Created hashing objects") if $myDEBUG
73
+ chunks = ''
74
+ while !fd.eof?
75
+ @cui.message("DEBUG[#{Thread.current.inspect}: Checked for EOF") if $myDEBUG
76
+ chunk = Chunk.new
77
+ @cui.message("DEBUG[#{Thread.current.inspect}: Created new chunk") if $myDEBUG
78
+ data = fd.read($options[:chunk_size])
79
+ @cui.message("DEBUG[#{Thread.current.inspect}: Read data") if $myDEBUG
80
+ chunk.set_data(data)
81
+ @cui.message("DEBUG[#{Thread.current.inspect}: Saved data in chunk") if $myDEBUG
82
+ md5hashing << data
83
+ @cui.message("DEBUG[#{Thread.current.inspect}: hashed for MD5") if $myDEBUG
84
+ sha256hashing << data
85
+ @cui.message("DEBUG[#{Thread.current.inspect}: hashed for sha256") if $myDEBUG
86
+ size += data.length
87
+ @cui.message("DEBUG[#{Thread.current.inspect}: Updated read size") if $myDEBUG
88
+ chunk.backup! # Upload this chunk
89
+ @cui.message("DEBUG[#{Thread.current.inspect}: backed up chunk") if $myDEBUG
90
+ chunks += chunk.sha256 + '.' + chunk.md5 + "\n"
91
+ @cui.finished_size_add(data.length)
92
+ end
93
+ fd.close()
94
+ @cui.message("DEBUG[#{Thread.current.inspect}: Closed FD") if $myDEBUG
95
+ md5 = md5hashing.hexdigest
96
+ @cui.message("DEBUG[#{Thread.current.inspect}: Got MD5") if $myDEBUG
97
+ sha256 = sha256hashing.hexdigest
98
+ @cui.message("DEBUG[#{Thread.current.inspect}: Got sha256") if $myDEBUG
99
+ # Build the metadata file
100
+ @metadata = "[stat]\n"
101
+ @metadata += "filename = " + @filename + "\n"
102
+ @metadata += "ftype = " + ftype + "\n"
103
+ @metadata += "mtime = " + mtime + "\n"
104
+ @metadata += "mode = " + mode + "\n"
105
+ @metadata += "uid = " + uid + "\n"
106
+ @metadata += "gid = " + gid + "\n"
107
+ @metadata += "size = " + size.to_s + "\n"
108
+ @metadata += "\n"
109
+ @metadata += "[checksums]\n"
110
+ @metadata += "md5 = " + md5 + "\n"
111
+ @metadata += "sha256 = " + sha256 + "\n"
112
+ @metadata += "\n"
113
+ @metadata += "[chunks]\n"
114
+ @metadata += chunks
115
+ @cui.message("Size mismatch: expected #{expected_size} but found #{size}!") if expected_size != size
116
+ @size = size
117
+ @mtime = mtime
118
+ @cui.message("DEBUG[#{Thread.current.inspect}: Checking if file already exists on server") if $myDEBUG
119
+ return true if @target.exists?(:file, "#{meta_sha256}.#{meta_md5}") # if this chunk already exists, no need to upload it
120
+ @cui.message("Uploading file metadata #{@filename}: #{meta_sha256}.#{meta_md5}...") if $myDEBUG
121
+ @target.write(:file, "#{meta_sha256}.#{meta_md5}", @metadata)
122
+ @cui.message("DEBUG[#{Thread.current.inspect}: Uploaded file metadata") if $myDEBUG
123
+ end
124
+
125
+ def backup_dir()
126
+ @cui.current_file_name(@filename)
127
+ stat = @source.lstat(@filename)
128
+ ftype = stat.ftype
129
+ mtime = stat.mtime.utc.rfc2822
130
+ mode = "%o" % stat.mode
131
+ uid = stat.uid.to_s
132
+ gid = stat.gid.to_s
133
+ # Build the metadata file
134
+ @metadata = "[stat]\n"
135
+ @metadata += "filename = " + @filename + "\n"
136
+ @metadata += "ftype = " + ftype + "\n"
137
+ @metadata += "mtime = " + mtime + "\n"
138
+ @metadata += "mode = " + mode + "\n"
139
+ @metadata += "uid = " + uid + "\n"
140
+ @metadata += "gid = " + gid + "\n"
141
+ @size = 0
142
+ @mtime = mtime
143
+ return true if @target.exists?(:file, "#{meta_sha256}.#{meta_md5}") # if this chunk already exists, no need to upload it
144
+ @cui.message("Uploading directory metadata #{@filename}: #{meta_sha256}.#{meta_md5}...") if $myVERBOSE
145
+ @target.write(:file, "#{meta_sha256}.#{meta_md5}", @metadata)
146
+ end
147
+
148
+ def backup_link()
149
+ @cui.message("Backing up link #{@filename}") if $myVERBOSE
150
+ stat = @source.lstat(@filename)
151
+ ftype = stat.ftype
152
+ mtime = stat.mtime.utc.rfc2822
153
+ mode = "%o" % stat.mode
154
+ uid = stat.uid.to_s
155
+ gid = stat.gid.to_s
156
+ link_target = @source.readlink(@filename)
157
+ @metadata = "[stat]\n"
158
+ @metadata += "filename = " + @filename + "\n"
159
+ @metadata += "ftype = " + ftype + "\n"
160
+ @metadata += "target = " + link_target + "\n"
161
+ @metadata += "mode = " + mode + "\n"
162
+ @metadata += "uid = " + uid + "\n"
163
+ @metadata += "gid = " + gid + "\n"
164
+ @size = 0
165
+ @mtime = mtime
166
+ return true if @target.exists?(:file, "#{meta_sha256}.#{meta_md5}") # if this chunk already exists, no need to upload it
167
+ @cui.message("Uploading file metadata #{@filename}: #{meta_sha256}.#{meta_md5}...") if $myVERBOSE
168
+ @target.write(:file, "#{meta_sha256}.#{meta_md5}", @metadata)
169
+ end
170
+
171
+ def restore!()
172
+ return false if ! @target.exists?(:file, "#{meta_sha256}.#{meta_md5}") # Cannot restore if metadata not available
173
+ @metadata = @target.read(:file, "#{meta_sha256}.#{meta_md5}")
174
+ parse_metadata
175
+ case @ftype
176
+ when 'file'
177
+ restore_file()
178
+ when 'directory'
179
+ restore_dir()
180
+ when 'characterSpecial'
181
+ # restore_dev(@filename) TODO
182
+ when 'blockSpecial'
183
+ # restore_dev(@filename) TODO
184
+ when 'fifo'
185
+ # restore_fifo(@filename) TODO
186
+ when 'link'
187
+ restore_link()
188
+ when 'socket'
189
+ # restore_socket(@filename) TODO
190
+ when 'unknown'
191
+ @cui.error("Unsupported file type for: #{@filename}")
192
+ end
193
+ end
194
+
195
+ def restore_file()
196
+ fd = @source.open_write(@filename)
197
+ size = 0
198
+ md5hashing = Digest::MD5.new
199
+ sha256hashing = Digest::SHA2.new(256)
200
+ @chunks.each { | chunk_sum |
201
+ # Get sha256 and md5 for the chunk, they are verified in the chunk
202
+ sha256 = chunk_sum[0..63]
203
+ md5 = chunk_sum[65..96]
204
+ chunk = Chunk.new
205
+ chunk.md5 = md5
206
+ chunk.sha256 = sha256
207
+ chunk.restore!
208
+ fd.write(chunk.data)
209
+ size += chunk.length
210
+ md5hashing << chunk.data
211
+ sha256hashing << chunk.data
212
+ }
213
+ fd.close()
214
+ # Recover the file information
215
+ @source.set_stat(@filename, @mtime, @mode, @uid, @gid)
216
+ @cui.error("File size mismatch in file #{@filename}: #{sha256}.#{md5}") if size != @size
217
+ # Now get the hashes for the whole file and compare them
218
+ md5 = md5hashing.hexdigest
219
+ sha256 = sha256hashing.hexdigest
220
+ if (@md5 = md5) and (@sha256 = sha256) then
221
+ @cui.message("File verified")
222
+ else
223
+ @cui.error("Checksum error in file #{@filename}: #{sha256}.#{md5}")
224
+ end
225
+ end
226
+
227
+ def restore_link()
228
+ @source.symlink(@filename, @target)
229
+ # @source.set_stat(@filename, Time.now, @mode, @uid, @gid)
230
+ end
231
+
232
+ def restore_dir()
233
+ if !(@source.exists?(@filename)) then
234
+ @source.mkdir(@filename)
235
+ @source.set_stat(@filename, @mtime, @mode, @uid, @gid)
236
+ end
237
+ end
238
+
239
+ def parse_metadata
240
+ chunk_data = /\[chunks\]\n([0-9a-zA-Z]{64}\.[0-9a-zA-Z]{32}\n)+/.match(@metadata)
241
+ chunk_data = chunk_data.to_s
242
+ chunk_data = chunk_data.split("\n")
243
+ chunk_data.delete_at(0)
244
+ @chunks = chunk_data
245
+ @metadata.lines { | line |
246
+ case
247
+ when line =~ /^[a-z0-9A-Z]+ = /
248
+ field = /^([a-z0-9A-Z]+) = /.match(line).captures[0]
249
+ case field
250
+ when "filename"
251
+ @filename = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0]
252
+ when "ftype"
253
+ @ftype = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0]
254
+ when "mtime"
255
+ @mtime = Time.rfc2822(/^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0])
256
+ when "mode"
257
+ @mode = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0].oct
258
+ when "uid"
259
+ @uid = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0].to_i
260
+ when "gid"
261
+ @gid = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0].to_i
262
+ when "size"
263
+ @size = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0].to_i
264
+ when "md5"
265
+ @md5 = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0]
266
+ when "sha256"
267
+ @sha256 = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0]
268
+ when "target"
269
+ @target = /^[a-z0-9A-Z]+ = (.*)$/.match(line).captures[0]
270
+ end
271
+ end
272
+ }
273
+ end
274
+
275
+ def meta_md5()
276
+ return @meta_md5 if @meta_md5
277
+ @meta_md5 = Digest::MD5.hexdigest(@metadata)
278
+ end
279
+
280
+ def meta_sha256()
281
+ return @meta_sha256 if @meta_sha256
282
+ @meta_sha256 = Digest::SHA2.new(256).hexdigest(@metadata)
283
+ end
284
+
285
+ def size()
286
+ @size
287
+ end
288
+
289
+ def mtime()
290
+ @mtime
291
+ end
292
+ end
data/classes/Source.rb ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'singleton'
4
+ require 'find'
5
+
6
+ $options = nil unless $options
7
+
8
+ class Source
9
+ include Singleton
10
+
11
+ def initialize()
12
+ @cui = Cui.instance
13
+ @files = nil
14
+ raise "Source (#{$options[:source]}) is not a directory" if !(File.directory?($options[:source]))
15
+ # Remove line endings if necessary
16
+ $options[:excl_incl].each { | s | s.chomp! }
17
+ end
18
+
19
+ def exists?(path = '')
20
+ File.exists?($options[:source] + path)
21
+ end
22
+
23
+ def open_read(path)
24
+ File.new($options[:source] + path, 'rb+')
25
+ end
26
+
27
+ def open_write(path)
28
+ File.new($options[:source] + path, 'wb+')
29
+ end
30
+
31
+ def mkdir(path)
32
+ Dir.mkdir($options[:source] + path)
33
+ end
34
+
35
+ def ftype(path)
36
+ File.ftype($options[:source] + path)
37
+ end
38
+
39
+ def lstat(path)
40
+ File.lstat($options[:source] + path)
41
+ end
42
+
43
+ def readlink(path)
44
+ File.readlink($options[:source] + path)
45
+ end
46
+
47
+ def symlink(path, target)
48
+ File.symlink(target, $options[:source] + path)
49
+ end
50
+
51
+ def set_stat(path, mtime, mode, uid, gid)
52
+ if File.lstat($options[:source] + path).ftype != 'link' then
53
+ File.utime(0, mtime, $options[:source] + path)
54
+ File.chmod(mode, $options[:source] + path)
55
+ File.chown(uid, gid, $options[:source] + path)
56
+ end
57
+ end
58
+
59
+ def files(rescan = false)
60
+ if not rescan then
61
+ return @files if @files
62
+ end
63
+ @files = Array.new
64
+ options_source_length = $options[:source].length
65
+ Find.find($options[:source]) { | f |
66
+ # Remove the leading full path
67
+ f = f[(options_source_length)..99999]
68
+ f = '/' if f.empty?
69
+ include = true # by default include everything
70
+ # Now loop over the include/exclude globs
71
+ $options[:excl_incl].each { | ei |
72
+ what = ei[0..0] # First character, this syntax is required for ruby 1.8
73
+ glob = ei[1..99999] # More than a hundred thousand characters? bad luck...
74
+ case what # Check the glob for a match and update include variable
75
+ when '-'
76
+ include = false # glob matched but was an exclude
77
+ when '+'
78
+ include = true # glob matched and is an include
79
+ end if File.fnmatch(glob, f)
80
+ }
81
+ if include then
82
+ size = 0
83
+ size = File.size($options[:source] + f) if File.file?($options[:source] + f)
84
+ @cui.total_size_add(size)
85
+ @cui.total_files_inc
86
+ @files << f
87
+ else
88
+ Find.prune # Skip directories or files if they are not to be included
89
+ end
90
+ }
91
+ @files
92
+ end
93
+ end