rest-ftp-daemon 0.72b → 0.85.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 +5 -13
- data/.ruby-version +1 -0
- data/Gemfile.lock +6 -4
- data/README.md +58 -93
- data/bin/rest-ftp-daemon +96 -24
- data/config.ru +12 -9
- data/lib/rest-ftp-daemon.rb +2 -1
- data/lib/rest-ftp-daemon/api/defaults.rb +1 -10
- data/lib/rest-ftp-daemon/api/jobs.rb +20 -18
- data/lib/rest-ftp-daemon/api/root.rb +37 -25
- data/lib/rest-ftp-daemon/common.rb +2 -37
- data/lib/rest-ftp-daemon/config.rb +14 -19
- data/lib/rest-ftp-daemon/constants.rb +21 -0
- data/lib/rest-ftp-daemon/exceptions.rb +16 -29
- data/lib/rest-ftp-daemon/helpers.rb +55 -0
- data/lib/rest-ftp-daemon/job.rb +274 -150
- data/lib/rest-ftp-daemon/job_queue.rb +112 -17
- data/lib/rest-ftp-daemon/logger.rb +29 -5
- data/lib/rest-ftp-daemon/notification.rb +33 -48
- data/lib/rest-ftp-daemon/static/css/bootstrap.css +4490 -0
- data/lib/rest-ftp-daemon/static/css/{bootstrap.min.css → bootstrap.min.old1.css} +1 -1
- data/lib/rest-ftp-daemon/uri.rb +7 -1
- data/lib/rest-ftp-daemon/views/dashboard.haml +10 -10
- data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +20 -26
- data/lib/rest-ftp-daemon/views/dashboard_tokens.haml +1 -1
- data/lib/rest-ftp-daemon/views/dashboard_workers.haml +45 -0
- data/lib/rest-ftp-daemon/worker_pool.rb +66 -29
- data/rest-ftp-daemon.gemspec +9 -8
- data/rest-ftp-daemon.yml.sample +15 -4
- metadata +48 -30
data/lib/rest-ftp-daemon/job.rb
CHANGED
@@ -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
|
-
#
|
15
|
-
|
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
|
-
|
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
|
-
#
|
44
|
-
|
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
|
-
|
54
|
+
info "Job.process/prepare"
|
55
|
+
status :preparing
|
50
56
|
prepare
|
51
57
|
|
52
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
@status = text
|
132
|
+
# @mutex.synchronize do
|
133
|
+
# out = @params.clone
|
134
|
+
# end
|
92
135
|
end
|
93
136
|
|
94
137
|
def get attribute
|
95
|
-
|
96
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
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
|
165
|
-
|
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
|
172
|
-
@target_url = expand_url @params[
|
173
|
-
set :target_url, @target_url.
|
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
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
201
|
-
|
202
|
-
info "Job.transfer"
|
260
|
+
# Do transfer
|
261
|
+
ftp_transfer target_name
|
203
262
|
|
204
|
-
#
|
205
|
-
|
206
|
-
|
263
|
+
# Close FTP connexion
|
264
|
+
info "Job.transfer disconnecting"
|
265
|
+
status :disconnecting
|
266
|
+
@ftp.close
|
267
|
+
end
|
207
268
|
|
208
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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
|
221
|
-
|
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.
|
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
|
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
|
311
|
+
info "Job.transfer scheme: other [#{@target_url.scheme}]"
|
235
312
|
end
|
313
|
+
end
|
236
314
|
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
#
|
244
|
-
info "Job.
|
245
|
-
|
246
|
-
|
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
|
-
|
254
|
-
|
255
|
-
set :status, :chdir
|
256
|
-
ftp.chdir(target_path)
|
324
|
+
ret = @ftp.connect(@target_url.host)
|
325
|
+
@ftp.passive = true
|
257
326
|
|
258
|
-
|
259
|
-
|
260
|
-
|
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
|
-
|
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
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
-
|
291
|
-
set :progress,
|
292
|
-
|
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
|
-
#
|
296
|
-
|
297
|
-
set :
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
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
|