morhekil-prisync 0.2.1
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 +23 -0
- data/bin/prisync +62 -0
- data/lib/inotify.rb +211 -0
- data/lib/prisync.rb +99 -0
- data/prisync.gemspec +26 -0
- metadata +57 -0
data/README
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
prisync is a pseudo-realtime file synchronization daemon. It uses rsync utility in background
|
|
2
|
+
to transfer changes and inotify kernel events to monitor changes - thus it can be run only
|
|
3
|
+
on Linux with inotify-capable kernel. Can be used to keep two local folders or (usually)
|
|
4
|
+
to keep files on two servers in sync.
|
|
5
|
+
|
|
6
|
+
Usage: ssync [options]
|
|
7
|
+
-p, --local-path PATH Local path to synchronize and monitor for changes,
|
|
8
|
+
defaults to current directory if omitted
|
|
9
|
+
-r, --target-path PATH Target (remote) path to sync with. If you want to sync with
|
|
10
|
+
a remote host it should be given as user@host:/remote/path/
|
|
11
|
+
--ssh-port [PORT] SSH port to use if you want to sync with remote host via SSH tunnel,
|
|
12
|
+
you HAVE to specify remote host in the target-path option
|
|
13
|
+
-t, --timeout [SECONDS] Time to wait for any new changes to arrive before
|
|
14
|
+
starting the actual synchronization, use to not trigger
|
|
15
|
+
multiple synchronizations for repetitive event sources like
|
|
16
|
+
file uploading, default value is 5 seconds
|
|
17
|
+
-x, --exclude [REGEXP] Exclusion mask, should be specified as a valid Regexp. Items
|
|
18
|
+
matching this regexp will be excluded from monitoring and will not
|
|
19
|
+
trigger synchronization but will still be synced by rsync
|
|
20
|
+
-s, --sync-on-start Triggers initial synchronization on start
|
|
21
|
+
-v, --verbose Run verbosely with debug output
|
|
22
|
+
--dry Only print the rsync command without actually executing it
|
|
23
|
+
-h, --help Show help
|
data/bin/prisync
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require File.dirname(__FILE__) + '/../lib/prisync'
|
|
5
|
+
|
|
6
|
+
# get a list of files in the specified path
|
|
7
|
+
|
|
8
|
+
options = { :local_path => Dir::pwd }
|
|
9
|
+
|
|
10
|
+
OptionParser.new do |opts|
|
|
11
|
+
opts.banner = "Usage: prisync [options]"
|
|
12
|
+
|
|
13
|
+
opts.on('-p', '--local-path PATH', 'Local path to synchronize and monitor for changes,',
|
|
14
|
+
'defaults to current directory if omitted') do |path|
|
|
15
|
+
options[:local_path] = path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
opts.on('-r', '--target-path PATH', 'Target (remote) path to sync with. If you want to sync with',
|
|
19
|
+
'a remote host it should be given as user@host:/remote/path/') do |path|
|
|
20
|
+
options[:remote_path] = path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
opts.on('--ssh-port [PORT]', Integer, 'SSH port to use if you want to sync with remote host via SSH tunnel,',
|
|
24
|
+
'you HAVE to specify remote host in the target-path option') do |port|
|
|
25
|
+
options[:ssh_port] = port
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
opts.on('-t', '--timeout [SECONDS]', Integer, 'Time to wait for any new changes to arrive before',
|
|
29
|
+
'starting the actual synchronization, use to not trigger',
|
|
30
|
+
'multiple synchronizations for repetitive event sources like',
|
|
31
|
+
'file uploading, default value is '+Prisync::DEFAULT_TIMEOUT.to_s+' seconds') do |secs|
|
|
32
|
+
options[:timeout] = secs
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on('-x', '--exclude [REGEXP]', Regexp, 'Exclusion mask, should be specified as a valid Regexp. Items',
|
|
36
|
+
'matching this regexp will be excluded from monitoring and will not',
|
|
37
|
+
'trigger synchronization but will still be synced by rsync') do |exclude|
|
|
38
|
+
options[:exclude] = exclude
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
opts.on('-s', '--sync-on-start', 'Triggers initial synchronization on start') do
|
|
42
|
+
options[:sync_on_start] = true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
opts.on('-v', '--verbose', 'Run verbosely with debug output') do
|
|
46
|
+
options[:debug] = true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
opts.on('--dry', 'Only print the rsync command without actually executing it') do
|
|
50
|
+
options[:dryrun] = true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
opts.on_tail('-h', '--help', 'Show help') do
|
|
54
|
+
puts opts
|
|
55
|
+
exit
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end.parse!
|
|
59
|
+
|
|
60
|
+
sync = Prisync.new
|
|
61
|
+
sync.start(options)
|
|
62
|
+
|
data/lib/inotify.rb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Version 0.3.0 (2005-09-27) by James Le Cuirot <chewi@ffaura.com>
|
|
2
|
+
# masks updated
|
|
3
|
+
# syscalls instead of /dev/inotify for linux-2.6.13 (are the archs correct?)
|
|
4
|
+
# start/stop methods added for threading
|
|
5
|
+
# ignore_dir_recursively method added
|
|
6
|
+
# Events class removed : not necessary
|
|
7
|
+
# (wd <=> dir) hashed both ways : needed for ignore
|
|
8
|
+
# default watch mask is IN_ALL_EVENTS
|
|
9
|
+
# UnsupportedPlatformError class added to deal with unsupported CPUs and OSes
|
|
10
|
+
#
|
|
11
|
+
# Version 0.2.3 (2005-01-18) by oxman
|
|
12
|
+
# function ignore_dir : was added
|
|
13
|
+
#
|
|
14
|
+
# Version 0.2.2 (2005-01-18) by oxman
|
|
15
|
+
# cleaning code (big thanks to gnome at #ruby-lang)
|
|
16
|
+
# rename next_event in each_event (thanks kig)
|
|
17
|
+
#
|
|
18
|
+
# Version 0.2.1 (2005-01-18) by oxman
|
|
19
|
+
# class Events : use real mask
|
|
20
|
+
#
|
|
21
|
+
# Version 0.2.0 (2005-01-18) by oxman
|
|
22
|
+
# function watch_dir : only watch
|
|
23
|
+
# function next_event : was added
|
|
24
|
+
# function watch_dir_recursively : was added
|
|
25
|
+
#
|
|
26
|
+
# Version 0.1.1 (2005-01-17) by oxman
|
|
27
|
+
# Correct IN_ var for inotify 0.18
|
|
28
|
+
|
|
29
|
+
module INotify
|
|
30
|
+
require 'rbconfig'
|
|
31
|
+
require 'io/nonblock'
|
|
32
|
+
|
|
33
|
+
class UnsupportedPlatformError < RuntimeError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
case Config::CONFIG["arch"]
|
|
37
|
+
|
|
38
|
+
when /i[3-6]86-linux/
|
|
39
|
+
INOTIFY_INIT = 291
|
|
40
|
+
INOTIFY_ADD_WATCH = 292
|
|
41
|
+
INOTIFY_RM_WATCH = 293
|
|
42
|
+
|
|
43
|
+
when /x86_64-linux/
|
|
44
|
+
INOTIFY_INIT = 253
|
|
45
|
+
INOTIFY_ADD_WATCH = 254
|
|
46
|
+
INOTIFY_RM_WATCH = 255
|
|
47
|
+
|
|
48
|
+
when /powerpc(64)?-linux/
|
|
49
|
+
INOTIFY_INIT = 275
|
|
50
|
+
INOTIFY_ADD_WATCH = 276
|
|
51
|
+
INOTIFY_RM_WATCH = 277
|
|
52
|
+
|
|
53
|
+
when /ia64-linux/
|
|
54
|
+
INOTIFY_INIT = 1277
|
|
55
|
+
INOTIFY_ADD_WATCH = 1278
|
|
56
|
+
INOTIFY_RM_WATCH = 1279
|
|
57
|
+
|
|
58
|
+
when /s390-linux/
|
|
59
|
+
INOTIFY_INIT = 284
|
|
60
|
+
INOTIFY_ADD_WATCH = 285
|
|
61
|
+
INOTIFY_RM_WATCH = 286
|
|
62
|
+
|
|
63
|
+
when /alpha-linux/
|
|
64
|
+
INOTIFY_INIT = 444
|
|
65
|
+
INOTIFY_ADD_WATCH = 445
|
|
66
|
+
INOTIFY_RM_WATCH = 446
|
|
67
|
+
|
|
68
|
+
when /sparc(64)?-linux/
|
|
69
|
+
INOTIFY_INIT = 151
|
|
70
|
+
INOTIFY_ADD_WATCH = 152
|
|
71
|
+
INOTIFY_RM_WATCH = 156
|
|
72
|
+
|
|
73
|
+
when /arm-linux/
|
|
74
|
+
INOTIFY_INIT = 316
|
|
75
|
+
INOTIFY_ADD_WATCH = 317
|
|
76
|
+
INOTIFY_RM_WATCH = 318
|
|
77
|
+
|
|
78
|
+
when /sh-linux/
|
|
79
|
+
INOTIFY_INIT = 290
|
|
80
|
+
INOTIFY_ADD_WATCH = 291
|
|
81
|
+
INOTIFY_RM_WATCH = 292
|
|
82
|
+
|
|
83
|
+
else raise UnsupportedPlatformError, Config::CONFIG["arch"]
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Mask = Struct::new(:value, :name)
|
|
88
|
+
|
|
89
|
+
Masks = {
|
|
90
|
+
:IN_ACCESS => Mask::new(0x00000001, 'access'),
|
|
91
|
+
:IN_MODIFY => Mask::new(0x00000002, 'modify'),
|
|
92
|
+
:IN_ATTRIB => Mask::new(0x00000004, 'attrib'),
|
|
93
|
+
:IN_CLOSE_WRITE => Mask::new(0x00000008, 'close_write'),
|
|
94
|
+
:IN_CLOSE_NOWRITE => Mask::new(0x00000010, 'close_nowrite'),
|
|
95
|
+
:IN_OPEN => Mask::new(0x00000020, 'open'),
|
|
96
|
+
:IN_MOVED_FROM => Mask::new(0x00000040, 'moved_from'),
|
|
97
|
+
:IN_MOVED_TO => Mask::new(0x00000080, 'moved_to'),
|
|
98
|
+
:IN_CREATE => Mask::new(0x00000100, 'create'),
|
|
99
|
+
:IN_DELETE => Mask::new(0x00000200, 'delete'),
|
|
100
|
+
:IN_DELETE_SELF => Mask::new(0x00000400, 'delete_self'),
|
|
101
|
+
:IN_UNMOUNT => Mask::new(0x00002000, 'unmount'),
|
|
102
|
+
:IN_Q_OVERFLOW => Mask::new(0x00004000, 'q_overflow'),
|
|
103
|
+
:IN_IGNORED => Mask::new(0x00008000, 'ignored'),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Masks.each {|key, value|
|
|
107
|
+
const_set(key, value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
OrMasks = {
|
|
111
|
+
:IN_CLOSE => Mask::new(IN_CLOSE_WRITE.value | IN_CLOSE_NOWRITE.value, 'close'),
|
|
112
|
+
:IN_MOVE => Mask::new(IN_MOVED_FROM.value | IN_MOVED_TO.value, 'moved'),
|
|
113
|
+
:IN_ALL_EVENTS => Mask::new(IN_ACCESS.value | IN_MODIFY.value | IN_ATTRIB.value | IN_CLOSE_WRITE.value | IN_CLOSE_NOWRITE.value | IN_OPEN.value | IN_MOVED_FROM.value | IN_MOVED_TO.value | IN_DELETE.value | IN_CREATE.value | IN_DELETE_SELF.value, 'all_events')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
OrMasks.each {|key, value|
|
|
117
|
+
const_set(key, value)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
AllMasks = Masks.merge OrMasks
|
|
121
|
+
|
|
122
|
+
require 'find'
|
|
123
|
+
|
|
124
|
+
class INotify
|
|
125
|
+
def initialize
|
|
126
|
+
@wd_dir = Hash.new
|
|
127
|
+
@dir_wd = Hash.new
|
|
128
|
+
@io = IO.open(syscall(INOTIFY_INIT))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def close
|
|
132
|
+
@io.close
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def watch_dir (dir, option = IN_ALL_EVENTS)
|
|
136
|
+
wd = syscall(INOTIFY_ADD_WATCH, @io.fileno, dir, option.value)
|
|
137
|
+
|
|
138
|
+
if wd >= 0
|
|
139
|
+
@dir_wd[dir] = wd
|
|
140
|
+
@wd_dir[wd] = dir
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
return wd
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def ignore_dir (dir)
|
|
147
|
+
syscall(INOTIFY_RM_WATCH, @io.fileno, @dir_wd[dir])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def watch_dir_recursively (dir, option = IN_ALL_EVENTS)
|
|
151
|
+
Find.find(dir) { |sub_dir| watch_dir(sub_dir, option) if (File::directory?(sub_dir) == true) }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def ignore_dir_recursively (dir)
|
|
155
|
+
Find.find(dir) { |sub_dir| ignore_dir(sub_dir) if (File::directory?(sub_dir) == true) }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def next_events( is_nonblocking = false )
|
|
159
|
+
|
|
160
|
+
events = Array.new
|
|
161
|
+
|
|
162
|
+
begin
|
|
163
|
+
@io.nonblock = is_nonblocking
|
|
164
|
+
begin
|
|
165
|
+
read_cnt = @io.read(16)
|
|
166
|
+
wd, mask, cookie, len = read_cnt.unpack('lLLL')
|
|
167
|
+
read_cnt = @io.read(len)
|
|
168
|
+
filename = read_cnt.unpack('Z*')
|
|
169
|
+
rescue Errno::EAGAIN
|
|
170
|
+
return events
|
|
171
|
+
ensure
|
|
172
|
+
@io.nonblock = false
|
|
173
|
+
end
|
|
174
|
+
end while (mask & IN_Q_OVERFLOW.value) != 0
|
|
175
|
+
|
|
176
|
+
AllMasks.each_value do |m|
|
|
177
|
+
next if m.value == IN_ALL_EVENTS.value
|
|
178
|
+
events.push Event.new(@wd_dir[wd].to_s, filename.to_s, m.name.to_s, cookie) if (m.value & mask) != 0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
return events
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def each_event
|
|
185
|
+
loop { next_events.each { |event| yield event } }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def start
|
|
189
|
+
@thread = Thread.new { loop { next_events.each { |event| yield event } } }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def stop
|
|
193
|
+
@thread.exit
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
class Event
|
|
198
|
+
attr_reader :path, :filename, :type, :cookie
|
|
199
|
+
|
|
200
|
+
def initialize (path, filename, type, cookie)
|
|
201
|
+
@path = path
|
|
202
|
+
@filename = filename
|
|
203
|
+
@type = type
|
|
204
|
+
@cookie = cookie
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def dump
|
|
208
|
+
"path: " + @path.to_s + ", filename: " + @filename.to_s + ", type: " + @type.to_s + ", cookie: " + @cookie.to_s
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
data/lib/prisync.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
|
2
|
+
|
|
3
|
+
require 'inotify.rb'
|
|
4
|
+
require 'pp'
|
|
5
|
+
|
|
6
|
+
class Prisync
|
|
7
|
+
|
|
8
|
+
DEFAULT_TIMEOUT = 5
|
|
9
|
+
|
|
10
|
+
# Initializes INotify events listener object
|
|
11
|
+
def initialize
|
|
12
|
+
@inotify = INotify::INotify.new
|
|
13
|
+
@inotify_mask = INotify::Mask.new(INotify::IN_MODIFY.value | INotify::IN_DELETE.value | INotify::IN_CREATE.value | INotify::IN_MOVED_TO.value, 'filechange')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Starts the synchronization process, options are
|
|
17
|
+
# :local_path - local path to watch for changes,
|
|
18
|
+
# :target_path - target (remote) path to sync with,
|
|
19
|
+
# :ssh_port - SSH port to use if syncing remotely over the ssh tunnel
|
|
20
|
+
# :timeout - seconds to wait for any new changes to arrive before starting the actual synchronization
|
|
21
|
+
# :exclude - regexp for excluding files, if omitted or nil - no excluding,
|
|
22
|
+
# :sync_on_start - trigger synchronization on start if set,
|
|
23
|
+
# :dryrun - run rsync with --dry-run option to not perform any actual changes if set,
|
|
24
|
+
# :debug - print out extra debug information if set
|
|
25
|
+
def start(opts)
|
|
26
|
+
|
|
27
|
+
# init runtime parameters
|
|
28
|
+
@exclude = opts[:exclude].kind_of?(Regexp) ? opts[:exclude] : nil
|
|
29
|
+
@pending_changes = opts[:sync_on_start] ? true : false
|
|
30
|
+
@timeout = opts[:timeout] || DEFAULT_TIMEOUT
|
|
31
|
+
@is_debug = opts[:debug]
|
|
32
|
+
@is_dryrun = opts[:dryrun]
|
|
33
|
+
# build the rsync command
|
|
34
|
+
@rsync_cmd = 'rsync -azcC --force --delete '
|
|
35
|
+
@rsync_cmd += '--dry-run ' if opts[:dryrun]
|
|
36
|
+
@rsync_cmd += '--progress ' if @is_debug
|
|
37
|
+
@rsync_cmd += "-e \"ssh -p#{opts[:ssh_port]}\" " if opts[:ssh_port]
|
|
38
|
+
@rsync_cmd += "#{opts[:local_path]} #{opts[:remote_path]}"
|
|
39
|
+
|
|
40
|
+
# Start inotify watcher
|
|
41
|
+
@inotify.watch_dir_recursively(opts[:local_path], @inotify_mask)
|
|
42
|
+
# Catch INT and TERM signals to perform a clean up after ourselves
|
|
43
|
+
%w{INT TERM}.each do |signal_name|
|
|
44
|
+
Signal.trap(signal_name, proc { self.cleanup })
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
print "Started synchronization with the following options:\n#{opts.pretty_inspect}\n\n" if @is_debug
|
|
48
|
+
|
|
49
|
+
while (true) do
|
|
50
|
+
begin
|
|
51
|
+
self.monitor_changes
|
|
52
|
+
rescue
|
|
53
|
+
print "Exception: ",$!, "\n"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Monitors for changes and initiates syncing in case syncable changes were detected
|
|
60
|
+
def monitor_changes
|
|
61
|
+
# Check to see if we've got any new changes
|
|
62
|
+
new_changes = false
|
|
63
|
+
while ( (events = @inotify.next_events(true)).size > 0 ) do
|
|
64
|
+
print "Detected #{events.size} new events\n" if @is_debug
|
|
65
|
+
new_changes = true if events.find { |event| @exclude.nil? || event.filename !~ @exclude }
|
|
66
|
+
print "Has synchronizable changes\n" if new_changes && @is_debug
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if we've got any pending changes those need to be synced and timeout has passed indicating that we're ready to go
|
|
70
|
+
if (@pending_changes && !new_changes) then
|
|
71
|
+
print "Synchronizing...\n" if @is_debug
|
|
72
|
+
self.synchronize
|
|
73
|
+
@pending_changes = false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Update pending status and wait for a timeout
|
|
77
|
+
@pending_changes = true if new_changes
|
|
78
|
+
print "Pending changes\n" if @pending_changes && @is_debug
|
|
79
|
+
sleep @timeout
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Actually synchronizes the files by calling rsync using the pre-built command line
|
|
84
|
+
def synchronize
|
|
85
|
+
print "#{@rsync_cmd}\n" if @is_debug
|
|
86
|
+
system @rsync_cmd
|
|
87
|
+
errorvalue = $?
|
|
88
|
+
errorvalue = errorvalue.exitstatus if errorvalue.kind_of?(Process::Status)
|
|
89
|
+
$stderr.print "rsync error status #{errorvalue}\n" if @is_debug
|
|
90
|
+
self.cleanup if errorvalue == 20 || errorvalue == 255
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Cleans up before exit, closing inotify connection
|
|
94
|
+
def cleanup
|
|
95
|
+
print "Cleaning up...\n" if @is_debug
|
|
96
|
+
@inotify.close
|
|
97
|
+
exit 0
|
|
98
|
+
end
|
|
99
|
+
end
|
data/prisync.gemspec
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.name = 'prisync'
|
|
3
|
+
s.version = "0.2.1"
|
|
4
|
+
s.date = "2008-10-01"
|
|
5
|
+
s.summary = "Pseudo-realtime file synchronization daemon."
|
|
6
|
+
s.description = %{Prisync allows to synchronize to local or remote directories in a pseudo-realtime fashion.
|
|
7
|
+
Filesystem changes are monitored with inotify kernel events, rsync utility is used for the actual
|
|
8
|
+
synchronization and can be run over SSH.%}
|
|
9
|
+
s.has_rdoc = false
|
|
10
|
+
|
|
11
|
+
s.files = ['lib/inotify.rb',
|
|
12
|
+
'lib/prisync.rb',
|
|
13
|
+
'bin/prisync',
|
|
14
|
+
'prisync.gemspec',
|
|
15
|
+
'README'
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
s.bindir = 'bin'
|
|
19
|
+
s.executables = ['prisync']
|
|
20
|
+
|
|
21
|
+
s.require_path = 'lib'
|
|
22
|
+
|
|
23
|
+
s.author = "Oleg Ivanov"
|
|
24
|
+
s.email = "morhekil@morhekil.net"
|
|
25
|
+
s.homepage = "http://morhekil.net"
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: morhekil-prisync
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Oleg Ivanov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-10-01 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: Prisync allows to synchronize to local or remote directories in a pseudo-realtime fashion. Filesystem changes are monitored with inotify kernel events, rsync utility is used for the actual synchronization and can be run over SSH.%
|
|
17
|
+
email: morhekil@morhekil.net
|
|
18
|
+
executables:
|
|
19
|
+
- prisync
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files: []
|
|
23
|
+
|
|
24
|
+
files:
|
|
25
|
+
- lib/inotify.rb
|
|
26
|
+
- lib/prisync.rb
|
|
27
|
+
- bin/prisync
|
|
28
|
+
- prisync.gemspec
|
|
29
|
+
- README
|
|
30
|
+
has_rdoc: false
|
|
31
|
+
homepage: http://morhekil.net
|
|
32
|
+
post_install_message:
|
|
33
|
+
rdoc_options: []
|
|
34
|
+
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: "0"
|
|
42
|
+
version:
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: "0"
|
|
48
|
+
version:
|
|
49
|
+
requirements: []
|
|
50
|
+
|
|
51
|
+
rubyforge_project:
|
|
52
|
+
rubygems_version: 1.2.0
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 2
|
|
55
|
+
summary: Pseudo-realtime file synchronization daemon.
|
|
56
|
+
test_files: []
|
|
57
|
+
|