six-rsync 0.1.0

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