rubyyabt 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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