rest-ftp-daemon 0.72b → 0.85.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,31 +3,37 @@
3
3
  require 'uri'
4
4
  require 'net/ftp'
5
5
  require 'double_bag_ftps'
6
+ require 'timeout'
6
7
 
7
8
  module RestFtpDaemon
8
9
  class Job < RestFtpDaemon::Common
10
+ attr_accessor :wid
9
11
 
10
12
  def initialize(id, params={})
11
13
  # Call super
12
- super()
14
+ # super()
15
+ info "Job.initialize"
13
16
 
14
- # Grab params
15
- @params = params
17
+ # Generate new Job.id
18
+ # $queue.counter_add :transferred, source_size
19
+
20
+ # Logger
21
+ @logger = RestFtpDaemon::Logger.new(:workers, "JOB #{id}")
22
+
23
+ # Protect with a mutex
24
+ @mutex = Mutex.new
16
25
 
17
26
  # Init context
27
+ @params = params
18
28
  set :id, id
19
29
  set :started_at, Time.now
20
- set :status, :created
30
+ status :created
21
31
 
22
32
  # Send first notification
33
+ info "Job.initialize/notify"
23
34
  notify "rftpd.queued"
24
35
  end
25
36
 
26
- def progname
27
- job_id = get(:id)
28
- "JOB #{job_id}"
29
- end
30
-
31
37
  def id
32
38
  get :id
33
39
  end
@@ -40,60 +46,99 @@ module RestFtpDaemon
40
46
  end
41
47
 
42
48
  def process
43
- # Init
44
- info "Job.process starting"
45
- set :status, :starting
46
- set :error, 0
49
+ # Update job's status
50
+ set :error, nil
47
51
 
52
+ # Prepare job
48
53
  begin
49
- # Validate job and params
54
+ info "Job.process/prepare"
55
+ status :preparing
50
56
  prepare
51
57
 
52
- # Process
58
+ rescue RestFtpDaemon::JobMissingAttribute => exception
59
+ return oops "rftpd.started", exception, :job_missing_attribute
60
+
61
+ rescue RestFtpDaemon::JobSourceNotFound => exception
62
+ return oops "rftpd.started", exception, :job_source_not_found
63
+
64
+ rescue RestFtpDaemon::RestFtpDaemonException => exception
65
+ return oops "rftpd.started", exception, :job_prepare_failed
66
+
67
+ rescue Exception => exception
68
+ return oops "rftpd.started", exception, :job_prepare_unhandled, true
69
+
70
+ rescue exception
71
+ return oops "rftpd.started", exception, :WOUHOU, true
72
+
73
+ else
74
+ # Update job's status
75
+ info "Job.process/prepare ok"
76
+ status :prepared
77
+ info "Job.process/prepare status updated"
78
+
79
+ # Notify rftpd.start
80
+ info "Job.process/prepare notify started"
81
+ notify "rftpd.started", 0
82
+ info "Job.process/prepare notified started"
83
+ end
84
+
85
+ info "Job.process prepare>transfer"
86
+
87
+ # Process job
88
+ begin
89
+ info "Job.process/transfer"
90
+ status :starting
53
91
  transfer
54
92
 
93
+ rescue Timeout::Error => exception
94
+ return oops "rftpd.ended", exception, :job_timeout_error
95
+
55
96
  rescue Net::FTPPermError => exception
56
- info "Job.process failed [Net::FTPPermError]"
57
- set :status, :failed
58
- set :error, exception.class
97
+ return oops "rftpd.ended", exception, :job_ftp_perm_error
98
+
99
+ rescue Errno::ECONNREFUSED => exception
100
+ return oops "rftpd.ended", exception, :job_connexion_refused
101
+
102
+ rescue Errno::EMFILE => exception
103
+ return oops "rftpd.ended", exception, :job_too_many_open_files
104
+
105
+ rescue RestFtpDaemon::JobTargetFileExists => exception
106
+ return oops "rftpd.ended", exception, :job_target_file_exists
59
107
 
