six-rsync 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/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ == six-rsync
2
+
3
+ Put appropriate LICENSE for your project here.
data/README ADDED
@@ -0,0 +1,3 @@
1
+ == six-rsync
2
+
3
+ You should document your project here.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ #
2
+ # To change this template, choose Tools | Templates
3
+ # and open the template in the editor.
4
+
5
+
6
+ require 'rubygems'
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'rake/gempackagetask'
10
+ require 'rake/rdoctask'
11
+ require 'rake/testtask'
12
+
13
+ spec = Gem::Specification.new do |s|
14
+ s.name = 'six-rsync'
15
+ s.version = '0.1.0'
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ['README', 'LICENSE']
18
+ s.summary = 'Your summary here'
19
+ s.description = s.summary
20
+ s.author = 'Sickboy'
21
+ s.email = 'sb@dev-heaven.net'
22
+ # s.executables = ['your_executable_here']
23
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,spec}/**/*") + Dir.glob("lib/*/**/*.rb")
24
+ s.require_path = "lib"
25
+ s.bindir = "bin"
26
+ end
27
+
28
+ Rake::GemPackageTask.new(spec) do |p|
29
+ p.gem_spec = spec
30
+ p.need_tar = true
31
+ p.need_zip = true
32
+ end
33
+
34
+ Rake::RDocTask.new do |rdoc|
35
+ files =['README', 'LICENSE', "lib/*/**/*.rb"]
36
+ rdoc.rdoc_files.add(files)
37
+ rdoc.main = "README" # page to start on
38
+ rdoc.title = "six-updater-gui Docs"
39
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
40
+ rdoc.options << '--line-numbers'
41
+ end
42
+
43
+ Rake::TestTask.new do |t|
44
+ t.test_files = FileList['test/**/*.rb']
45
+ end
@@ -0,0 +1,154 @@
1
+ # encoding: UTF-8
2
+
3
+ module Six
4
+ module Repositories
5
+ module Rsync
6
+ class Base
7
+ attr_reader :repository
8
+
9
+ # opens a new Rsync Project from a working directory
10
+ # you can specify non-standard rsync_dir and index file in the options
11
+ def self.open(working_dir, opts={})
12
+ self.new({:working_directory => working_dir}.merge(opts))
13
+ end
14
+
15
+ # initializes a rsync repository
16
+ #
17
+ # options:
18
+ # :repository
19
+ # :index_file
20
+ #
21
+ def self.init(working_dir, opts = {})
22
+ opts = {
23
+ :working_directory => working_dir,
24
+ :repository => File.join(working_dir, '.rsync')
25
+ }.merge(opts)
26
+
27
+ #FileUtils.mkdir_p(opts[:working_directory]) if opts[:working_directory] && !File.directory?(opts[:working_directory])
28
+
29
+ # run rsync_init there
30
+ logger = if opts[:log]
31
+ opts[:log]
32
+ else
33
+ nil
34
+ end
35
+ Rsync::Lib.new(opts, logger).init
36
+
37
+ self.new(opts)
38
+ end
39
+
40
+ # clones a rsync repository locally
41
+ #
42
+ # repository - http://repo.or.cz/w/sinatra.git
43
+ # name - sinatra
44
+ #
45
+ # options:
46
+ # :repository
47
+ #
48
+ # :bare
49
+ # or
50
+ # :working_directory
51
+ # :index_file
52
+ #
53
+ def self.clone(host, name, opts = {})
54
+ # run Rsync clone
55
+ logger = if opts[:log]
56
+ opts[:log]
57
+ else
58
+ nil
59
+ end
60
+ self.new(Rsync::Lib.new(nil, logger).clone(host, name, opts))
61
+ end
62
+
63
+ def initialize(options = {})
64
+ if working_dir = options[:working_directory]
65
+ options[:repository] ||= File.join(working_dir, '.rsync')
66
+ end
67
+ if options[:log]
68
+ @logger = options[:log]
69
+ @logger.debug("Starting Rsync on #{working_dir}")
70
+ else
71
+ @logger = nil
72
+ end
73
+ @working_directory = options[:working_directory] ? Rsync::WorkingDirectory.new(options[:working_directory]) : nil
74
+ @repository = options[:repository] ? Rsync::Repository.new(options[:repository]) : nil
75
+
76
+ end
77
+
78
+ def update
79
+ lib.update('')
80
+ end
81
+
82
+ def reset(opts = {})
83
+ lib.reset(opts)
84
+ end
85
+
86
+ def add(file = '')
87
+ lib.add(file)
88
+ end
89
+
90
+ def commit
91
+ lib.commit
92
+ end
93
+
94
+ def push
95
+ lib.push
96
+ end
97
+
98
+ # returns a reference to the working directory
99
+ # @rsync.dir.path
100
+ # @rsync.dir.writeable?
101
+ def dir
102
+ @working_directory
103
+ end
104
+
105
+ # returns reference to the rsync repository directory
106
+ # @rsync.dir.path
107
+ def repo
108
+ @repository
109
+ end
110
+
111
+ def set_working(work_dir, check = true)
112
+ @lib = nil
113
+ @working_directory = Rsync::WorkingDirectory.new(work_dir.to_s, check)
114
+ end
115
+
116
+ # changes current working directory for a block
117
+ # to the rsync working directory
118
+ #
119
+ # example
120
+ # @rsync.chdir do
121
+ # # write files
122
+ # @rsync.add
123
+ # @rsync.commit('message')
124
+ # end
125
+ def chdir # :yields: the Rsync::Path
126
+ Dir.chdir(dir.path) do
127
+ yield dir.path
128
+ end
129
+ end
130
+
131
+ # returns the repository size in bytes
132
+ def repo_size
133
+ size = 0
134
+ Dir.chdir(repo.path) do
135
+ (size, dot) = `du -s`.chomp.split
136
+ end
137
+ size.to_i
138
+ end
139
+
140
+ # returns a Rsync::Status object
141
+ def status
142
+ Rsync::Status.new(self)
143
+ end
144
+
145
+ # this is a convenience method for accessing the class that wraps all the
146
+ # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
147
+ # class with one that uses native methods or libgit C bindings
148
+ def lib
149
+ @lib ||= Rsync::Lib.new(self, @logger)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,835 @@
1
+ # encoding: UTF-8
2
+
3
+ # TODO: Add Rsync add, commit and push (Update should be pull?), either with staging like area like Git, or add is pack into .pack, and commit is update sum ?
4
+ # TODO: Seperate command lib from custom layer over rsync?
5
+
6
+ module Six
7
+ module Repositories
8
+ module Rsync
9
+ DIR_RSYNC = '.rsync'
10
+ DIR_PACK = File.join(DIR_RSYNC, '.pack')
11
+ REGEX_FOLDER = /(.*)[\\|\/](.*)/
12
+
13
+ class RsyncExecuteError < StandardError
14
+ end
15
+
16
+ class RsyncError < StandardError
17
+ end
18
+
19
+ class Lib
20
+ attr_accessor :verbose
21
+ PROTECTED = false
22
+ WINDRIVE = /\"(\w)\:/
23
+ DEFAULT_CONFIG = {:hosts => [], :exclude => []}.to_yaml
24
+ PARAMS = if PROTECTED
25
+ "--dry-run --times -O --no-whole-file -r --delete --progress -h --exclude=.rsync"
26
+ else
27
+ "--times -O --no-whole-file -r --delete --progress -h --exclude=.rsync"
28
+ end
29
+
30
+ def initialize(base = nil, logger = nil)
31
+ @rsync_dir = nil
32
+ @rsync_work_dir = nil
33
+ @path = nil
34
+ @stats = false
35
+ @verbose = true
36
+
37
+ @repos_local = {:pack => Hash.new, :wd => Hash.new, :version => 0}
38
+ @repos_remote = {:pack => Hash.new, :wd => Hash.new, :version => 0}
39
+
40
+ if base.is_a?(Rsync::Base)
41
+ @rsync_dir = base.repo.path
42
+ @rsync_work_dir = base.dir.path if base.dir
43
+ elsif base.is_a?(Hash)
44
+ @rsync_dir = base[:repository]
45
+ @rsync_work_dir = base[:working_directory]
46
+ end
47
+ @logger = logger
48
+
49
+ etc = File.join(TOOLS_PATH, 'etc')
50
+ FileUtils.mkdir_p etc
51
+ fstab = File.join(etc, 'fstab')
52
+ str = ""
53
+ str = File.open(fstab) {|file| file.read} if FileTest.exist?(fstab)
54
+ unless str[/cygdrive/]
55
+ str += "\nnone /cygdrive cygdrive user,noacl,posix=0 0 0\n"
56
+ File.open(fstab, 'w') {|file| file.puts str}
57
+ end
58
+ end
59
+
60
+ def init
61
+ @logger.info "Processing: #{rsync_path}"
62
+ if FileTest.exist? rsync_path
63
+ @logger.error "Seems to already be an Rsync repository, Aborting!"
64
+ raise RsyncError
65
+ end
66
+ if FileTest.exist? @rsync_work_dir
67
+ @logger.error "Seems to already be a folder, Aborting!"
68
+ raise RsyncError
69
+ end
70
+ FileUtils.mkdir_p pack_path
71
+ save_config(config)
72
+ save_repos(:local)
73
+ save_repos(:remote)
74
+ end
75
+
76
+ def clone(repository, name, opts = {})
77
+ @path = opts[:path] || '.'
78
+ @rsync_work_dir = opts[:path] ? File.join(@path, name) : name
79
+
80
+ # TODO: Solve logger mess completely.
81
+ @logger = opts[:log] if opts[:log]
82
+
83
+ case repository
84
+ when Array
85
+ config[:hosts] += repository
86
+ when String
87
+ config[:hosts] << repository
88
+ end
89
+
90
+ begin
91
+ init
92
+
93
+ # TODO: Eval move to update?
94
+ arr_opts = []
95
+ arr_opts << "-I" if opts[:force]
96
+ begin
97
+ update('', arr_opts)
98
+ rescue RsyncError
99
+ @logger.error "Unable to sucessfully update, aborting..."
100
+ # Dangerous? :D
101
+ FileUtils.rm_rf @rsync_work_dir if File.exists?(@rsync_work_dir)
102
+ #rescue
103
+ # FileUtils.rm_rf @rsync_work_dir if File.exists?(@rsync_work_dir)
104
+ end
105
+ rescue RsyncError
106
+ @logger.error "Unable to initialize"
107
+ end
108
+
109
+ opts[:bare] ? {:repository => @rsync_work_dir} : {:working_directory => @rsync_work_dir}
110
+ end
111
+
112
+ def update(cmd, x_opts = [], opts = {})
113
+ @logger.info "Checking for updates..."
114
+ @config = load_config
115
+ unless @config
116
+ @logger.error "Not an Rsync repository!"
117
+ raise RsyncError
118
+ end
119
+
120
+ unless config[:hosts].size > 0
121
+ @logger.error "No hosts configured!"
122
+ raise RsyncError
123
+ end
124
+
125
+ #unpack
126
+
127
+ # FIXME: This does not work when not forced, as host is sampled in comparesums :)
128
+ host = config[:hosts].sample
129
+ @logger.info "Trying: #{host}, please wait..."
130
+
131
+ if opts[:force]
132
+ arr_opts = []
133
+ arr_opts << PARAMS
134
+ arr_opts += x_opts
135
+
136
+ # TODO: UNCLUSTERFUCK
137
+ arr_opts << esc(File.join(host, '.pack/.'))
138
+ arr_opts << esc(pack_path)
139
+
140
+ command(cmd, arr_opts)
141
+ calc
142
+ save_repos
143
+ else
144
+ #reset(:hard => true)
145
+ calc
146
+ save_repos
147
+
148
+ # fetch latest sums and only update when changed
149
+ compare_sums(true, host)
150
+ end
151
+ end
152
+
153
+ # TODO: Allow local-self healing, AND remote healing. reset and fetch?
154
+ def reset(opts = {})
155
+ @logger.info "Resetting!"
156
+ if opts[:hard]
157
+ @config = load_config
158
+ calc
159
+ save_repos
160
+ compare_sums(false)
161
+ end
162
+ end
163
+
164
+ # TODO: WIP
165
+ def add(file)
166
+ @logger.error "Please use commit instead!"
167
+ return
168
+ @logger.info "Adding #{file}"
169
+ if (file == ".")
170
+ load_repos(:remote)
171
+ @logger.info "Calculating Checksums..."
172
+ ar = Dir[File.join(@rsync_work_dir, '/**/*')]
173
+
174
+ change = false
175
+ i = 0
176
+ ar.each do |file|
177
+ i += 1
178
+ unless file[/\.gz\Z/]
179
+ relative = file.clone
180
+ relative.gsub!(@rsync_work_dir, '')
181
+ relative.gsub!(/\A[\\|\/]/, '')
182
+
183
+ checksum = md5(file)
184
+ if checksum != @repos_remote[:wd][relative]
185
+ change = true
186
+ @logger.info "Packing #{i}/#{ar.size}: #{file}"
187
+ gzip(file)
188
+ @repos_remote[:wd][relative] = checksum
189
+ @repos_remote[:pack]["#{relative}.gz"] = md5("#{file}.gz")
190
+ FileUtils.mv("#{file}.gz", pack_path("#{relative}.gz"))
191
+ end
192
+ end
193
+ end
194
+ if change
195
+ save_repos
196
+ #File.open(File.join(@rsync_work_dir, '.sums.yml'), 'w') { |file| file.puts remote_wd[:list].sort.to_yaml }
197
+ #File.open(pack_path('.sums.yml'), 'w') { |file| file.puts remote_pack[:list].sort.to_yaml }
198
+ end
199
+ else
200
+
201
+ end
202
+ end
203
+
204
+ def commit
205
+ @logger.info "Committing changes on #{@rsync_work_dir}"
206
+ @config = load_config
207
+ unless @config
208
+ @logger.error "Not an Rsync repository!"
209
+ return
210
+ end
211
+
212
+ unless config[:hosts].size > 0
213
+ @logger.error "No hosts configured!"
214
+ return
215
+ end
216
+
217
+ load_repos(:local)
218
+ load_repos(:remote)
219
+
220
+ @logger.info "Calculating Checksums..."
221
+ @repos_local[:wd] = calc_sums(:wd)
222
+ # Added or Changed files
223
+ ar = Dir[File.join(@rsync_work_dir, '/**/*')]
224
+
225
+
226
+ change = false
227
+ i = 0
228
+ @repos_local[:wd].each_pair do |key, value|
229
+ i += 1
230
+ if value != @repos_remote[:wd][key]
231
+ change = true
232
+ @logger.info "Packing #{i}/#{@repos_local[:wd].size}: #{key}"
233
+ file = File.join(@rsync_work_dir, key)
234
+ file[REGEX_FOLDER]
235
+ folder = $2
236
+ gzip(file)
237
+ @repos_local[:pack]["#{key}.gz"] = md5("#{file}.gz")
238
+ FileUtils.mkdir_p pack_path(folder) if folder
239
+ FileUtils.mv("#{file}.gz", pack_path("#{key}.gz"))
240
+ end
241
+ end
242
+
243
+ =begin
244
+ i = 0
245
+ ar.each do |file|
246
+ i += 1
247
+ unless file[/\.gz\Z/]
248
+ relative = file.clone
249
+ relative.gsub!(@rsync_work_dir, '')
250
+ relative.gsub!(/\A[\\|\/]/, '')
251
+ #checksum = md5(file)
252
+ if @repos_local[:wd][relative] != @repos_remote[:wd][relative]
253
+ relative[/(.*)\/(.*)/]
254
+ folder = $1
255
+ change = true
256
+ @logger.info "Packing #{i}/#{ar.size}: #{relative}"
257
+ gzip(file)
258
+ #@repos_local[:wd][relative] = checksum
259
+ @repos_local[:pack]["#{relative}.gz"] = md5("#{file}.gz")
260
+ FileUtils.mkdir_p pack_path(folder) if folder
261
+ FileUtils.mv("#{file}.gz", pack_path("#{relative}.gz"))
262
+ end
263
+ end
264
+ end
265
+ =end
266
+ # Deleted files
267
+ @logger.info "Checking for deleted files..."
268
+
269
+ @repos_remote[:wd].each_pair do |key, value|
270
+ i += 1
271
+ if @repos_local[:wd][key].nil?
272
+ packed = "#{key}.gz"
273
+ change = true
274
+ file = pack_path(packed)
275
+ file[REGEX_FOLDER]
276
+ folder = $2
277
+
278
+ @logger.info "Removing #{i}/#{@repos_remote[:wd].size}: #{packed}"
279
+ @repos_local[:wd].delete key
280
+ @repos_local[:pack].delete packed
281
+ FileUtils.rm_f(file) if File.exists?(file)
282
+ end
283
+ end
284
+
285
+ =begin
286
+ p @repos_local[:wd]
287
+ ar2 = Dir[File.join(@rsync_work_dir, '/.rsync/.pack/**/*')]
288
+ i = 0
289
+ ar2.each do |file|
290
+ i += 1
291
+ if file[/\.gz\Z/]
292
+ relative = file.clone
293
+ relative.gsub!(@rsync_work_dir, '')
294
+ relative.gsub!(/\A[\\|\/]\.rsync[\\|\/]\.pack[\\|\/]/, '')
295
+ local = relative.clone
296
+ local.gsub!(/\.gz\Z/, '')
297
+ p file
298
+ p local
299
+ p @repos_local[:wd][local]
300
+ puts
301
+ if @repos_local[:wd][local].nil?
302
+ relative[/(.*)\/(.*)/]
303
+ folder = $1
304
+ change = true
305
+ @logger.info "Deleting #{i}/#{ar2.size}: #{relative}"
306
+ @repos_local[:wd].delete local
307
+ @repos_local[:pack].delete relative
308
+ FileUtils.rm_f(file)
309
+ end
310
+ end
311
+ end
312
+ =end
313
+
314
+ #gets
315
+ if change
316
+ @logger.info "Changes found!"
317
+ cmd = ''
318
+ save_repos(:local)
319
+
320
+ # TODO: Change to repositories.yml
321
+ host = config[:hosts].sample
322
+ verfile_srv = File.join(".pack", ".repository.yml")
323
+ begin
324
+ verbose = @verbose
325
+ @verbose = false
326
+ fetch_file(verfile_srv, host)
327
+ @verbose = verbose
328
+ rescue
329
+ @verbose = verbose
330
+ # FIXME: Should never assume that :)
331
+ @logger.warn "Unable to retrieve version file from server, repository probably doesnt exist!"
332
+ # raise RsyncExecuteError
333
+ end
334
+ load_repos(:remote)
335
+ if @repos_local[:version] < @repos_remote[:version] # && !force
336
+ @logger.warn "WARNING, version on server is NEWER, aborting!"
337
+ raise RsyncError
338
+ end
339
+ @repos_local[:version] += 1
340
+ @repos_remote[:version] = @repos_local[:version]
341
+ @repos_remote[:pack] = @repos_local[:pack].clone
342
+ @repos_remote[:wd] = @repos_local[:wd].clone
343
+ save_repos(:remote)
344
+ save_repos(:local)
345
+ push(host)
346
+ else
347
+ @logger.info "No changes found!"
348
+ end
349
+ end
350
+
351
+ def push(host = nil)
352
+ @logger.info "Pushing..."
353
+ @config = load_config
354
+ host = config[:hosts].sample unless host
355
+ # TODO: UNCLUSTERFUCK
356
+ arr_opts = []
357
+ arr_opts << PARAMS
358
+
359
+ # Upload .pack changes
360
+ if host[/\A(\w)*\@/]
361
+ arr_opts << "-e ssh"
362
+ end
363
+ arr_opts << esc(pack_path('.'))
364
+ arr_opts << esc(File.join(host, '.pack'))
365
+
366
+ command('', arr_opts)
367
+ end
368
+
369
+ def compare_set(typ, host, online = true)
370
+ load_repos(:local)
371
+ load_repos(:remote)
372
+
373
+ #if local[typ][:md5] == remote[typ][:md5]
374
+ # @logger.info "#{typ} Match!"
375
+ #else
376
+ # @logger.info "#{typ} NOT match, updating!"
377
+
378
+ mismatch = []
379
+ @repos_remote[typ].each_pair do |key, value|
380
+ if value == @repos_local[typ][key]
381
+ #@logger.info "Match! #{key}"
382
+ else
383
+ @logger.debug "Mismatch! #{key}"
384
+ mismatch << key
385
+ end
386
+ end
387
+
388
+ if mismatch.size > 0
389
+ case typ
390
+ when :pack
391
+ # direct unpack of gz into working folder
392
+ # Update file
393
+ if online
394
+ # TODO: Progress bar
395
+ if mismatch.count > (@repos_remote[typ].count / 4)
396
+ @logger.info "Many files mismatched (#{mismatch.count}), running full update on .pack folder"
397
+ arr_opts = []
398
+ arr_opts << PARAMS
399
+ arr_opts << File.join(host, '.pack/.')
400
+ arr_opts << esc(pack_path)
401
+
402
+ command('', arr_opts)
403
+ else
404
+ c = mismatch.size
405
+ i = 0
406
+ mismatch.each do |e|
407
+ # TODO: Nicer progress bar...
408
+ i += 1
409
+ @logger.info "Fetching #{i}/#{c}: #{e}"
410
+ fetch_file(File.join(".pack", e), host)
411
+ end
412
+ end
413
+ end
414
+ when :wd
415
+ c = mismatch.size
416
+ i = 0
417
+ mismatch.each do |e|
418
+ # TODO: Nicer progress bar...
419
+ i += 1
420
+ @logger.info "Unpacking #{i}/#{c}: #{e}"
421
+ unpack(:path => "#{e}.gz")
422
+ end
423
+ end
424
+ end
425
+
426
+ del = []
427
+ @repos_local[typ].each_pair do |key, value|
428
+ if @repos_remote[typ][key].nil?
429
+ @logger.info "File does not exist in remote! #{key}"
430
+ del << key unless config[:exclude].include?(key)
431
+ end
432
+ end
433
+ del.each { |e| del_file(e, typ) }
434
+ @repos_local[typ] = calc_sums(typ)
435
+ @repos_local[:version] = @repos_remote[:version]
436
+ save_repos
437
+ end
438
+
439
+ def compare_sums(online = true, host = config[:hosts].sample)
440
+ hosts = config[:hosts].clone
441
+ done = false
442
+
443
+ ## Pack
444
+ if online
445
+ b = false
446
+ while hosts.size > 0 && !done do
447
+ # FIXME: Nasty
448
+ if b
449
+ host = hosts.sample
450
+ end
451
+ b = true
452
+ hosts -= [host]
453
+
454
+ begin
455
+ verbose = @verbose
456
+ @verbose = false
457
+ fetch_file(".pack/.repository.yml", host)
458
+ @verbose = verbose
459
+
460
+ load_repos(:remote)
461
+ load_repos(:local)
462
+
463
+ if @repos_local[:version] > @repos_remote[:version] # && !force
464
+ @logger.warn "WARNING, version on server is OLDER, aborting!"
465
+ raise RsyncError
466
+ end
467
+ done = true
468
+ rescue
469
+ @logger.info "Failed #{host}, trying next.."
470
+ end
471
+ end
472
+ # TODO: CLEANUP, Should depricate in time.
473
+ if FileTest.exists? pack_path('.repository.yml')
474
+ [pack_path('.version'), pack_path('.sums.yml'), File.join(@rsync_work_dir, '.sums.yml')].each do |f|
475
+ FileUtils.rm_f f if FileTest.exists? f
476
+ end
477
+ end
478
+ end
479
+ if done || online
480
+ # TODO: Don't do actions when not online
481
+ @logger.info "Verifying Packed files..."
482
+ compare_set(:pack, host)
483
+ @logger.info "Verifying Unpacked files..."
484
+ compare_set(:wd, host)
485
+ save_repos
486
+ end
487
+ end
488
+
489
+ private
490
+ def config
491
+ cfg = @config ||= YAML::load(DEFAULT_CONFIG)
492
+ cfg[:exclude] = [] unless cfg[:exclude]
493
+ cfg[:hosts] = [] unless cfg[:hosts]
494
+ cfg
495
+ end
496
+
497
+ def rsync_path(path = '')
498
+ p = File.join(@rsync_work_dir, DIR_RSYNC)
499
+ p = File.join(p, path) unless path.size == 0
500
+ p
501
+ end
502
+
503
+ def pack_path(path = '')
504
+ p = File.join(@rsync_work_dir, DIR_PACK)
505
+ p = File.join(p, path) unless path.size == 0
506
+ p
507
+ end
508
+
509
+ def esc(val)
510
+ "\"#{val}\""
511
+ end
512
+
513
+ def escape(s)
514
+ "\"" + s.to_s.gsub('\"', '\"\\\"\"') + "\""
515
+ end
516
+
517
+ def fetch_file(path, host)
518
+ path[/(.*)\/(.*)/]
519
+ folder, file = $1, $2
520
+ folder = "." unless folder
521
+ file = path unless file
522
+ # Only fetch a specific file
523
+ @logger.debug "Fetching #{path} from #{host}"
524
+ arr_opts = []
525
+ arr_opts << PARAMS
526
+ if host[/\A(\w)*\@/]
527
+ arr_opts << "-e ssh"
528
+ end
529
+ arr_opts << esc(File.join(host, path))
530
+ arr_opts << esc(rsync_path(folder))
531
+
532
+ command('', arr_opts)
533
+ end
534
+
535
+ def calc
536
+ [:pack, :wd].each { |t| @repos_local[t] = calc_sums(t) }
537
+ end
538
+
539
+ def calc_sums(typ)
540
+ @logger.debug "Calculating checksums of #{typ} files"
541
+ ar = []
542
+ reg = case typ
543
+ when :pack
544
+ ar = Dir[pack_path('**/*')]
545
+ /\A[\\|\/]\.rsync[\\|\/]\.pack[\\|\/]/
546
+ when :wd
547
+ ar = Dir[File.join(@rsync_work_dir, '/**/*')]
548
+ /\A[\\|\/]/
549
+ end
550
+ h = Hash.new
551
+ ar.each do |file|
552
+ relative = file.clone
553
+ relative.gsub!(@rsync_work_dir, '')
554
+ relative.gsub!(reg, '')
555
+
556
+ sum = md5(file)
557
+ h[relative] = sum if sum && !config[:exclude].include?(relative)
558
+ end
559
+ h
560
+ end
561
+
562
+ def load_config
563
+ # TODO: Remove after a while, depricated .pack
564
+ old = File.join(@rsync_work_dir, '.pack')
565
+ FileUtils.mv(old, pack_path) if FileTest.exists?(old)
566
+ load_yaml(File.join(rsync_path, 'config.yml'))
567
+ end
568
+
569
+ def load_yaml(file)
570
+ if FileTest.exist?(file)
571
+ YAML::load_file(file)
572
+ else
573
+ nil
574
+ end
575
+ end
576
+
577
+ def save_default_config
578
+ FileUtils.mkdir_p rsync_path
579
+ save_config(config)
580
+ end
581
+
582
+ def save_config(config = YAML::load(DEFAULT_CONFIG))
583
+ File.open(File.join(rsync_path, 'config.yml'), 'w') { |file| file.puts config.to_yaml }
584
+ end
585
+
586
+ def save_repos(typ = :local)
587
+ file, config = nil, nil
588
+ case typ
589
+ when :local
590
+ file = rsync_path('.repository.yml')
591
+ config = @repos_local.clone
592
+ when :remote
593
+ file = pack_path('.repository.yml')
594
+ config = @repos_remote.clone
595
+ end
596
+ config[:pack] = config[:pack].sort
597
+ config[:wd] = config[:wd].sort
598
+ File.open(file, 'w') { |file| file.puts config.to_yaml }
599
+
600
+ # TODO: CLEANUP, Should depricate in time.
601
+ [File.join(@rsync_work_dir, '.rsync/.version'), File.join(@rsync_work_dir, '.rsync/sums_pack.yml'), File.join(@rsync_work_dir, '.rsync/sums_wd.yml')].each do |f|
602
+ FileUtils.rm_f f if FileTest.exists? f
603
+ end
604
+ end
605
+
606
+ def load_repos(typ)
607
+ config = Hash.new
608
+ case typ
609
+ when :local
610
+ File.open(rsync_path('.repository.yml')) { |file| config = YAML::load(file) }
611
+ when :remote
612
+ if FileTest.exists?(pack_path('.repository.yml'))
613
+ File.open(pack_path('.repository.yml')) { |file| config = YAML::load(file) }
614
+ else
615
+ # Deprecated
616
+ config[:wd] = File.open(File.join(@rsync_work_dir, '.sums.yml')) { |file| YAML::load(file) }
617
+ config[:pack] = File.open(pack_path('.sums.yml')) { |file| YAML::load(file) }
618
+ config[:version] = File.open(pack_path('.version')) { |file| file.read.to_i }
619
+ end
620
+ end
621
+
622
+ [:wd, :pack].each do |t|
623
+ h = Hash.new
624
+ config[t].each { |e| h[e[0]] = e[1] }
625
+ config[t] = h
626
+ end
627
+
628
+ case typ
629
+ when :local
630
+ @repos_local = config
631
+ when :remote
632
+ @repos_remote = config
633
+ end
634
+ end
635
+
636
+ def del_file(file, typ, opts = {})
637
+ file = case typ
638
+ when :pack
639
+ File.join(DIR_PACK, file)
640
+ when :wd
641
+ file
642
+ end
643
+ FileUtils.rm_f File.join(@rsync_work_dir, file)
644
+ end
645
+
646
+ def md5(path)
647
+ unless File.directory? path
648
+ path[/(.*)[\/|\\](.*)/]
649
+ folder, file = $1, $2
650
+ Dir.chdir(folder) do
651
+ r = %x[md5sum #{esc(file)}]
652
+ @logger.debug r
653
+ r[/\A\w*/]
654
+ end
655
+ end
656
+ end
657
+
658
+ def zip7(file)
659
+ out = %x[7z x #{esc(file)} -y]
660
+ @logger.debug out
661
+ out
662
+ end
663
+
664
+ def gzip(file)
665
+ @logger.info "Packing #{file}"
666
+ out = %x[gzip -f --best --rsyncable --keep #{esc(file)}]
667
+ @logger.debug out
668
+ end
669
+
670
+ def unpack_file(file, path)
671
+ Dir.chdir(path) do |dir|
672
+ zip7(file)
673
+ # TODO: Evaluate if this is actually wanted / useful at all..
674
+ =begin
675
+ if file[/\.tar\.?/]
676
+ file[/(.*)\/(.*)/]
677
+ fil = $2
678
+ fil = file unless fil
679
+ f2 = fil.gsub('.gz', '')
680
+ zip7(f2)
681
+ FileUtils.rm_f f2
682
+ end
683
+ =end
684
+ end
685
+ end
686
+
687
+ def unpack(opts = {})
688
+ items = if opts[:path]
689
+ [pack_path(opts[:path])]
690
+ else
691
+ Dir[pack_path('**/*')]
692
+ end
693
+
694
+ items.each do |file|
695
+ unless File.directory? file
696
+ relative = file.clone
697
+ relative.gsub!(@rsync_work_dir, '')
698
+ relative.gsub!(/\A[\\|\/]\.rsync[\\|\/]\.pack[\\|\/]/, '')
699
+ fil = relative
700
+ folder = "."
701
+ folder, fil = $1, $2 if relative[/(.*)\/(.*)/]
702
+ #puts "Relative: #{relative}, Folder: #{folder}, File: #{fil} (Origin: #{file})"
703
+
704
+ path = File.join(@rsync_work_dir, folder)
705
+ FileUtils.mkdir_p path
706
+ unpack_file(file, path)
707
+ end
708
+ end
709
+ end
710
+
711
+ def command_lines(cmd, opts = [], chdir = true, redirect = '')
712
+ command(cmd, opts, chdir).split("\n")
713
+ end
714
+
715
+ def command(cmd, opts = [], chdir = true, redirect = '', &block)
716
+ path = @rsync_work_dir || @rsync_dir || @path
717
+
718
+ opts << "--stats" if @stats
719
+
720
+ opts = [opts].flatten.map {|s| s }.join(' ') # escape()
721
+ rsync_cmd = "rsync #{cmd} #{opts} #{redirect} 2>&1"
722
+
723
+ while rsync_cmd[WINDRIVE] do
724
+ drive = rsync_cmd[WINDRIVE]
725
+ #if ENV['six-app-root']
726
+ # rsync_cmd.gsub!(drive, "\"#{ENV['six-app-root']}") # /cygdrive/#{$1}
727
+ #else
728
+ rsync_cmd.gsub!(drive, "\"/cygdrive/#{$1}")
729
+ #end
730
+ end
731
+
732
+ if @logger
733
+ @logger.debug(rsync_cmd)
734
+ end
735
+
736
+ out = nil
737
+ if chdir && (Dir.getwd != path)
738
+ Dir.chdir(path) { out = run_command(rsync_cmd, &block) }
739
+ else
740
+ out = run_command(rsync_cmd, &block)
741
+ end
742
+
743
+ #@logger.debug(out)
744
+
745
+ out
746
+ end
747
+
748
+ def run_command(rsync_cmd, &block)
749
+ # TODO: Make this switchable? Verbosity ?
750
+ # Or actually parse this live for own stats?
751
+ #puts rsync_cmd
752
+ s = nil
753
+ out = ''
754
+ $stdout.sync = true # Seems to fix C:/Packaging/six-updater/NEW - Copy/ruby/lib/ruby/gems/1.9.1/gems/log4r-1.0.5/lib/log4r/outputter/iooutputter.rb:43:in `flush': Broken pipe (Errno::EPIPE)
755
+
756
+ # Simpler method but on windows the !? exitstatus is not working properly..
757
+ # Does nicely display error output in logwindow though
758
+ #io = IO.popen(rsync_cmd)
759
+ #io.sync = true
760
+ #io.each do |buffer|
761
+ # process_msg buffer
762
+ # out << buffer
763
+ #end
764
+ status = Open3.popen3(rsync_cmd) { |io_in, io_out, io_err, waitth|
765
+ io_out.sync = true
766
+ io_err.sync = true
767
+
768
+ io_out.each do |buffer|
769
+ process_msg buffer
770
+ out << buffer
771
+ end
772
+
773
+ #while !io_out.eof?
774
+ # buffer = io_out.readline
775
+ # # print buf#.gsub("\r", '')
776
+ # process_msg buffer
777
+ # out << buffer
778
+ #end
779
+ error = io_err.gets
780
+ if error
781
+ puts "Error: " + error.chomp
782
+ # exit
783
+ end
784
+ # puts "Result: " + io_out.gets
785
+ s = waitth.value
786
+ }
787
+ # FIXME: This doesn't work with the new popen or is there a way?
788
+ if s.exitstatus > 0
789
+ if s.exitstatus == 1 && out == ''
790
+ return ''
791
+ end
792
+ raise Rsync::RsyncExecuteError.new(rsync_cmd + ':' + out.to_s)
793
+ end
794
+ status
795
+ end
796
+
797
+ def process_msg(msg)
798
+ if msg[/[k|m|g]?B\/s/i]
799
+ msg.gsub!("\n", '')
800
+ print "#{msg}\r" if @verbose
801
+ else
802
+ @logger.debug msg
803
+ print msg if @verbose
804
+ end
805
+ msg
806
+
807
+ =begin
808
+ m = nil
809
+ if msg[/\r/]
810
+ # TODO; must still be written even if there is no next message :P
811
+
812
+ if @write
813
+ print msg
814
+ end
815
+ m = msg.gsub("\r", '')
816
+ #@previous = m
817
+ else
818
+ m = msg
819
+ #if @previous
820
+ # @logger.debug @previous
821
+ # @previous = nil
822
+ #end
823
+ #unless @previous
824
+ # @logger.debug m
825
+ #end
826
+ @logger.debug m
827
+ puts m if @write
828
+ end
829
+ m
830
+ =end
831
+ end
832
+ end
833
+ end
834
+ end
835
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ module Six
4
+ module Repositories
5
+ module Rsync
6
+ class Path
7
+
8
+ attr_accessor :path
9
+
10
+ def initialize(path, check_path = true)
11
+ if !check_path || File.exists?(path)
12
+ @path = File.expand_path(path)
13
+ else
14
+ raise ArgumentError, "path does not exist", File.expand_path(path)
15
+ end
16
+ end
17
+
18
+ def readable?
19
+ File.readable?(@path)
20
+ end
21
+
22
+ def writable?
23
+ File.writable?(@path)
24
+ end
25
+
26
+ def to_s
27
+ @path
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: UTF-8
2
+
3
+ module Six
4
+ module Repositories
5
+ module Rsync
6
+ class Repository < Path
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: UTF-8
2
+
3
+ module Six
4
+ module Repositories
5
+ module Rsync
6
+ class WorkingDirectory < Path
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/six/rsync.rb ADDED
@@ -0,0 +1,145 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+ require 'yaml'
6
+
7
+ require 'six/rsync/path'
8
+ require 'six/rsync/repository'
9
+ require 'six/rsync/working_directory'
10
+ require 'six/rsync/lib'
11
+ require 'six/rsync/base'
12
+ #require 'six/popen'
13
+
14
+ require 'open3'
15
+ #require 'win32/open3'
16
+
17
+ if RUBY_VERSION == "1.8.7"
18
+ class Array
19
+ def sample
20
+ self[rand self.size]
21
+ end
22
+ end
23
+ end
24
+
25
+ module Six
26
+ module Repositories
27
+ module Md5
28
+ end
29
+
30
+ module Rsync
31
+ VERSION = '0.0.1'
32
+ BASE_PATH = Dir.pwd
33
+ TOOLS_PATH = File.join(BASE_PATH, 'tools')
34
+ FOLDER = /(.*)\/(.*)/
35
+ ENV['PATH'] = ENV['PATH'] + ";#{TOOLS_PATH};#{File.join(TOOLS_PATH, 'bin')}"
36
+ # No meaning on Cygwin 1.7
37
+ # ENV['CYGWIN'] = "nontsec"
38
+
39
+ # open a bare repository
40
+ #
41
+ # this takes the path to a bare rsync repo
42
+ # it expects not to be able to use a working directory
43
+ # so you can't checkout stuff, commit things, etc.
44
+ # but you can do most read operations
45
+ def self.bare(rsync_dir, options = {})
46
+ Base.bare(rsync_dir, options)
47
+ end
48
+
49
+ # open an existing rsync working directory
50
+ #
51
+ # this will most likely be the most common way to create
52
+ # a rsync reference, referring to a working directory.
53
+ # if not provided in the options, the library will assume
54
+ # your rsync_dir is in the default place (.rsync/)
55
+ #
56
+ # options
57
+ # :repository => '/path/to/alt_rsync_dir'
58
+ # :index => '/path/to/alt_index_file'
59
+ def self.open(working_dir, options = {})
60
+ Base.open(working_dir, options)
61
+ end
62
+
63
+ # initialize a new rsync repository, defaults to the current working directory
64
+ #
65
+ # options
66
+ # :repository => '/path/to/alt_rsync_dir'
67
+ # :index => '/path/to/alt_index_file'
68
+ def self.init(working_dir = '.', options = {})
69
+ Base.init(working_dir, options)
70
+ end
71
+
72
+ # clones a remote repository
73
+ #
74
+ # options
75
+ # :bare => true (does a bare clone)
76
+ # :repository => '/path/to/alt_rsync_dir'
77
+ # :index => '/path/to/alt_index_file'
78
+ #
79
+ # example
80
+ # Rsync.clone('rsync://repo.or.cz/ruby', 'clone', :bare => true)
81
+ #
82
+ def self.clone(repository, name, options = {})
83
+ Base.clone(repository, name, options)
84
+ end
85
+
86
+ =begin
87
+ # Export the current HEAD (or a branch, if <tt>options[:branch]</tt>
88
+ # is specified) into the +name+ directory, then remove all traces of rsync from the
89
+ # directory.
90
+ #
91
+ # See +clone+ for options. Does not obey the <tt>:remote</tt> option,
92
+ # since the rsync info will be deleted anyway; always uses the default
93
+ # remote, 'origin.'
94
+ def self.export(repository, name, options = {})
95
+ options.delete(:remote)
96
+ repo = clone(repository, name, {:depth => 1}.merge(options))
97
+ repo.checkout("origin/#{options[:branch]}") if options[:branch]
98
+ Dir.chdir(repo.dir.to_s) { FileUtils.rm_r '.rsync' }
99
+ end
100
+
101
+ #g.config('user.name', 'Scott Chacon') # sets value
102
+ #g.config('user.email', 'email@email.com') # sets value
103
+ #g.config('user.name') # returns 'Scott Chacon'
104
+ #g.config # returns whole config hash
105
+ def config(name = nil, value = nil)
106
+ lib = Rsync::Lib.new
107
+ if(name && value)
108
+ # set value
109
+ lib.config_set(name, value)
110
+ elsif (name)
111
+ # return value
112
+ lib.config_get(name)
113
+ else
114
+ # return hash
115
+ lib.config_list
116
+ end
117
+ end
118
+
119
+ # Same as g.config, but forces it to be at the global level
120
+ #
121
+ #g.config('user.name', 'Scott Chacon') # sets value
122
+ #g.config('user.email', 'email@email.com') # sets value
123
+ #g.config('user.name') # returns 'Scott Chacon'
124
+ #g.config # returns whole config hash
125
+ def self.global_config(name = nil, value = nil)
126
+ lib = Rsync::Lib.new(nil, nil)
127
+ if(name && value)
128
+ # set value
129
+ lib.global_config_set(name, value)
130
+ elsif (name)
131
+ # return value
132
+ lib.global_config_get(name)
133
+ else
134
+ # return hash
135
+ lib.global_config_list
136
+ end
137
+ end
138
+
139
+ def global_config(name = nil, value = nil)
140
+ self.class.global_config(name, value)
141
+ end
142
+ =end
143
+ end
144
+ end
145
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: six-rsync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sickboy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-25 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Your summary here
17
+ email: sb@dev-heaven.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - LICENSE
25
+ files:
26
+ - LICENSE
27
+ - README
28
+ - Rakefile
29
+ - lib/six/rsync/base.rb
30
+ - lib/six/rsync/lib.rb
31
+ - lib/six/rsync/path.rb
32
+ - lib/six/rsync/repository.rb
33
+ - lib/six/rsync/working_directory.rb
34
+ - lib/six/rsync.rb
35
+ has_rdoc: true
36
+ homepage:
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Your summary here
63
+ test_files: []
64
+