odisk 0.2.2 → 0.2.3

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/README.md CHANGED
@@ -25,9 +25,11 @@ which utilizes an alternative approach to dealing with multiple threads.
25
25
 
26
26
  ## <a name="release">Release Notes</a>
27
27
 
28
- ### Release 0.2.2
28
+ ### Release 0.2.3
29
29
 
30
- - Added odisk_forget which removes remote entries causing a file or directory to look like a new file or directory.
30
+ - Added odisk_forget which clears the local and remote digest of a specific file or directory.
31
+
32
+ - Added odisk_remove which marks a file or directory as removed so that all file or directory is removed from all local copies.
31
33
 
32
34
  # Plans and Notes
33
35
 
@@ -46,12 +48,6 @@ know when you run into bugs.
46
48
  - pass array of ignores to digest creation
47
49
  - loosen up current restriction on any file that begins with .
48
50
 
49
- - Support file removal
50
- - Detect file removal based on previous digest or by calling a remove script
51
- - Keep record of removals in digest
52
- - Note conflicts if modifications are more recent that removal
53
- - Use a script to pick remove or keep
54
-
55
51
  - Handle changes in mode, owner, and group
56
52
  - Compare to previous digest to detect changes
57
53
  - File modification times are not changed by mode, owner, or group changes
data/bin/odisk CHANGED
@@ -33,12 +33,18 @@ $plain = false
33
33
  $remote = ::ODisk::Remote.new()
34
34
  $crypter_count = 2
35
35
  $copier_count = 4
36
+ $careful = false
36
37
 
37
- opts = OptionParser.new('Usage: odisk [options] <local_directory>')
38
+ opts = OptionParser.new(%{Usage: odisk [options] <local_directory>
39
+
40
+ Synchronizes a local directory with a remote directory. The remote directory
41
+ is encrypted and compressed.
42
+ })
38
43
  opts.on('-s', 'decrease verbosity') { $verbose += 1 unless 5 == $verbose }
39
44
  opts.on('-v', 'increase verbosity') { $verbose -= 1 unless 0 == $verbose }
40
45
  opts.on('-d', 'generate digests only') { $digests_only = true }
41
46
  opts.on('-u', 'unencrypted or plain remote files') { $plain = true }
47
+ opts.on('-c', 'careful mode, no local removes') { $careful = true }
42
48
  opts.on('-n', 'dry run / no modifications') { $dry_run = true }