60
- rescue RestFtpDaemonException => exception
61
- info "Job.process failed [::#{exception.class}]"
62
- set :status, :failed
63
- set :error, exception.class
108
+ rescue RestFtpDaemon::RestFtpDaemonException => exception
109
+ return oops "rftpd.ended", exception, :job_transfer_failed
64
110
 
65
111
  rescue Exception => exception
66
- info "Job.process exception [#{exception.class}] #{exception.backtrace.inspect}"
67
- set :status, :crashed
68
- set :error, exception.class
112
+ return oops "rftpd.ended", exception, :job_transfer_unhandled, true
69
113
 
70
114
  else
115
+ # Update job's status
71
116
  info "Job.process finished"
72
- set :status, :finished
117
+ status :finished
118
+
119
+ # Notify rftpd.ended
120
+ notify "rftpd.ended", 0
73
121
  end
74
122
 
75
123
  end
76
124
 
77
125
  def describe
78
- # Update realtime info
79
- #w = wandering_time
80
- #set :wandering, w.round(2) unless w.nil?
81
-
82
126
  # Update realtime info
83
127
  u = up_time
84
128
  set :uptime, u.round(2) unless u.nil?
85
129
 
86
- # Return the whole structure
130
+ # Return the whole structure FIXME
87
131
  @params
88
- end
89
-
90
- def status text
91
- @status = text
132
+ # @mutex.synchronize do
133
+ # out = @params.clone
134
+ # end
92
135
  end
93
136
 
94
137
  def get attribute
95
- return nil unless @params.is_a? Enumerable
96
- @params[attribute.to_s]
138
+ @mutex.synchronize do
139
+ @params || {}
140
+ @params[attribute]
141
+ end
97
142
  end
98
143
 
99
144
  protected
@@ -116,15 +161,17 @@ module RestFtpDaemon
116
161
  @wander_for.to_f - (Time.now - @wander_started)
117
162
  end
118
163
 
119
- # def exception_handler(actor, reason)
120
- # set :status, :crashed
121
- # set :error, reason
122
- # end
123
-
124
164
  def set attribute, value
125
- return unless @params.is_a? Enumerable
126
- @params[:updated_at] = Time.now
127
- @params[attribute.to_s] = value
165
+ @mutex.synchronize do
166
+ @params || {}
167
+ # return unless @params.is_a? Enumerable
168
+ @params[:updated_at] = Time.now
169
+ @params[attribute] = value
170
+ end
171
+ end
172
+
173
+ def status status
174
+ set :status, status
128
175
  end
129
176
 
130
177
  def expand_path path
@@ -138,169 +185,246 @@ module RestFtpDaemon
138
185
  def replace_token path
139
186
  # Ensure endpoints are not a nil value
140
187
  return path unless Settings.endpoints.is_a? Enumerable
141
- newpath = path.clone
188
+ vectors = Settings.endpoints.clone
189
+
190
+ # Stack RANDOM into tokens
191
+ vectors['RANDOM'] = SecureRandom.hex(IDENT_RANDOM_LEN)
142
192
 
143
193
  # Replace endpoints defined in config
144
- Settings.endpoints.each do |from, to|
145
- newpath.gsub! "[#{from}]", to
194
+ newpath = path.clone
195
+ vectors.each do |from, to|
196
+ next if to.to_s.blank?
197
+ #info "Job.replace_token #{Helpers.tokenize(from)} > #{to}"
198
+ newpath.gsub! Helpers.tokenize(from), to
146
199
  end
147
200
 
148
- # Replace with the special RAND token
149
- newpath.gsub! "[RANDOM]", SecureRandom.hex(8)
150
-
151
201
  return newpath
152
202
  end
153
203
 
154
204
  def prepare
155
205
  # Init
156
- info "Job.prepare"
157
- set :status, :preparing
206
+ status :preparing
158
207
  @source_method = :file
159
208
  @target_method = nil
160
209
  @source_path = nil
161
210
  @target_url = nil
162
211
 
163
212
  # Check source
