lsync 1.2.5 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  Commands = {
4
- "mount" => "mount",
5
- "unmount" => "umount"
4
+ "mount" => "mount",
5
+ "unmount" => "umount"
6
6
  }
7
7
 
8
8
  DevicePaths = [
9
- "/dev/disk/by-label",
10
- "/dev/disk/by-uuid",
11
- "/dev"
9
+ "/dev/disk/by-label",
10
+ "/dev/disk/by-uuid",
11
+ "/dev"
12
12
  ]
13
13
 
14
14
  action = ARGV[0]
@@ -19,10 +19,10 @@ mountpoint = File.join('', 'mnt', disk_name)
19
19
  if (action == 'mountpoint')
20
20
  puts File.join(mountpoint, ARGV[2..-1])
21
21
  else
22
- puts "#{action.capitalize}ing #{mountpoint}..."
23
- system Commands[action], mountpoint
24
-
25
- if $?.exitstatus != 0 or $?.exitstatus != 3383
26
- exit 5
27
- end
22
+ puts "#{action.capitalize}ing #{mountpoint}..."
23
+ system Commands[action], mountpoint
24
+
25
+ if $?.exitstatus != 0 or $?.exitstatus != 3383
26
+ exit 5
27
+ end
28
28
  end
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'base64'
4
+
4
5
  cmd = Base64.decode64(ARGV[0])
6
+
5
7
  exec cmd
6
8
 
@@ -1,50 +1,64 @@
1
1
 
2
+ require 'lsync/event_handler'
2
3
  require 'pathname'
3
4
 
4
5
  class Pathname
5
- def components
6
- return to_s.split(SEPARATOR_PAT)
7
- end
8
-
9
- def normalize_trailing_slash
10
- if to_s.match(/\/$/)
11
- return self
12
- else
13
- return self.class.new(to_s + "/")
14
- end
15
- end
16
-
17
- # Returns the number of path components
18
- # We need to work with a cleanpath to get an accurate depth
19
- # "", "/" => 0
20
- # "bob" => 1
21
- # "bob/dole" => 2
22
- # "/bob/dole" => 2
6
+ # Split a pathname up based on the individual path components.
7
+ def components
8
+ return to_s.split(SEPARATOR_PAT)
9
+ end
10
+
11
+ # Add a trailing slash to the path if it doesn't already exist.
12
+ def normalize_trailing_slash
13
+ if to_s.match(/\/$/)
14
+ return self
15
+ else
16
+ return self.class.new(to_s + "/")
17
+ end
18
+ end
19
+
20
+ # Returns the number of path components in a normalised fashion.
23
21
  #
22
+ # We need to work with a cleanpath to get an accurate depth:
23
+ # "", "/" => 0
24
+ # "bob" => 1
25
+ # "bob/dole" => 2
26
+ # "/bob/dole" => 2
24
27
  def depth
25
28
  bits = cleanpath.to_s.split(SEPARATOR_PAT)
26
-
29
+
27
30
  bits.delete("")
28
31
  bits.delete(".")
29
-
32
+
30
33
  return bits.size
31
34
  end
32
35
  end
33
36
 
34
37
  module LSync
35
-
36
- class Directory
37
- def initialize(config)
38
- @path = Pathname.new(config["path"]).cleanpath.normalize_trailing_slash
39
-
40
- abort "Directory paths must be relative (#{config["path"]} is absolute!)." if @path.absolute?
41
- end
42
-
43
- attr :path
44
-
45
- def to_s
46
- @path.to_s
47
- end
48
- end
49
-
38
+
39
+ # A specific directory which is relative to the root of a given server. Specific configuration details
40
+ # such as excludes and other options may be specified.
41
+ class Directory
42
+ include EventHandler
43
+
44
+ def initialize(path)
45
+ @path = Pathname.new(path).cleanpath.normalize_trailing_slash
46
+ @options = {:arguments => []}
47
+ end
48
+
49
+ attr :path
50
+ attr :options
51
+
52
+ # Exclude a specific shell glob pattern.
53
+ def exclude(pattern)
54
+ # RSync specific... need to figure out if there is a good way to do this generally.
55
+ @options[:arguments] += ["--exclude", pattern]
56
+ end
57
+
58
+ # A string representation of the path for logging.
59
+ def to_s
60
+ @path.to_s
61
+ end
62
+ end
63
+
50
64
  end
