entangler 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/lib/entangler/entangled_file.rb +17 -16
- data/lib/entangler/executor/background/base.rb +108 -0
- data/lib/entangler/executor/background/master.rb +24 -0
- data/lib/entangler/executor/base.rb +15 -202
- data/lib/entangler/executor/master.rb +12 -19
- data/lib/entangler/executor/processing/base.rb +122 -0
- data/lib/entangler/version.rb +1 -1
- data/lib/entangler.rb +2 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e910a8be4617a7df89c5a0d5f94b51617a26deb6
|
4
|
+
data.tar.gz: 1d083cf56c15e7f332c703eb36381d31e717b7f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 829fd8b9cc788a049e1daab942a3eff9d911351f580b71f276610c0c48b758638698868066ceb39e3bac63a6bebc7e295915d19fd95d7f93e4a66a0bfe057b65
|
7
|
+
data.tar.gz: b08f880f5e249658f83b906e3789adf84b999d9922b068dc6eec037aa069475a62434212287f3a3898f56661a1a01c43911eab4c9b70fd6c59a12f153edd92ef
|
@@ -28,6 +28,23 @@ module Entangler
|
|
28
28
|
File.exists?(full_path)
|
29
29
|
end
|
30
30
|
|
31
|
+
def export
|
32
|
+
raise "Delta file doesn't exist when creaing patched file" unless delta_exists?
|
33
|
+
tempfile = Tempfile.new('final_file')
|
34
|
+
if File.exists?(full_path)
|
35
|
+
LibRubyDiff.patch(full_path, delta_file.path, tempfile.path)
|
36
|
+
else
|
37
|
+
temp_empty_file = Tempfile.new('empty_file')
|
38
|
+
LibRubyDiff.patch(temp_empty_file.path, delta_file.path, tempfile.path)
|
39
|
+
end
|
40
|
+
tempfile.rewind
|
41
|
+
File.open(full_path, 'w'){|f| f.write(tempfile.read)}
|
42
|
+
tempfile.close
|
43
|
+
tempfile.unlink
|
44
|
+
File.utime(File.atime(full_path), @desired_modtime, full_path)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
31
48
|
def signature_exists?
|
32
49
|
defined?(@signature_tempfile)
|
33
50
|
end
|
@@ -79,22 +96,6 @@ module Entangler
|
|
79
96
|
delta_file.read
|
80
97
|
end
|
81
98
|
|
82
|
-
def export
|
83
|
-
raise "Delta file doesn't exist when creaing patched file" unless delta_exists?
|
84
|
-
tempfile = Tempfile.new('final_file')
|
85
|
-
if File.exists?(full_path)
|
86
|
-
LibRubyDiff.patch(full_path, delta_file.path, tempfile.path)
|
87
|
-
else
|
88
|
-
temp_empty_file = Tempfile.new('empty_file')
|
89
|
-
LibRubyDiff.patch(temp_empty_file.path, delta_file.path, tempfile.path)
|
90
|
-
end
|
91
|
-
tempfile.rewind
|
92
|
-
File.open(full_path, 'w'){|f| f.write(tempfile.read)}
|
93
|
-
tempfile.close
|
94
|
-
tempfile.unlink
|
95
|
-
File.utime(File.atime(full_path), @desired_modtime, full_path)
|
96
|
-
end
|
97
|
-
|
98
99
|
def close_and_unlink_files
|
99
100
|
if signature_exists?
|
100
101
|
@signature_tempfile.close
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Entangler
|
2
|
+
module Executor
|
3
|
+
module Background
|
4
|
+
module Base
|
5
|
+
protected
|
6
|
+
def wait_for_threads
|
7
|
+
@consumer_thread.join
|
8
|
+
@remote_io_thread.join
|
9
|
+
@local_io_thread.join
|
10
|
+
Process.wait @notify_daemon_pid
|
11
|
+
end
|
12
|
+
|
13
|
+
def kill_off_threads
|
14
|
+
Process.kill("TERM", @notify_daemon_pid) rescue nil
|
15
|
+
@consumer_thread.terminate
|
16
|
+
@remote_io_thread.terminate
|
17
|
+
@local_io_thread.terminate
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_notify_daemon
|
21
|
+
logger.info('starting notify daemon')
|
22
|
+
r,w = IO.pipe
|
23
|
+
@notify_daemon_pid = spawn(start_notify_daemon_cmd, out: w)
|
24
|
+
w.close
|
25
|
+
@notify_reader = r
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_remote_io
|
29
|
+
logger.info('starting remote IO')
|
30
|
+
@remote_io_thread = Thread.new do
|
31
|
+
begin
|
32
|
+
loop do
|
33
|
+
msg = Marshal.load(@remote_reader)
|
34
|
+
next if msg.nil?
|
35
|
+
|
36
|
+
case msg[:type]
|
37
|
+
when :new_changes
|
38
|
+
process_new_changes(msg[:content])
|
39
|
+
when :entangled_files
|
40
|
+
process_entangled_files(msg[:content])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue => e
|
44
|
+
$stderr.puts e.message
|
45
|
+
$stderr.puts e.backtrace.join("\n")
|
46
|
+
kill_off_threads
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def start_local_io
|
52
|
+
logger.info('starting local IO')
|
53
|
+
@local_action_queue = Queue.new
|
54
|
+
@local_io_thread = Thread.new do
|
55
|
+
begin
|
56
|
+
msg = []
|
57
|
+
loop do
|
58
|
+
ready = IO.select([@notify_reader]).first
|
59
|
+
next unless ready && ready.any?
|
60
|
+
break if ready.first.eof?
|
61
|
+
line = ready.first.gets
|
62
|
+
next if line.nil? || line.empty?
|
63
|
+
line = line.strip
|
64
|
+
next if line == '-'
|
65
|
+
@local_action_queue.push line
|
66
|
+
end
|
67
|
+
rescue => e
|
68
|
+
$stderr.puts e.message
|
69
|
+
$stderr.puts e.backtrace.join("\n")
|
70
|
+
kill_off_threads
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def start_local_consumer
|
76
|
+
@consumer_thread = Thread.new do
|
77
|
+
loop do
|
78
|
+
msg = [@local_action_queue.pop]
|
79
|
+
loop do
|
80
|
+
sleep 0.2
|
81
|
+
break if @local_action_queue.empty?
|
82
|
+
while !@local_action_queue.empty?
|
83
|
+
msg << @local_action_queue.pop
|
84
|
+
end
|
85
|
+
end
|
86
|
+
while Time.now.to_f <= @notify_sleep
|
87
|
+
sleep 0.5
|
88
|
+
while !@local_action_queue.empty?
|
89
|
+
msg << @local_action_queue.pop
|
90
|
+
end
|
91
|
+
end
|
92
|
+
process_lines(msg.uniq)
|
93
|
+
msg = []
|
94
|
+
sleep 0.5
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def start_notify_daemon_cmd
|
100
|
+
uname = `uname`.strip.downcase
|
101
|
+
raise 'Unsupported OS' unless ['darwin', 'linux'].include?(uname)
|
102
|
+
|
103
|
+
"#{File.join(File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))), 'notifier', 'bin', uname, 'notify')} #{self.base_dir}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Entangler
|
2
|
+
module Executor
|
3
|
+
module Background
|
4
|
+
module Master
|
5
|
+
protected
|
6
|
+
def start_remote_slave
|
7
|
+
require 'open3'
|
8
|
+
@remote_writer, @remote_reader, remote_err, @remote_thread = Open3.popen3("ssh -q #{@opts[:remote_user]}@#{@opts[:remote_host]} -p #{@opts[:remote_port]} -C \"source ~/.rvm/environments/default && entangler slave #{@opts[:remote_base_dir]}\"")
|
9
|
+
remote_err.close
|
10
|
+
end
|
11
|
+
|
12
|
+
def wait_for_threads
|
13
|
+
super
|
14
|
+
Process.wait @remote_thread[:pid] rescue nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def kill_off_threads
|
18
|
+
Process.kill("INT", @remote_thread[:pid])
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,23 +1,32 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'thread'
|
4
|
+
require_relative 'background/base'
|
5
|
+
require_relative 'processing/base'
|
4
6
|
|
5
7
|
module Entangler
|
6
8
|
module Executor
|
7
9
|
class Base
|
10
|
+
include Entangler::Executor::Background::Base, Entangler::Executor::Processing::Base
|
11
|
+
|
8
12
|
attr_reader :base_dir
|
9
13
|
|
10
14
|
def initialize(base_dir, opts = {})
|
11
15
|
@base_dir = File.realpath(File.expand_path(base_dir))
|
12
16
|
@notify_sleep = 0
|
17
|
+
@exported_at = 0
|
13
18
|
@opts = opts
|
14
19
|
@opts[:ignore] = [/^\/\.git.*/, /^\/\.entangler.*/, /^\/\.idea.*/, /^\/log.*/, /^\/tmp.*/] unless @opts.has_key?(:ignore)
|
15
20
|
validate_opts
|
16
21
|
logger.info("Starting executor")
|
17
22
|
end
|
18
23
|
|
19
|
-
def
|
20
|
-
|
24
|
+
def generate_abs_path(rel_path)
|
25
|
+
File.join(self.base_dir, rel_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def strip_base_path(path, base_dir = self.base_dir)
|
29
|
+
File.expand_path(path).sub(base_dir, '')
|
21
30
|
end
|
22
31
|
|
23
32
|
def run
|
@@ -27,214 +36,18 @@ module Entangler
|
|
27
36
|
start_local_consumer
|
28
37
|
logger.debug("NOTIFY PID: #{@notify_daemon_pid}")
|
29
38
|
Signal.trap("INT") { kill_off_threads }
|
30
|
-
|
31
|
-
@remote_io_thread.join
|
32
|
-
@local_io_thread.join
|
33
|
-
Process.wait @notify_daemon_pid
|
34
|
-
end
|
35
|
-
|
36
|
-
def kill_off_threads
|
37
|
-
Process.kill("TERM", @notify_daemon_pid) rescue nil
|
38
|
-
@consumer_thread.terminate
|
39
|
-
@remote_io_thread.terminate
|
40
|
-
@local_io_thread.terminate
|
41
|
-
end
|
42
|
-
|
43
|
-
def start_file_transfers(paths, pipe)
|
44
|
-
Marshal.dump(paths.map{|path| EntangledFile.new(path) }, pipe)
|
39
|
+
wait_for_threads
|
45
40
|
end
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@notify_daemon_pid = spawn(start_notify_daemon_cmd, out: w)
|
51
|
-
w.close
|
52
|
-
@notify_reader = r
|
53
|
-
end
|
54
|
-
|
55
|
-
def start_local_io
|
56
|
-
logger.info('starting local IO')
|
57
|
-
@local_action_queue = Queue.new
|
58
|
-
@local_io_thread = Thread.new do
|
59
|
-
begin
|
60
|
-
msg = []
|
61
|
-
loop do
|
62
|
-
ready = IO.select([@notify_reader]).first
|
63
|
-
next unless ready && ready.any?
|
64
|
-
break if ready.first.eof?
|
65
|
-
line = ready.first.gets
|
66
|
-
next if line.nil? || line.empty?
|
67
|
-
line = line.strip
|
68
|
-
next if line == '-'
|
69
|
-
@local_action_queue.push line
|
70
|
-
end
|
71
|
-
rescue => e
|
72
|
-
$stderr.puts e.message
|
73
|
-
$stderr.puts e.backtrace.join("\n")
|
74
|
-
kill_off_threads
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def start_local_consumer
|
80
|
-
@consumer_thread = Thread.new do
|
81
|
-
loop do
|
82
|
-
msg = [@local_action_queue.pop]
|
83
|
-
while !@local_action_queue.empty?
|
84
|
-
msg << @local_action_queue.pop
|
85
|
-
end
|
86
|
-
while Time.now.to_i <= @notify_sleep
|
87
|
-
sleep 1
|
88
|
-
while !@local_action_queue.empty?
|
89
|
-
msg << @local_action_queue.pop
|
90
|
-
end
|
91
|
-
end
|
92
|
-
process_lines(msg.uniq)
|
93
|
-
msg = []
|
94
|
-
sleep 1
|
95
|
-
end
|
96
|
-
end
|
42
|
+
protected
|
43
|
+
def validate_opts
|
44
|
+
raise "Base directory doesn't exist" unless Dir.exists?(self.base_dir)
|
97
45
|
end
|
98
46
|
|
99
47
|
def send_to_remote(msg = {})
|
100
48
|
Marshal.dump(msg, @remote_writer)
|
101
49
|
end
|
102
50
|
|
103
|
-
def start_remote_io
|
104
|
-
logger.info('starting remote IO')
|
105
|
-
@remote_io_thread = Thread.new do
|
106
|
-
begin
|
107
|
-
loop do
|
108
|
-
msg = Marshal.load(@remote_reader)
|
109
|
-
next if msg.nil?
|
110
|
-
|
111
|
-
case msg[:type]
|
112
|
-
when :new_changes
|
113
|
-
logger.debug("Got #{msg[:content].length} new folder changes from remote")
|
114
|
-
|
115
|
-
created_dirs = []
|
116
|
-
dirs_to_remove = []
|
117
|
-
files_to_remove = []
|
118
|
-
files_to_update = []
|
119
|
-
|
120
|
-
msg[:content].each do |base, changes|
|
121
|
-
possible_creation_dirs = changes[:dirs].clone
|
122
|
-
possible_creation_files = changes[:files].keys.clone
|
123
|
-
full_base_path = generate_abs_path(base)
|
124
|
-
|
125
|
-
unless File.directory?(full_base_path)
|
126
|
-
FileUtils::mkdir_p(full_base_path)
|
127
|
-
@notify_sleep = Time.now.to_i + 60
|
128
|
-
end
|
129
|
-
|
130
|
-
Dir.entries(full_base_path).each do |f|
|
131
|
-
next if ['.', '..'].include? f
|
132
|
-
full_path = File.join(generate_abs_path(base), f)
|
133
|
-
if File.directory?(full_path)
|
134
|
-
possible_creation_dirs -= [f]
|
135
|
-
dirs_to_remove << full_path unless changes[:dirs].include?(f)
|
136
|
-
elsif changes[:files].has_key?(f)
|
137
|
-
possible_creation_files -= [f]
|
138
|
-
files_to_update << File.join(base, f) unless changes[:files][f] == [File.size(full_path), File.mtime(full_path).to_i]
|
139
|
-
else
|
140
|
-
files_to_remove << full_path
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
dirs_to_create = possible_creation_dirs.map{|d| File.join(generate_abs_path(base), d)}
|
145
|
-
if dirs_to_create.any?
|
146
|
-
logger.debug("Creating #{dirs_to_create.length} dirs")
|
147
|
-
@notify_sleep = Time.now.to_i + 60
|
148
|
-
FileUtils.mkdir_p dirs_to_create
|
149
|
-
end
|
150
|
-
created_dirs += dirs_to_create
|
151
|
-
files_to_update += possible_creation_files.map{|f| File.join(base, f)}
|
152
|
-
end
|
153
|
-
|
154
|
-
@notify_sleep = Time.now.to_i + 60 if (files_to_remove + created_dirs + dirs_to_remove + files_to_update).any?
|
155
|
-
|
156
|
-
if files_to_remove.any?
|
157
|
-
logger.debug("Deleting #{files_to_remove.length} files")
|
158
|
-
FileUtils.rm files_to_remove
|
159
|
-
end
|
160
|
-
if dirs_to_remove.any?
|
161
|
-
logger.debug("Deleting #{dirs_to_remove.length} dirs")
|
162
|
-
FileUtils.rm_r dirs_to_remove
|
163
|
-
end
|
164
|
-
if files_to_update.any?
|
165
|
-
logger.debug("Creating #{files_to_update.length} new entangled files to sync")
|
166
|
-
send_to_remote(type: :entangled_files, content: files_to_update.map{|f| Entangler::EntangledFile.new(f) })
|
167
|
-
end
|
168
|
-
@notify_sleep = Time.now.to_i + 1 if (files_to_remove + created_dirs + dirs_to_remove + files_to_update).any?
|
169
|
-
@notify_sleep += 60 if files_to_update.any?
|
170
|
-
when :entangled_files
|
171
|
-
logger.debug("Got #{msg[:content].length} entangled files from remote")
|
172
|
-
completed_files, updated_files = msg[:content].partition(&:done?)
|
173
|
-
|
174
|
-
completed_files.each(&:export)
|
175
|
-
|
176
|
-
updated_files = updated_files.find_all{|f| f.state != 1 || f.file_exists? }
|
177
|
-
if updated_files.any?
|
178
|
-
send_to_remote(type: :entangled_files, content: updated_files)
|
179
|
-
end
|
180
|
-
@notify_sleep = Time.now.to_i + 1 if completed_files.any?
|
181
|
-
end
|
182
|
-
end
|
183
|
-
rescue => e
|
184
|
-
$stderr.puts e.message
|
185
|
-
$stderr.puts e.backtrace.join("\n")
|
186
|
-
kill_off_threads
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def process_lines(lines)
|
192
|
-
to_process = lines.map do |line|
|
193
|
-
path = line[2..-1]
|
194
|
-
stripped_path = strip_base_path(path)
|
195
|
-
next unless @opts[:ignore].nil? || @opts[:ignore].none?{|i| stripped_path.match(i) }
|
196
|
-
next unless File.directory?(path)
|
197
|
-
|
198
|
-
[stripped_path, generate_file_list(path)]
|
199
|
-
end.compact.sort_by(&:first)
|
200
|
-
|
201
|
-
return unless to_process.any?
|
202
|
-
logger.debug("PROCESSING #{to_process.count} folder/s")
|
203
|
-
send_to_remote(type: :new_changes, content: to_process)
|
204
|
-
end
|
205
|
-
|
206
|
-
def generate_file_list(path)
|
207
|
-
dirs = []
|
208
|
-
files = {}
|
209
|
-
|
210
|
-
Dir.entries(path).each do |f|
|
211
|
-
next if ['.', '..'].include? f
|
212
|
-
f_path = File.join(path, f)
|
213
|
-
if File.directory? f_path
|
214
|
-
dirs << f
|
215
|
-
else
|
216
|
-
files[f] = [File.size(f_path), File.mtime(f_path).to_i]
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
{dirs: dirs, files: files}
|
221
|
-
end
|
222
|
-
|
223
|
-
def generate_abs_path(rel_path)
|
224
|
-
File.join(self.base_dir, rel_path)
|
225
|
-
end
|
226
|
-
|
227
|
-
def strip_base_path(path, base_dir = self.base_dir)
|
228
|
-
File.expand_path(path).sub(base_dir, '')
|
229
|
-
end
|
230
|
-
|
231
|
-
def start_notify_daemon_cmd
|
232
|
-
uname = `uname`.strip.downcase
|
233
|
-
raise 'Unsupported OS' unless ['darwin', 'linux'].include?(uname)
|
234
|
-
|
235
|
-
"#{File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'notifier', 'bin', uname, 'notify')} #{self.base_dir}"
|
236
|
-
end
|
237
|
-
|
238
51
|
def logger
|
239
52
|
FileUtils::mkdir_p log_dir
|
240
53
|
@logger ||= Logger.new(File.join(log_dir, 'entangler.log'))
|
@@ -1,30 +1,29 @@
|
|
1
|
+
require_relative 'background/master'
|
2
|
+
|
1
3
|
module Entangler
|
2
4
|
module Executor
|
3
5
|
class Master < Base
|
4
|
-
|
5
|
-
super
|
6
|
-
raise 'Missing remote base dir' unless @opts.keys.include?(:remote_base_dir)
|
7
|
-
raise 'Missing remote user' unless @opts.keys.include?(:remote_user)
|
8
|
-
raise 'Missing remote host' unless @opts.keys.include?(:remote_host)
|
9
|
-
@opts[:remote_port] ||= '22'
|
10
|
-
res = `ssh -q #{@opts[:remote_user]}@#{@opts[:remote_host]} -p #{@opts[:remote_port]} -C "[[ -d '#{@opts[:remote_base_dir]}' ]] && echo 'ok' || echo 'missing'"`
|
11
|
-
raise 'Cannot connect to remote' if res.empty?
|
12
|
-
raise 'Remote base dir invalid' unless res.strip == 'ok'
|
13
|
-
end
|
6
|
+
include Entangler::Executor::Background::Master
|
14
7
|
|
15
8
|
def run
|
16
9
|
perform_initial_rsync
|
17
10
|
sleep 1
|
18
11
|
start_remote_slave
|
19
12
|
super
|
20
|
-
Process.wait @remote_thread[:pid] rescue nil
|
21
13
|
@remote_writer.close
|
22
14
|
@remote_reader.close
|
23
15
|
end
|
24
16
|
|
25
|
-
|
26
|
-
|
17
|
+
private
|
18
|
+
def validate_opts
|
27
19
|
super
|
20
|
+
raise 'Missing remote base dir' unless @opts.keys.include?(:remote_base_dir)
|
21
|
+
raise 'Missing remote user' unless @opts.keys.include?(:remote_user)
|
22
|
+
raise 'Missing remote host' unless @opts.keys.include?(:remote_host)
|
23
|
+
@opts[:remote_port] ||= '22'
|
24
|
+
res = `ssh -q #{@opts[:remote_user]}@#{@opts[:remote_host]} -p #{@opts[:remote_port]} -C "[[ -d '#{@opts[:remote_base_dir]}' ]] && echo 'ok' || echo 'missing'"`
|
25
|
+
raise 'Cannot connect to remote' if res.empty?
|
26
|
+
raise 'Remote base dir invalid' unless res.strip == 'ok'
|
28
27
|
end
|
29
28
|
|
30
29
|
def perform_initial_rsync
|
@@ -34,12 +33,6 @@ module Entangler
|
|
34
33
|
end
|
35
34
|
logger.debug 'Initial sync complete'
|
36
35
|
end
|
37
|
-
|
38
|
-
def start_remote_slave
|
39
|
-
require 'open3'
|
40
|
-
@remote_writer, @remote_reader, remote_err, @remote_thread = Open3.popen3("ssh -q #{@opts[:remote_user]}@#{@opts[:remote_host]} -p #{@opts[:remote_port]} -C \"source ~/.rvm/environments/default && entangler slave #{@opts[:remote_base_dir]}\"")
|
41
|
-
remote_err.close
|
42
|
-
end
|
43
36
|
end
|
44
37
|
end
|
45
38
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Entangler
|
2
|
+
module Executor
|
3
|
+
module Processing
|
4
|
+
module Base
|
5
|
+
protected
|
6
|
+
def generate_file_list(path)
|
7
|
+
dirs = []
|
8
|
+
files = {}
|
9
|
+
|
10
|
+
Dir.entries(path).each do |f|
|
11
|
+
next if ['.', '..'].include? f
|
12
|
+
f_path = File.join(path, f)
|
13
|
+
if File.directory? f_path
|
14
|
+
dirs << f
|
15
|
+
else
|
16
|
+
files[f] = [File.size(f_path), File.mtime(f_path).to_i]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
{dirs: dirs, files: files}
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_new_changes(content)
|
24
|
+
logger.debug("Got #{content.length} new folder changes from remote")
|
25
|
+
|
26
|
+
created_dirs = []
|
27
|
+
dirs_to_remove = []
|
28
|
+
files_to_remove = []
|
29
|
+
files_to_update = []
|
30
|
+
|
31
|
+
content.each do |base, changes|
|
32
|
+
possible_creation_dirs = changes[:dirs].clone
|
33
|
+
possible_creation_files = changes[:files].keys.clone
|
34
|
+
full_base_path = generate_abs_path(base)
|
35
|
+
|
36
|
+
unless File.directory?(full_base_path)
|
37
|
+
FileUtils::mkdir_p(full_base_path)
|
38
|
+
@notify_sleep = Time.now.to_i + 60
|
39
|
+
end
|
40
|
+
|
41
|
+
Dir.entries(full_base_path).each do |f|
|
42
|
+
next if ['.', '..'].include? f
|
43
|
+
full_path = File.join(generate_abs_path(base), f)
|
44
|
+
if File.directory?(full_path)
|
45
|
+
possible_creation_dirs -= [f]
|
46
|
+
dirs_to_remove << full_path unless changes[:dirs].include?(f)
|
47
|
+
elsif changes[:files].has_key?(f)
|
48
|
+
possible_creation_files -= [f]
|
49
|
+
files_to_update << File.join(base, f) unless changes[:files][f] == [File.size(full_path), File.mtime(full_path).to_i]
|
50
|
+
else
|
51
|
+
files_to_remove << full_path
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
dirs_to_create = possible_creation_dirs.map{|d| File.join(generate_abs_path(base), d)}
|
56
|
+
if dirs_to_create.any?
|
57
|
+
logger.debug("Creating #{dirs_to_create.length} dirs")
|
58
|
+
@notify_sleep = Time.now.to_i + 60
|
59
|
+
FileUtils.mkdir_p dirs_to_create
|
60
|
+
end
|
61
|
+
created_dirs += dirs_to_create
|
62
|
+
files_to_update += possible_creation_files.map{|f| File.join(base, f)}
|
63
|
+
end
|
64
|
+
|
65
|
+
@notify_sleep = Time.now.to_i + 60 if (files_to_remove + created_dirs + dirs_to_remove + files_to_update).any?
|
66
|
+
|
67
|
+
if files_to_remove.any?
|
68
|
+
logger.debug("Deleting #{files_to_remove.length} files")
|
69
|
+
FileUtils.rm files_to_remove
|
70
|
+
end
|
71
|
+
if dirs_to_remove.any?
|
72
|
+
logger.debug("Deleting #{dirs_to_remove.length} dirs")
|
73
|
+
FileUtils.rm_r dirs_to_remove
|
74
|
+
end
|
75
|
+
if files_to_update.any?
|
76
|
+
logger.debug("Creating #{files_to_update.length} new entangled files to sync")
|
77
|
+
send_to_remote(type: :entangled_files, content: files_to_update.map{|f| Entangler::EntangledFile.new(f) })
|
78
|
+
end
|
79
|
+
@notify_sleep = Time.now.to_f + 0.5 if (files_to_remove + created_dirs + dirs_to_remove + files_to_update).any?
|
80
|
+
@notify_sleep += 60 if files_to_update.any?
|
81
|
+
end
|
82
|
+
|
83
|
+
def process_entangled_files(content)
|
84
|
+
logger.debug("Got #{content.length} entangled files from remote")
|
85
|
+
completed_files, updated_files = content.partition(&:done?)
|
86
|
+
|
87
|
+
if completed_files.any?
|
88
|
+
@exported_at = Time.now.to_f
|
89
|
+
@exported_folders = completed_files.map{|ef| "#{File.dirname(generate_abs_path(ef.path))}/" }.uniq
|
90
|
+
completed_files.each(&:export)
|
91
|
+
end
|
92
|
+
|
93
|
+
updated_files = updated_files.find_all{|f| f.state != 1 || f.file_exists? }
|
94
|
+
if updated_files.any?
|
95
|
+
send_to_remote(type: :entangled_files, content: updated_files)
|
96
|
+
end
|
97
|
+
@notify_sleep = Time.now.to_f + 0.5 if completed_files.any?
|
98
|
+
end
|
99
|
+
|
100
|
+
def process_lines(lines)
|
101
|
+
paths = lines.map{|line| line[2..-1] }
|
102
|
+
|
103
|
+
if @exported_at < Time.now.to_f && Time.now.to_f < @exported_at + 2
|
104
|
+
paths -= @exported_folders
|
105
|
+
end
|
106
|
+
|
107
|
+
to_process = paths.map do |path|
|
108
|
+
stripped_path = strip_base_path(path)
|
109
|
+
next unless @opts[:ignore].nil? || @opts[:ignore].none?{|i| stripped_path.match(i) }
|
110
|
+
next unless File.directory?(path)
|
111
|
+
|
112
|
+
[stripped_path, generate_file_list(path)]
|
113
|
+
end.compact.sort_by(&:first)
|
114
|
+
|
115
|
+
return unless to_process.any?
|
116
|
+
logger.debug("PROCESSING #{to_process.count} folder/s")
|
117
|
+
send_to_remote(type: :new_changes, content: to_process)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/entangler/version.rb
CHANGED
data/lib/entangler.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: entangler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Allie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -88,8 +88,11 @@ files:
|
|
88
88
|
- exe/entangler
|
89
89
|
- lib/entangler.rb
|
90
90
|
- lib/entangler/entangled_file.rb
|
91
|
+
- lib/entangler/executor/background/base.rb
|
92
|
+
- lib/entangler/executor/background/master.rb
|
91
93
|
- lib/entangler/executor/base.rb
|
92
94
|
- lib/entangler/executor/master.rb
|
95
|
+
- lib/entangler/executor/processing/base.rb
|
93
96
|
- lib/entangler/executor/slave.rb
|
94
97
|
- lib/entangler/version.rb
|
95
98
|
- lib/notifier/bin/darwin/notify
|