164
- raise JobSourceMissing unless @params["source"]
165
- #@source = expand_path @params["source"]
166
- @source_path = expand_path @params["source"]
213
+ raise RestFtpDaemon::JobMissingAttribute unless @params[:source]
214
+ @source_path = expand_path @params[:source]
167
215
  set :source_path, @source_path
168
216
  set :source_method, :file
169
217
 
170
218
  # Check target
171
- raise JobTargetMissing unless @params["target"]
172
- @target_url = expand_url @params["target"]
173
- set :target_url, @target_url.inspect
219
+ raise RestFtpDaemon::JobMissingAttribute unless @params[:target]
220
+ @target_url = expand_url @params[:target]
221
+ set :target_url, @target_url.to_s
174
222
 
175
223
  if @target_url.kind_of? URI::FTP
176
224
  @target_method = :ftp
225
+ elsif @target_url.kind_of? URI::FTPES
226
+ @target_method = :ftps
177
227
  elsif @target_url.kind_of? URI::FTPS
178
228
  @target_method = :ftps
179
229
  end
180
230
  set :target_method, @target_method
181
231
 
182
232
  # Check compliance
183
- raise JobTargetUnparseable if @target_url.nil?
184
- raise JobTargetUnsupported if @target_method.nil?
185
- raise JobSourceNotFound unless File.exists? @source_path
186
-
233
+ raise RestFtpDaemon::JobTargetUnparseable if @target_url.nil?
234
+ raise RestFtpDaemon::JobTargetUnsupported if @target_method.nil?
235
+ raise RestFtpDaemon::JobSourceNotFound unless File.exists? @source_path
187
236
  end
188
237
 
189
- def transfer_fake
238
+ def transfer
239
+ # Method assertions
240
+ info "Job.transfer checking_source"
241
+ status :checking_source
242
+ raise RestFtpDaemon::JobAssertionFailed unless @source_path && @target_url
243
+
190
244
  # Init
191
- set :status, :faking
192
245
 
193
- # Work
194
- (0..9).each do |i|
195
- set :faking, i
196
- sleep 0.5
246
+ target_name = File.basename @target_url.path
247
+
248
+ # Scheme-aware config
249
+ ftp_init
250
+
251
+ # Connect remote server, login and chdir
252
+ ftp_connect
253
+
254
+ # Check for target file presence
255
+ if get(:overwrite).nil? && (ftp_presence target_name)
256
+ @ftp.close
257
+ raise RestFtpDaemon::JobTargetFileExists
197
258
  end
198
- end
199
259
 
200
- def transfer
201
- # Init
202
- info "Job.transfer"
260
+ # Do transfer
261
+ ftp_transfer target_name
203
262
 
204
- # Send first notification
205
- transferred = 0
206
- notify "rftpd.started"
263
+ # Close FTP connexion
264
+ info "Job.transfer disconnecting"
265
+ status :disconnecting
266
+ @ftp.close
267
+ end
207
268
 
208
- # Ensure @source and @target are there
209
- info "Job.transfer checking_source"
210
- set :status, :checking_source
211
- raise RestFtpDaemon::JobPrerequisitesNotMet unless @source_path
212
- raise RestFtpDaemon::JobPrerequisitesNotMet unless @target_url
213
- target_path = File.dirname @target_url.path
214
- target_name = File.basename @target_url.path
269
+ private
215
270
 
216
- # Read source file size
217
- source_size = File.size @source_path
218
- set :file_size, source_size
271
+ def oops signal_name, exception, error_name = nil, include_backtrace = false
272
+ # Log this error
273
+ error_name = exception.class if error_name.nil?
274
+ info "Job.oops si[#{signal_name}] er[#{error_name.to_s}] ex[#{exception.class}]"
275
+
276
+ # Update job's internal status
277
+ set :status, :failed
278
+ set :error, error_name
279
+ set :error_exception, exception.class
280
+
281
+ # Build status stack
282
+ status = nil
283
+ if include_backtrace
284
+ set :error_backtrace, exception.backtrace
285
+ status = {
286
+ backtrace: exception.backtrace,
287
+ }
288
+ end
219
289
 