data/lib/lsync/error.rb CHANGED
@@ -1,33 +1,33 @@
1
1
 
2
2
  module LSync
3
-
4
- class Error < StandardError
5
- def initialize(reason, components = {})
6
- @reason = reason
7
- @components = components
8
- end
9
-
10
- def to_s
11
- @reason
12
- end
13
-
14
- attr :reason
15
- attr :components
16
- end
17
-
18
- class ScriptError < Error
19
- end
20
-
21
- class BackupMethodError < Error
22
- end
23
-
24
- class ConfigurationError < Error
25
- end
26
-
27
- class BackupActionError < Error
28
- def initialize(server, action, exception)
29
- super("Backup action failed: #{action} (#{exception.to_s})", :action => action, :exception => exception)
30
- end
31
- end
32
-
3
+
4
+ # Base exception class which keeps track of related components.
5
+ class Error < StandardError
6
+ def initialize(reason, components = {})
7
+ @reason = reason
8
+ @components = components
9
+ end
10
+
11
+ def to_s
12
+ @reason
13
+ end
14
+
15
+ attr :reason
16
+ attr :components
17
+ end
18
+
19
+ # Indicates that there has been a major backup script error.
20
+ class ScriptError < Error
21
+ end
22
+
23
+ # Indicates that there has been a major backup method error.
24
+ class BackupMethodError < Error
25
+ end
26
+
27
+ # Indicates that a backup action shell script has failed.
28
+ class ShellScriptError < Error
29
+ def initialize(script, return_code)
30
+ super("Shell script #{script} failed", :return_code => return_code)
31
+ end
32
+ end
33
33
  end
@@ -0,0 +1,72 @@
1
+
2
+ module LSync
3
+
4
+ # Basic event handling and delegation.
5
+ module EventHandler
6
+ # Register an event handler which may be triggered when an event is fired.
7
+ def on(event, &block)
8
+ @events ||= {}
9
+
10
+ @events[event] ||= []
11
+ @events[event] << block
12
+ end
13
+
14
+ # Fire an event which calls all registered event handlers in the order they were defined.
15
+ def fire(event, *args)
16
+ handled = false
17
+
18
+ if @events && @events[event]
19
+ @events[event].each do |handler|
20
+ handled = true
21
+ handler.call(*args)
22
+ end
23
+ end
24
+
25
+ return handled
26
+ end
27
+
28
+ # Try executing a given block of code and fire appropriate events.
29
+ #
30
+ # The sequence of events (registered via #on) are as follows:
31
+ # [+:prepare+] Fired before the block is executed. May call #abort! to cancel execution.
32
+ # [+:success+] Fired after the block of code has executed without raising an exception.
33
+ # [+:failure+] Fired if an exception is thrown during normal execution.
34
+ # [+:done+] Fired at the end of execution regardless of failure.
35
+ #
36
+ # If #abort! has been called in the past, this function returns immediately.
37
+ def try(*arguments)
38
+ return if @aborted
39
+
40
+ begin
41
+ catch(abort_name) do
42
+ fire(:prepare, *arguments)
43
+
44
+ yield
45
+
46
+ fire(:success, *arguments)
47
+ end
48
+ rescue Exception => error
49
+ # Propagage the exception unless it was handled in some specific way.
50
+ raise unless fire(:failure, *arguments + [error])
51
+ ensure
52
+ fire(:done, *arguments)
53
+ end
54
+ end
55
+
56
+ # Abort the current event handler. Aborting an event handler persistently implies that in
57
+ # the future it will still be aborted; thus calling #try will have no effect.
58
+ def abort!(persistent = false)
59
+ @aborted = true if persistent
60
+
61
+ throw abort_name
62
+ end
63
+
64
+ private
65
+
66
+ # The name used for throwing abortions.
67
+ def abort_name
68
+ ("abort_" + self.class.name).downcase.to_sym
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,80 @@
1
+
2
+ require 'thread'
3
+
4
+ module LSync
5
+
6
+ # Manages a callback that will be executed after a set duration.
7
+ class Timeout
8
+ def initialize(timeout, &block)
9
+ @cancelled = false
10
+
11
+ @thread = Thread.new do
12
+ sleep timeout
13
+
14
+ unless @cancelled
15
+ yield
16
+ end
17
+ end
18
+ end
19
+
20
+ # The thread on which the timeout is being waited.
21
+ attr :thread
22
+
23
+ # Cancel the timeout if possible and ensure that the callback is not executed.
24
+ def cancel!
25
+ @cancelled = true
26
+ @thread.exit
27
+ end
28
+ end
29
+
30
+ # The EventTimer provides a simple time based callback mechanism in which events can be aggregated.
31
+ # If the timer is triggered once, it will take at most max time for the callback to be triggered.
32
+ class EventTimer
33
+ # Times are measured in seconds.
34
+ # Min specifies the minimum duration between callback invocations.
35
+ # Max specifies the maximum duration between callback invocations.
36
+ def initialize(max, &block)
37
+ @max = max
38
+
39
+ @fired = nil
40
+ @timeout = nil
41
+
42
+ @callback = Proc.new(&block)
43
+ @processing = Mutex.new
44
+ end
45
+
46
+ # Trigger the event timer such that within the specified time, the callback will be fired.
47
+ def trigger!
48
+ unless @timeout
49
+ @timeout = Timeout.new(@max) { fire! }
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # Return true of the timeout has expired, e.g if it has not been fired within the given duration.
56
+ def expired?(duration = nil)
57
+ !@fired || ((Time.now - @fired) > duration)
58
+ end
59
+
60
+ # Fire the callback.
61
+ def fire!
62
+ @processing.synchronize do
63
+ @timeout = nil
64
+
65
+ @fired = Time.now
66
+ @callback.call
67
+ end
68
+ end
69
+
70
+ public
71
+
72
+ # Wait for the timeout to complete nicely.
73
+ def join
74
+ if @timeout
75
+ @timeout.thread.join
76
+ end
77
+ end
78
+ end
79
+
80
+ end
data/lib/lsync/method.rb CHANGED
@@ -2,191 +2,25 @@
2
2
  require 'fileutils'