43
49
  opts.on('-r', '--remote [user@host:dir:pass_file]',
44
50
  String, 'remote user, host, directory, passphrase file for gpg') { |r|
@@ -59,7 +59,7 @@ unless ODisk.gather_remote_info($local_top, $remote)
59
59
  end
60
60
  $remote.pass_file = nil if $plain
61
61
 
62
- Thread.current[:name] = 'main'
62
+ Thread.current[:name] = 'forget'
63
63
  ::Opee::Env.logger.formatter = proc { |s,t,p,m|
64
64
  s = '' if s.nil?
65
65
  "#{s[0]} [#{t.strftime('%Y-%m-%dT%H:%M:%S.%6N')} ##{p}]: #{m}\n"
@@ -67,13 +67,7 @@ Thread.current[:name] = 'main'
67
67
  ::Opee::Env.logger.severity = $verbose
68
68
 
69
69
  if Logger::INFO >= $verbose
70
- if $digests_only
71
- ::Opee::Env.info(%{
72
- Generate Local Digests
73
- local directory: #{::File.expand_path($local_top)}
74
- })
75
- else
76
- ::Opee::Env.info(%{
70
+ ::Opee::Env.info(%{
77
71
  Forget "#{$forget_me}"
78
72
  remote host: #{$remote.host}
79
73
  remote user: #{$remote.user}
@@ -81,10 +75,9 @@ if Logger::INFO >= $verbose
81
75
  local: #{::File.expand_path($local_top)}
82
76
  dry run: #{$dry_run}
83
77
  })
84
- end
85
78
  end
86
79
 
87
- # If $local_top/.odisk/remote does not exist or is different that what is in $remote, replace it.
80
+ # If $local_top/.odisk/remote does not exist or is different than what is in $remote, replace it.
88
81
  remote_str = $remote.to_s
89
82
  top_remote_path = ::File.join($local_top, '.odisk', 'remote')
90
83
  if !::File.file?(top_remote_path) || ::File.read(top_remote_path).strip() != remote_str
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env ruby -wW1
2
+ # encoding: UTF-8
3
+
4
+ while (i = ARGV.index('-I'))
5
+ x,path = ARGV.slice!(i, 2)
6
+ $: << path
7
+ end
8
+
9
+ # TBD tmp for testing
10
+ $: << ::File.join(::File.dirname(__FILE__), "../../oj/lib")
11
+ $: << ::File.join(::File.dirname(__FILE__), "../../oj/ext")
12
+ $: << ::File.join(::File.dirname(__FILE__), "../../opee/lib")
13
+ $: << ::File.join(::File.dirname(__FILE__), "../lib")
14
+
15
+ require 'optparse'
16
+ begin
17
+ v = $VERBOSE
18
+ $VERBOSE = false
19
+ require 'net/ssh'
20
+ require 'net/sftp'
21
+ $VERBOSE = v
22
+ end
23
+ require 'opee'
24
+ require 'oj'
25
+ require 'odisk'
26
+
27
+ $verbose = Logger::WARN
28
+ $dry_run = false
29
+ $dir = '.'
30
+ $master = nil
31
+ $remote = ::ODisk::Remote.new()
32
+
33
+ opts = OptionParser.new(%{Usage: odisk_remove [options] <local_directory> <relative_remove_path>
34
+
35
+ Will remove data for the file or directory specified from the local file system as well as on the
36
+ remote server. The path will also be marked indication that the file or directory should be removed
37
+ from other local directories if that directory is synced with the remote site. To get rid of the
38
+ remove flag for the path use the oDisk_cleanup command or sync with a local as master.
39
+ })
40
+ opts.on('-s', 'decrease verbosity') { $verbose += 1 unless 5 == $verbose }
41
+ opts.on('-v', 'increase verbosity') { $verbose -= 1 unless 0 == $verbose }
42
+ opts.on('-n', 'dry run / no modifications') { $dry_run = true }
43
+ opts.on('-r', '--remote [user@host:dir:pass_file]',
44
+ String, 'remote user, host, directory, passphrase file for gpg') { |r|
45
+ $remote.update(r)
46
+ }
47
+ opts.on('-h', '--help', 'Show this display') { puts opts.help; Process.exit!(0) }
48
+ dirs = opts.parse(ARGV)
49
+
50
+ if 2 != dirs.size
51
+ puts opts.help
52
+ Process.exit!(0)
53
+ end
54
+ $local_top = ::File.expand_path(dirs[0])
55
+ $remove_me = dirs[1] # relative path from top
56
+
57
+ # TBD move this to odisk.rb
58
+ unless ODisk.gather_remote_info($local_top, $remote)
59
+ puts opts.help
60
+ Process.exit!(0)
61
+ end
62
+ $remote.pass_file = nil if $plain
63
+
64
+ Thread.current[:name] = 'remove'
65
+ ::Opee::Env.logger.formatter = proc { |s,t,p,m|
66
+ s = '' if s.nil?
67
+ "#{s[0]} [#{t.strftime('%Y-%m-%dT%H:%M:%S.%6N')} ##{p}]: #{m}\n"
68
+ }
69
+ ::Opee::Env.logger.severity = $verbose
70
+
71
+ if Logger::INFO >= $verbose
72
+ ::Opee::Env.info(%{
73
+ Remove "#{$remove_me}"
74
+ remote host: #{$remote.host}
75
+ remote user: #{$remote.user}
76
+ remote directory: #{$remote.dir}
77
+ local: #{::File.expand_path($local_top)}
78
+ dry run: #{$dry_run}
79
+ })
80
+ end
81
+
82
+ # If $local_top/.odisk/remote does not exist or is different than what is in $remote, replace it.
83
+ remote_str = $remote.to_s
84
+ top_remote_path = ::File.join($local_top, '.odisk', 'remote')
85
+ if !::File.file?(top_remote_path) || ::File.read(top_remote_path).strip() != remote_str
86
+ ::Opee::Env.info("Writing #{top_remote_path}")
87
+ unless $dry_run
88
+ `mkdir -p #{::File.join($local_top, '.odisk')}`
89
+ ::File.open(top_remote_path, 'w') { |f| f.write(remote_str + "\n") }
90
+ end
91
+ end
92
+
93
+ path = ::File.join($local_top, $remove_me)
94
+ local_digest_path = ::File.join(::File.dirname(path), '.odisk', 'digest.json')
95
+ remove_name = ::File.basename(path)
96
+ remote_path = ::File.join($remote.dir, $remove_me)
97
+ remote_digest_path = ::File.join(::File.dirname(remote_path), '.odisk', 'digest.json')
98
+
99
+ # Removed the local file.
100
+ `rm -rf "#{path}"`
101
+
102
+ # Remove remove_me from local digest.
103
+ digest = Oj.load_file(local_digest_path, mode: :object)
104
+ digest.delete(remove_name)
105
+ Oj.to_file(local_digest_path, digest, indent: 2) unless $dry_run
106
+
107
+ # Removed the remote file or directory.
108
+ Net::SSH.start($remote.host, $remote.user) do |ssh|
109
+ ssh.exec!(%{rm -rf "#{remote_path}.gpg"})
110
+ ssh.exec!(%{rm -rf "#{remote_path}"})
111
+ end
112
+
113
+ # Set the removed flag on the remove_me entry in the remote digest.
114
+ Net::SFTP.start($remote.host, $remote.user) do |ftp|
115
+ json = ftp.download!(remote_digest_path)
116
+ digest = Oj.load(json, mode: :object)
117
+ unless (entry = digest[remove_name]).nil?
118
+ entry.removed = true
119
+ end
120
+ json = Oj.dump(digest, indent: 2)
121
+ ftp.open!(remote_digest_path, 'w') { |res| ftp.write!(res[:handle], 0, json) }
122
+ end
@@ -1,3 +1,10 @@
1
+ begin
2
+ v = $VERBOSE
3
+ $VERBOSE = false
4
+ require 'net/ssh'
5
+ require 'net/sftp'
6
+ $VERBOSE = v
7
+ end
1
8
 
2
9
  module ODisk
3
10
  # Provides upload and download functionality using sftp and ssh on a single connection.
@@ -36,8 +43,12 @@ module ODisk
36
43
  ::Opee::Env.warn("Uploaded \"#{local}\"")
37
44
  rescue Net::SFTP::StatusException => e
38
45
  if Net::SFTP::Constants::StatusCodes::FX_NO_SUCH_FILE == e.code
39
- assure_dirs_exist(::File.dirname(remote))
40
- retry
46
+ begin
47
+ assure_dirs_exist(::File.dirname(remote))
48
+ retry
49
+ rescue Exception => e2
50
+ ::Opee::Env.error("Upload of \"#{local}\" failed: #{e2.class}: #{e2.message}")
51
+ end
41
52
  else
42
53
  ::Opee::Env.error("Upload of \"#{local}\" failed: #{e.class}: (#{e.code}) #{e.description}\n #{e.text}\n #{e.response}")
43
54
  end
@@ -64,11 +75,27 @@ module ODisk
64
75
  @copy_queue.ask(:ready, self)
65
76
  end
66
77
 
78
+ def remove_local(path)
79
+ `rm -rf "#{path}"` unless $dry_run
80
+ ::Opee::Env.warn("Removed local \"#{path}\"")
81
+ end
82
+
83
+ def remove_remote(path)
84
+ unless $dry_runb
85
+ @ssh = Net::SSH.start($remote.host, $remote.user) if @ssh.nil?
86
+ out = @ssh.exec!(%{rm -rf "#{path}" "#{path}.gpg"})
87
+ raise out unless out.nil? || out.strip().empty?
88
+ end
89
+ ::Opee::Env.warn("Removed remote \"#{path}\"")
90
+ end
91
+
67
92
  def assure_dirs_exist(dir)
68
93
  ::Opee::Env.info("creating remote dir \"#{dir}\"")
69
- @ssh = Net::SSH.start($remote.host, $remote.user) if @ssh.nil?
70
- out = @ssh.exec!(%{mkdir -p "#{dir}"})
71
- raise out unless out.nil? || out.strip().empty?
94
+ unless $dry_run
95
+ @ssh = Net::SSH.start($remote.host, $remote.user) if @ssh.nil?
96
+ out = @ssh.exec!(%{mkdir -p "#{dir}"})
97
+ raise 'Remote ' + out unless out.nil? || out.strip().empty?
98
+ end
72
99
  end
73
100
 
74
101
  end # Copier
@@ -42,7 +42,10 @@ module ODisk
42
42
  files = @ftp.dir.entries(top).map {|e| e.name }
43
43
  job.remote_digest = Oj.load(json, mode: :object)
44
44
  missing = []
45
- job.remote_digest.entries.each { |e| missing << e.name unless files.include?(e.name + '.gpg') || files.include?(e.name) || e.is_a?(::ODisk::Link) }
45
+ job.remote_digest.entries.each { |e| missing << e.name unless (files.include?(e.name + '.gpg') ||
46
+ files.include?(e.name) ||
47
+ e.is_a?(::ODisk::Link) ||
48
+ e.removed) }
46
49
  unless ::ODisk::Planner::Step::REMOTE == $master
47
50
  missing.each { |name| job.remote_digest.delete(name) }
48
51
  end
@@ -54,16 +54,16 @@ module ODisk
54
54
  if le.class != re.class
55
55
  ::Opee::Env.error("Conflict syncing #{ld.top_path}/#{name}. Local and remote types do not match.")
56
56
  steps[name] = Step.new(name, Step::LOCAL, Step::ERROR)
57
+ elsif le.removed
58
+ ::Opee::Env.error("Unexpected digest entry for #{ld.top_path}/#{name}. The removed flag is sent in the local digest.")
59
+ elsif re.removed
60
+ steps[name] = Step.new(name, Step::LOCAL, Step::REMOVE)
57
61
  elsif le.is_a?(::ODisk::File) || le.is_a?(::ODisk::Link)
58
62
  op = le.is_a?(::ODisk::File) ? Step::COPY : Step::LINK
59
63
  if Step::LOCAL == master
60
64
  steps[name] = Step.new(name, Step::LOCAL, op) if le.is_a?(::ODisk::File)
61
65
  elsif Step::REMOTE == master
62
66
  steps[name] = Step.new(name, Step::REMOTE, op) if re.is_a?(::ODisk::File)
63
- elsif le.removed
64
- # TBD
65
- elsif re.removed
66
- # TBD
67
67
  elsif le.mtime > re.mtime
68
68
  pe = ph[name]
69
69
  if pe.nil? || pe.mtime == re.mtime
@@ -196,8 +196,21 @@ module ODisk
196
196
 
197
197
  def process_sync(job, odisk_dir)
198
198
  dirs = []
199
- job.current_digest.entries.each { |e| dirs << e.name unless !e.is_a?(::ODisk::Dir) || dirs.include?(e.name) }
200
- job.remote_digest.entries.each { |e| dirs << e.name unless !e.is_a?(::ODisk::Dir) || dirs.include?(e.name) }
199
+ if Step::REMOTE == $master
200
+ job.remote_digest.entries.each { |e| dirs << e.name unless !e.is_a?(::ODisk::Dir) || dirs.include?(e.name) }
201
+ elsif Step::LOCAL == $master
202
+ job.current_digest.entries.each { |e| dirs << e.name unless !e.is_a?(::ODisk::Dir) || dirs.include?(e.name) }
203
+ else
204
+ job.current_digest.entries.each { |e| dirs << e.name unless !e.is_a?(::ODisk::Dir) || dirs.include?(e.name) }
205
+ job.remote_digest.entries.each do |e|
206
+ next unless e.is_a?(::ODisk::Dir)
207
+ if e.removed
208
+ dirs.delete(e.name)
209
+ else
210
+ dirs << e.name unless dirs.include?(e.name)
211
+ end
212
+ end
213
+ end
201
214
  dirs.each do |dir|
202
215
  path = job.path.empty? ? dir : ::File.join(job.path, dir)
203
216
  local = ::File.join($local_top, path)
@@ -250,7 +263,16 @@ module ODisk
250
263
  nrh[e.name] = e
251
264
  nlh[e.name] = e
252
265
  when Step::REMOVE
253
- # TBD
266
+ path = job.path.empty? ? s.name : ::File.join(job.path, s.name)
267
+ if Step::REMOTE == s.master
268
+ remote = ::File.join($remote.dir, path)
269
+ @copy_queue.add_method(:remove_remote, remote)
270
+ nrh.delete(s.name)
271
+ else
272
+ local = ::File.join($local_top, path)
273
+ @copy_queue.add_method(:remove_local, local)
274
+ nlh.delete(s.name)
275
+ end
254
276
  when Step::DIGEST
255
277
  e = (Step::REMOTE == s.master) ? job.remote_digest[s.name] : job.current_digest[s.name]
256
278
  nrh[e.name] = e
@@ -1,5 +1,5 @@
1
1
 
2
2
  module ODisk
3
3
  # Current version of the module.
4
- VERSION = '0.2.2'
4
+ VERSION = '0.2.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: odisk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-16 00:00:00.000000000 Z
12
+ date: 2012-09-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: opee
@@ -80,6 +80,7 @@ email: peter@ohler.com
80
80
  executables:
81
81
  - odisk
82
82
  - odisk_forget
83
+ - odisk_remove
83
84
  extensions: []
84
85
  extra_rdoc_files:
85
86
  - README.md
@@ -105,8 +106,12 @@ files:
105
106
  - lib/odisk.rb
106
107
  - LICENSE
107
108
  - README.md
108
- - bin/odisk
109
- - bin/odisk_forget
109
+ - !binary |-
110
+ YmluL29kaXNr
111
+ - !binary |-
112
+ YmluL29kaXNrX2ZvcmdldA==
113
+ - !binary |-
114
+ YmluL29kaXNrX3JlbW92ZQ==
110
115
  homepage: http://www.ohler.com/odisk
111
116
  licenses: []
112
117
  post_install_message:
@@ -129,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
134
  version: '0'
130
135
  requirements: []
131
136
  rubyforge_project: odisk
132
- rubygems_version: 1.8.23
137
+ rubygems_version: 1.8.21
133
138
  signing_key:
134
139
  specification_version: 3
135
140
  summary: Remote Encrypted File Synchronization, oDisk