220
- # Prepare FTP transfer
221
- info "Job.transfer preparing"
290
+ # Prepare notification if signal given
291
+ return unless signal_name
292
+ notify signal_name, error_name, status
293
+ end
294
+
295
+ def ftp_init
296
+ # Method assertions
297
+ info "Job.ftp_init"
298
+ status :ftp_init
299
+ raise RestFtpDaemon::JobAssertionFailed if @target_method.nil? || @target_url.nil?
222
300
 
223
- # Scheme-aware config
224
301
  case @target_method
225
302
  when :ftp
226
- info "Job.transfer scheme FTP"
227
- ftp = Net::FTP.new
303
+ info "Job.ftp_init scheme: ftp"
304
+ @ftp = Net::FTP.new
228
305
  when :ftps
229
- info "Job.transfer scheme FTPS"
230
- ftp = DoubleBagFTPS.new
231
- ftp.ssl_context = DoubleBagFTPS.create_ssl_context(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
232
- ftp.ftps_mode = DoubleBagFTPS::EXPLICIT
306
+ info "Job.transfer scheme: ftps"
307
+ @ftp = DoubleBagFTPS.new
308
+ @ftp.ssl_context = DoubleBagFTPS.create_ssl_context(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
309
+ @ftp.ftps_mode = DoubleBagFTPS::EXPLICIT
233
310
  else
234
- info "Job.transfer scheme other: [#{@target_url.scheme}]"
311
+ info "Job.transfer scheme: other [#{@target_url.scheme}]"
235
312
  end
313
+ end
236
314
 
237
- # Connect remote server
238
- info "Job.transfer connecting"
239
- set :status, :connecting
240
- ftp.connect(@target_url.host)
241
- ftp.passive = true
315
+ def ftp_connect
316
+ #status :ftp_connect
317
+ # connect_timeout_sec = (Settings.transfer.connect_timeout_sec rescue nil) || DEFAULT_CONNECT_TIMEOUT_SEC
242
318
 
243
- # Logging in
244
- info "Job.transfer login"
245
- begin
246
- u = ftp.login @target_url.user, @target_url.password
247
- rescue Exception => exception
248
- info "Job.process login failed [#{exception.class}] #{u.inspect}"
249
- set :status, :login_failed
250
- set :error, exception.class
251
- end
319
+ # Method assertions
320
+ info "Job.ftp_connect connect"
321
+ status :ftp_connect
322
+ raise RestFtpDaemon::JobAssertionFailed if @ftp.nil? || @target_url.nil?
252
323
 
253
- # Changing to directory
254
- info "Job.transfer chdir"
255
- set :status, :chdir
256
- ftp.chdir(target_path)
324
+ ret = @ftp.connect(@target_url.host)
325
+ @ftp.passive = true
257
326
 
258
- # Check for target file presence
259
- if get(:overwrite).nil?
260
- info "Job.transfer remote_check (#{target_name})"
261
- set :status, :remote_check
262
-
263
- # Get file list, sometimes the response can be an empty value
264
- results = ftp.list(target_name) rescue nil
265
-
266
- # Result can be nil or a list of files
267
- if results.nil? || results.count.zero?
268
- info "Job.transfer remote_absent"
269
- set :status, :remote_absent
270
- else
271
- info "Job.transfer remote_present"
272
- set :status, :remote_present
273
- ftp.close
274
- notify "rftpd.ended", RestFtpDaemon::JobTargetFileExists
275
- raise RestFtpDaemon::JobTargetFileExists
276
- end
327
+ info "Job.ftp_connect login"
328
+ status :ftp_login
329
+ ret = @ftp.login @target_url.user, @target_url.password
277
330
 
278
- end
331
+ info "Job.ftp_connect chdir"
332
+ status :ftp_chdir
333
+ path = File.dirname @target_url.path
334
+ ret = @ftp.chdir(path)
335
+ end
279
336
 
280
- # Do transfer
281
- info "Job.transfer uploading"
282
- set :status, :uploading
283
- chunk_size = Settings.transfer.chunk_size || Settings[:default_chunk_size]
284
- notify_size = Settings.transfer.chunk_size || Settings[:default_notify_size]
285
- ftp.putbinaryfile(@source_path, target_name, chunk_size) do |block|
337
+ def ftp_presence target_name
338
+ # Method assertions
339
+ info "Job.ftp_presence"
340
+ status :ftp_presence
341
+ raise RestFtpDaemon::JobAssertionFailed if @ftp.nil? || @target_url.nil?
342
+
343
+ # Get file list, sometimes the response can be an empty value
344
+ results = @ftp.list(target_name) rescue nil
345
+
346
+ # Result can be nil or a list of files
347
+ return false if results.nil?
348
+ return results.count >0
349
+ end
350
+
351
+ def ftp_transfer target_name
352
+ # Method assertions
353
+ info "Job.ftp_transfer starting"
354
+ status :ftp_transfer
355
+ raise RestFtpDaemon::JobAssertionFailed if @ftp.nil? || @source_path.nil?
356
+
357
+ # Read source file size and parameters
358
+ source_size = File.size @source_path
359
+ set :transfer_size, source_size
360
+ update_every_kb = (Settings.transfer.update_every_kb rescue nil) || DEFAULT_UPDATE_EVERY_KB
361
+ notify_after_sec = Settings.transfer.notify_after_sec rescue nil
362
+
363
+ # Start transfer
364
+ transferred = 0
365
+ chunk_size = update_every_kb * 1024
366
+ t0 = tstart = Time.now
367
+ notified_at = Time.now
368
+ status :uploading
369
+ @ftp.putbinaryfile(@source_path, target_name, chunk_size) do |block|
286
370
  # Update counters
287
371
  transferred += block.bytesize
372
+ set :transfer_sent, transferred
373
+
374
+ # Update bitrate
375
+ dt = Time.now - t0
376
+ bitrate0 = (8 * chunk_size/dt).round(0)
377
+ set :transfer_bitrate, bitrate0
288
378
 
289
379
  # Update job info
290
- percent = (100.0 * transferred / source_size).round(1)
291
- set :progress, percent
292
- set :file_sent, transferred
380
+ percent1 = (100.0 * transferred / source_size).round(1)
381
+ set :progress, percent1
382
+
383
+ # Log progress
384
+ status = []
385
+ status << "#{percent1} %"
386
+ status << (Helpers.format_bytes transferred, "B")
387
+ status << (Helpers.format_bytes source_size, "B")
388
+ status << (Helpers.format_bytes bitrate0, "bps")
389
+ info "Job.ftp_transfer" + status.map{|txt| ("%#{DEFAULT_LOGS_PROGNAME_TRIM.to_i}s" % txt)}.join("\t")
390
+
391
+ # Update time pointer
392
+ t0 = Time.now
393
+
394
+ # Notify if requested
395
+ unless notify_after_sec.nil? || (notified_at + notify_after_sec > Time.now)
396
+ status = {
397
+ progress: percent1,
398
+ transfer_sent: transferred,
399
+ transfer_size: source_size,
400
+ transfer_bitrate: bitrate0
401
+ }
402
+ notify "rftpd.progress", 0, status
403
+ notified_at = Time.now
404
+ end
405
+
293
406
  end
294
407
 
295
- # Close FTP connexion
296
- info "Job.transfer closing"
297
- set :status, :disconnecting
298
- notify "rftpd.ended"
299
- set :progress, nil
300
- ftp.close
408
+ # Compute final bitrate
409
+ tbitrate0 = (8 * source_size.to_f / (Time.now - tstart)).round(0)
410
+ set :transfer_bitrate, tbitrate0
411
+
412
+ # Add total transferred to counter
413
+ $queue.counter_add :transferred, source_size
414
+
415
+ # Done
416
+ #set :progress, nil
417
+ info "Job.ftp_transfer finished"
301
418
  end
302
419
 
303
- private
420
+ def notify signal, error = 0, status = {}
421
+ RestFtpDaemon::Notification.new get(:notify), {
422
+ id: get(:id),
423
+ signal: signal,
424
+ error: error,
425
+ status: status,
426
+ }
427
+ end
304
428
 
305
429
  end
306
430
  end