3
3
  require 'pathname'
4
4
  require 'lsync/run'
5
+ require 'lsync/event_handler'
5
6
 
6
7
  module LSync
7
-
8
- class Method
9
- @@methods = {}
10
-
11
- def self.register(name, handler)
12
- @@methods[name] = handler
13
- end
14
-
15
- def self.lookup(name)
16
- @@methods[name]
17
- end
18
-
19
- def initialize(config, logger = nil)
20
- @logger = logger || Logger.new(STDOUT)
21
-
22
- @name, @options = config.split(/\s+/, 2)
23
-
24
- @method = Method.lookup(@name)
25
-
26
- if @method == nil
27
- raise BackupError.new("Could not find method #{@name}!")
28
- end
29
- end
30
-
31
- attr :logger, true
32
-
33
- def run(master_server, target_server, directory)
34
- @method.run(master_server, target_server, directory, @options, @logger)
35
- end
36
-
37
- def should_run?(master_server, current_server, target_server)
38
- @method.should_run?(master_server, current_server, target_server)
39
- end
40
-
41
- def run_actions(actions, logger)
42
- end
43
- end
44
-
45
- module Methods
46
- module DirectionalMethodHelper
47
- protected
48
- def connect_options_for_server (local_server, remote_server)
49
- # RSync -e option simply appends the hostname. There is no way to control this behaviour.
50
- cmd = remote_server.shell.full_command(remote_server)
51
-
52
- if cmd.match(/ #{remote_server.host}$/)
53
- cmd.gsub!(/ #{remote_server.host}$/, "")
54
- else
55
- abort "RSync shell requires hostname at end of command! #{cmd.dump}"
56
- end
57
-
58
- ['-e', cmd.dump].join(" ")
59
- end
60
-
61
- public
62
- def initialize(direction)
63
- @direction = direction
64
- end
65
-
66
- def run(master_server, target_server, directory, options, logger)
67
- options ||= ""
68
-
69
- local_server = nil
70
- remote_server = nil
71
-
72
- if @direction == :push
73
- local_server = master_server
74
- remote_server = target_server
75
-
76
- dst = remote_server.connection_string(directory)
77
- src = local_server.full_path(directory)
78
- else
79
- local_server = target_server
80
- remote_server = master_server
81
-
82
- src = remote_server.connection_string(directory)
83
- dst = local_server.full_path(directory)
84
- end
85
-
86
- options += " " + connect_options_for_server(local_server, remote_server)
87
-
88
- # Create the destination backup directory
89
- @connection = target_server.connect
90
- @connection.send_object([:mkdir_p, target_server.full_path(directory)])
91
-
92
- @logger = logger
93
-
94
- @logger.info "In directory #{Dir.getwd}"
95
- Dir.chdir(local_server.root_path) do
96
- if run_handler(src, dst, options) == false
97
- raise BackupMethodError.new("Backup from #{src.dump} to #{dst.dump} failed.", :method => self)
98
- end
99
- end
100
- end
101
-
102
- def should_run?(master_server, current_server, target_server)
103
- if @direction == :push
104
- return current_server == master_server
105
- elsif @direction == :pull
106
- return target_server.is_local?
107
- else
108
- return false
109
- end
110
- end
111
-
112
- def run_command(cmd)
113
- return LSync.run_command(cmd, @logger) == 0
114
- end
115
- end
116
-
117
- class RSync
118
- include DirectionalMethodHelper
119
-
120
- def run_handler(src, dst, options)
121
- run_command("rsync #{options} #{src.dump} #{dst.dump}")
122
- end
123
- end
124
-
125
- Method.register("rsync-pull", RSync.new(:pull))
126
- Method.register("rsync-push", RSync.new(:push))
127
-
128
- class RSyncSnapshot < RSync
129
- def run(master_server, target_server, directory, options, logger)
130
- options ||= ""
131
- link_dest = Pathname.new("../" * (directory.path.depth + 1)) + "latest" + directory.path
132
- options += " --archive --link-dest #{link_dest.to_s.dump}"
133
-
134
- inprogress_path = ".inprogress"
135
- dst_directory = File.join(inprogress_path, directory.to_s)
136
-
137
- local_server = nil
138
- remote_server = nil
139
-
140
- if @direction == :push
141
- local_server = master_server
142
- remote_server = target_server
143
-
144
- dst = remote_server.connection_string(dst_directory)
145
- src = local_server.full_path(directory)
146
- else
147
- local_server = target_server
148
- remote_server = master_server
149
-
150
- dst = local_server.full_path(dst_directory)
151
- src = remote_server.connection_string(directory)
152
- end
153
-
154
- options += " " + connect_options_for_server(local_server, remote_server)
155
-
156
- # Create the destination backup directory
157
- @connection = target_server.connect
158
- @connection.send_object([:mkdir_p, target_server.full_path(dst_directory)])
159
-
160
- @logger = logger
161
-
162
- Dir.chdir(local_server.root_path) do
163
- if run_handler(src, dst, options) == false
164
- raise BackupMethodError.new("Backup from #{src.dump} to #{dst.dump} failed.", :method => self)
165
- end
166
- end
167
- end
168
- end
169
-
170
- Method.register("rsync-snapshot-pull", RSyncSnapshot.new(:pull))
171
- Method.register("rsync-snapshot-push", RSyncSnapshot.new(:push))
172
-
173
- class LinkBackup
174
- include DirectionalMethodHelper
175
-
176
- def self.lb_bin
177
- return File.join(File.dirname(__FILE__), "lb.py")
178
- end
179
-
180
- def run_handler(src, dst, options)
181
- # Verbose mode for debugging..
182
- # options += " --verbose"
183
- run_command("python #{LinkBackup.lb_bin.dump} #{options} #{src.dump} #{dst.dump}")
184
- end
185
- end
186
-
187
- Method.register("lb-pull", LinkBackup.new(:pull))
188
- Method.register("lb-push", LinkBackup.new(:push))
189
-
190
- end
191
-
8
+
9
+ # A backup method provides the interface to copy data from one system to another.
10
+ class Method
11
+ include EventHandler
12
+
13
+ def initialize(options = {})
14
+ @logger = options[:logger] || Logger.new(STDOUT)
15
+ end
16
+
17
+ attr :logger, true
18
+
19
+ def run(master_server, target_server, directory)
20
+ end
21
+
22
+ def should_run?(master_server, current_server, target_server)
23
+ end
24
+ end
25
+
192
26
  end