rest-ftp-daemon 0.90.1 → 0.94.4

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.
@@ -1,140 +1,72 @@
1
- require 'haml'
2
- require "facter"
3
- require "sys/cpu"
4
-
5
-
6
1
  module RestFtpDaemon
7
2
  module API
8
-
9
3
  class Root < Grape::API
10
4
 
11
5
 
12
6
  ####### CLASS CONFIG
13
7
 
14
- include RestFtpDaemon::API::Defaults
15
8
  logger RestFtpDaemon::Logger.new(:api, "API")
16
- mount RestFtpDaemon::API::Jobs => '/jobs'
17
- #add_swagger_documentation
18
- # mount RestFtpDaemon::API::Workers => '/workers'
19
9
 
10
+ do_not_route_head!
11
+ do_not_route_options!
20
12
 
21
- ####### HELPERS
22
-
23
- helpers do
24
- def info message, level = 0
25
- Root.logger.info(message, level)
26
- end
13
+ # FIXME
14
+ # add_swagger_documentation
15
+ # default_error_formatter :json
16
+ format :json
27
17
 
28
- end
29
-
30
-
31
- ####### INITIALIZATION
32
-
33
- def initialize
34
- # Check that Queue and Pool are available
35
- raise RestFtpDaemon::MissingQueue unless defined? $queue
36
- raise RestFtpDaemon::MissingQueue unless defined? $pool
37
- super
38
- end
39
18
 
19
+ ####### EXCETPIONS HANDLING
20
+ # FIXME
21
+ # rescue_from :all do |e|
22
+ # error_response(message: "Internal server error", status: 500)
23
+ # end
40
24
 
41
- ####### API DEFINITION
42
25
 
43
- # Server global status
44
- get '/' do
45
- info "GET /"
26
+ ####### HELPERS
46
27
 
47
- # Initialize UsageWatch
48
- Facter.loadfacts
49
- @info_load = Sys::CPU.load_avg.first.to_f
50
- @info_procs = (Facter.value :processorcount).to_i
51
- @info_ipaddr = Facter.value(:ipaddress)
52
- @info_memfree = Facter.value(:memoryfree)
28
+ helpers do
53
29
 
54
- # Compute normalized load
55
- if @info_procs.zero?
56
- @info_norm = "N/A"
57
- else
58
- @info_norm = (100 * @info_load / @info_procs).round(1)
30
+ def info message, level = 0
31
+ Root.logger.info(message, level)
59
32
  end
60
33
 
61
- # Jobs to display
62
- # all_jobs_in_queue = $queue.all
63
- # all_jobs_in_queue = $queue.ordered_queue
64
- # + $queue.popped
65
- popped_jobs = $queue.ordered_popped.reverse
66
- @jobs_queued = $queue.ordered_queue.reverse
67
-
68
- if params["only"].nil? || params["only"].blank?
69
- @only = nil
70
- else
71
- @only = params["only"].to_sym
34
+ def api_error exception
35
+ {
36
+ :error => exception.message,
37
+ :message => exception.backtrace.first,
38
+ #:backtrace => exception.backtrace,
39
+ }
72
40
  end
73
41
 
74
- case @only
75
- when nil
76
- @jobs_popped = popped_jobs
77
- # when :queue
78
- # @jobs_popped = $queue.queued
79
- else
80
- @jobs_popped = $queue.popped_reverse_sorted_by_status @only
42
+ def render name, values={}
43
+ template = File.read("#{APP_LIBS}/views/#{name.to_s}.haml")
44
+ haml_engine = Haml::Engine.new(template)
45
+ haml_engine.render(binding, values)
81
46
  end
82
47
 
83
- # Count jobs for each status
84
- @counts = $queue.popped_counts_by_status
48
+ def job_find job_id
49
+ return nil if ($queue.all_size==0)
85
50
 
86
- # Get workers status
87
- @gworker_statuses = $pool.get_worker_statuses
51
+ # Find a job with exactly this id, or prefixed if not found
52
+ $queue.find_by_id(job_id) || $queue.find_by_id(job_id, true)
53
+ end
88
54
 
89
- # Compile haml template
90
- output = render :dashboard
55
+ end
91
56
 
92
- # Send response
93
- env['api.format'] = :html
94
- format "html"
95
- status 200
96
- content_type "text/html"
97
- body output
98
57
 
99
- end
58
+ ####### INITIALIZATION
100
59
 
101
- # Server global status
102
- get '/status' do
103
- info "GET /status"
104
- status 200
105
- return {
106
- hostname: `hostname`.chomp,
107
- version: APP_VER,
108
- config: Settings.to_hash,
109
- started: APP_STARTED,
110
- uptime: (Time.now - APP_STARTED).round(1),
111
- counters: $queue.counters,
112
- status: $queue.popped_counts_by_status,
113
- queue_size: $queue.all_size,
114
- jobs_queued: $queue.queued.collect(&:id),
115
- jobs_popped: $queue.popped.collect(&:id),
116
- routes: RestFtpDaemon::API::Root::routes,
117
- }
118
- end
60
+ def initialize
61
+ # Call daddy
62
+ super
119
63
 
120
- # Server test
121
- get '/debug' do
122
- info "GET /debug"
123
-
124
- begin
125
- raise RestFtpDaemon::DummyException
126
- rescue RestFtpDaemon::RestFtpDaemonException => exception
127
- status 501
128
- api_error exception
129
- rescue Exception => exception
130
- status 501
131
- api_error exception
132
- else
133
- status 200
134
- {}
135
- end
64
+ # Check that Queue and Pool are available
65
+ raise RestFtpDaemon::MissingQueue unless defined? $queue
66
+ raise RestFtpDaemon::MissingQueue unless defined? $pool
136
67
  end
137
68
 
69
+
138
70
  end
139
71
  end
140
72
  end
@@ -0,0 +1,16 @@
1
+ module RestFtpDaemon
2
+ module API
3
+ class Root < Grape::API
4
+
5
+
6
+ ####### GET /routes
7
+
8
+ get '/routes' do
9
+ info "GET /routes"
10
+ status 200
11
+ return RestFtpDaemon::API::Root::routes
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ module RestFtpDaemon
2
+ module API
3
+ class Root < Grape::API
4
+
5
+
6
+ ####### GET /status
7
+
8
+ # Server global status
9
+ get '/status' do
10
+ info "GET /status"
11
+ status 200
12
+ return {
13
+ hostname: `hostname`.chomp,
14
+ version: APP_VER,
15
+ config: Helpers.get_censored_config,
16
+ started: APP_STARTED,
17
+ uptime: (Time.now - APP_STARTED).round(1),
18
+ counters: $queue.counters,
19
+ status: $queue.counts_by_status,
20
+ jobs_count: $queue.all_size,
21
+ jobs_queued: $queue.queued.collect(&:id),
22
+ jobs_popped: $queue.popped.collect(&:id),
23
+ #routes: RestFtpDaemon::API::Root::routes,
24
+ }
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -1,8 +1,7 @@
1
1
  # Terrific constants
2
2
  APP_NAME = "rest-ftp-daemon"
3
3
  APP_CONF = "/etc/#{APP_NAME}.yml"
4
- APP_VER = "0.90.1"
5
-
4
+ APP_VER = "0.94.4"
6
5
 
7
6
  # Some global constants
8
7
  IDENT_JOB_LEN = 4
@@ -15,7 +14,10 @@ DEFAULT_UPDATE_EVERY_KB = 2048
15
14
  DEFAULT_WORKERS = 1
16
15
  DEFAULT_LOGS_PROGNAME_TRIM = 12
17
16
 
18
-
19
17
  # Initialize markers
20
18
  APP_STARTED = Time.now
21
19
  APP_LIBS = File.dirname(__FILE__)
20
+
21
+ # Debugging
22
+ DEBUG_FTP_COMMANDS = false
23
+
@@ -3,6 +3,13 @@ require 'securerandom'
3
3
  module RestFtpDaemon
4
4
  class Helpers
5
5
 
6
+ def self.get_censored_config
7
+ config = Settings.to_hash
8
+ config[:users] = Settings.users.keys if Settings.users
9
+ config[:endpoints] = Settings.endpoints.keys if Settings.endpoints
10
+ config
11
+ end
12
+
6
13
  def self.format_bytes number, unit=""
7
14
  return "&Oslash;" if number.nil? || number.zero?
8
15
 
@@ -12,6 +19,11 @@ module RestFtpDaemon
12
19
  "#{converted} #{units[index]}#{unit}"
13
20
  end
14
21
 
22
+ def self.text_or_empty text
23
+ return "&Oslash;" if text.nil? || text.empty?
24
+ text
25
+ end
26
+
15
27
  def self.identifier len
16
28
  rand(36**len).to_s(36)
17
29
  end
@@ -20,12 +32,25 @@ module RestFtpDaemon
20
32
  "[#{item}]"
21
33
  end
22
34
 
23
- def self.contains_tokens(item)
24
- /\[[a-zA-Z0-9]+\]/.match(item)
35
+ def self.highlight_tokens(path)
36
+ path.gsub(/(\[[^\[]+\])/, '<span class="token">\1</span>')
25
37
  end
26
38
 
27
39
  def self.extract_filename path
28
- m = path.match /\/([^\/]+)$/
40
+ # match everything that's after a slash at the end of the string
41
+ m = path.match /\/?([^\/]+)$/
42
+ return m[1] unless m.nil?
43
+ end
44
+
45
+ def self.extract_dirname path
46
+ # match all the beginning of the string up to the last slash
47
+ m = path.match(/^(.*)\/[^\/]*$/)
48
+ return "/#{m[1]}" unless m.nil?
49
+ end
50
+
51
+ def self.extract_parent path
52
+ # m = path.match(/^(.*\/)[^\/]*\/+$/)
53
+ m = path.match(/^(.*\/)[^\/]+\/?$/)
29
54
  return m[1] unless m.nil?
30
55
  end
31
56
 
@@ -65,15 +90,16 @@ module RestFtpDaemon
65
90
  "<div class=\"transfer-method label #{klass}\">#{method.upcase}</div>"
66
91
  end
67
92
 
68
- # def snakecase
69
- # gsub(/::/, '/').
70
- # gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
71
- # gsub(/([a-z\d])([A-Z])/,'\1_\2').
72
- # tr('-', '_').
73
- # gsub(/\s/, '_').
74
- # gsub(/__+/, '_').
75
- # downcase
76
- # end
93
+ # Dates and times: date with time generator
94
+ def self.datetime_full param
95
+ return "-" if param.nil?
96
+ return param.to_datetime.strftime("%d.%m.%Y %H:%M")
97
+ end
98
+
99
+ def self.datetime_short param
100
+ return "-" if param.nil?
101
+ return param.to_datetime.strftime("%d/%m %H:%M")
102
+ end
77
103
 
78
104
  end
79
105
  end
@@ -1,5 +1,3 @@
1
- #require 'net/ftptls'
2
-
3
1
  require 'uri'
4
2
  require 'net/ftp'
5
3
  require 'double_bag_ftps'
@@ -7,18 +5,36 @@ require 'timeout'
7
5
 
8
6
  module RestFtpDaemon
9
7
  class Job < RestFtpDaemon::Common
10
- attr_accessor :id
8
+
9
+ FIELDS = [:source, :target, :priority, :notify, :overwrite, :mkdir]
10
+
11
+ attr_reader :id
11
12
  attr_accessor :wid
12
13
 
14
+ attr_reader :error
15
+ attr_reader :status
16
+
17
+ attr_reader :started_at
18
+ attr_reader :updated_at
19
+
20
+ attr_reader :params
21
+
22
+ FIELDS.each do |field|
23
+ attr_reader field
24
+ end
25
+
13
26
  def initialize job_id, params={}
14
27
  # Call super
15
28
  # super()
16
29
  info "Job.initialize"
17
30
 
18
31
  # Init context
19
- @params = params
20
32
  @id = job_id.to_s
21
33
  #set :id, job_id
34
+ FIELDS.each do |field|
35
+ instance_variable_set("@#{field.to_s}", params[field])
36
+ end
37
+ @params = {}
22
38
 
23
39
  # Protect with a mutex
24
40
  @mutex = Mutex.new
@@ -27,40 +43,29 @@ module RestFtpDaemon
27
43
  @logger = RestFtpDaemon::Logger.new(:workers, "JOB #{id}")
28
44
 
29
45
  # Flag current job
30
- set :started_at, Time.now
31
- status :created
46
+ @started_at = Time.now
47
+ @status = :created
32
48
 
33
49
  # Send first notification
34
50
  #info "Job.initialize/notify"
35
- notify "rftpd.queued"
36
- end
37
-
38
- def id
39
- @id
40
- end
41
-
42
- def priority
43
- get :priority
44
- end
45
- def get_status
46
- get :status
51
+ client_notify "rftpd.queued"
47
52
  end
48
53
 
49
54
  def process
50
55
  # Update job's status
51
- set :error, nil
56
+ @error = nil
52
57
 
53
58
  # Prepare job
54
59
  begin
55
- info "Job.process/prepare"
56
- status :preparing
60
+ info "Job.process prepare"
61
+ @status = :preparing
57
62
  prepare
58
63
 
59
64
  rescue RestFtpDaemon::JobMissingAttribute => exception
60
65
  return oops "rftpd.started", exception, :job_missing_attribute
61
66
 
62
- rescue RestFtpDaemon::JobSourceNotFound => exception
63
- return oops "rftpd.started", exception, :job_source_not_found
67
+ # rescue RestFtpDaemon::JobSourceNotFound => exception
68
+ # return oops "rftpd.started", exception, :job_source_not_found
64
69
 
65
70
  rescue RestFtpDaemon::JobUnresolvedTokens => exception
66
71
  return oops "rftpd.started", exception, :job_unresolved_tokens
@@ -68,33 +73,32 @@ module RestFtpDaemon
68
73
  rescue RestFtpDaemon::JobTargetUnparseable => exception
69
74
  return oops "rftpd.started", exception, :job_target_unparseable
70
75
 
76
+ rescue RestFtpDaemon::JobTargetUnsupported => exception
77
+ return oops "rftpd.started", exception, :job_target_unsupported
78
+
71
79
  rescue RestFtpDaemon::JobAssertionFailed => exception
72
80
  return oops "rftpd.started", exception, :job_assertion_failed
73
81
 
74
82
  rescue RestFtpDaemon::RestFtpDaemonException => exception
75
83
  return oops "rftpd.started", exception, :job_prepare_failed
76
84
 
85
+ rescue URI::InvalidURIError => exception
86
+ return oops "rftpd.started", exception, :job_target_invalid
87
+
77
88
  rescue Exception => exception
78
89
  return oops "rftpd.started", exception, :job_prepare_unhandled, true
79
90
 
80
91
  else
81
- # Update job's status
82
- info "Job.process/prepare ok"
83
- status :prepared
84
- info "Job.process/prepare status updated"
85
-
86
- # Notify rftpd.start
87
- info "Job.process/prepare notify started"
88
- notify "rftpd.started", 0
89
- info "Job.process/prepare notified started"
92
+ # Prepare done !
93
+ @status = :prepared
94
+ info "Job.process notify rftpd.started"
95
+ client_notify "rftpd.started", nil
90
96
  end
91
97
 
92
- info "Job.process prepare>transfer"
93
-
94
98
  # Process job
95
99
  begin
96
- info "Job.process/transfer"
97
- status :starting
100
+ info "Job.process transfer"
101
+ @status = :starting
98
102
  transfer
99
103
 
100
104
  rescue Errno::EHOSTDOWN => exception
@@ -109,9 +113,15 @@ module RestFtpDaemon
109
113
  rescue Net::FTPPermError => exception
110
114
  return oops "rftpd.ended", exception, :job_perm_error
111
115
 
116
+ rescue OpenSSL::SSL::SSLError => exception
117
+ return oops "rftpd.ended", exception, :job_openssl_error
118
+
112
119
  rescue Errno::EMFILE => exception
113
120
  return oops "rftpd.ended", exception, :job_too_many_open_files
114
121
 
122
+ rescue RestFtpDaemon::JobSourceNotFound => exception
123
+ return oops "rftpd.ended", exception, :job_source_not_found
124
+
115
125
  rescue RestFtpDaemon::JobTargetFileExists => exception
116
126
  return oops "rftpd.ended", exception, :job_target_file_exists
117
127
 
@@ -128,31 +138,14 @@ module RestFtpDaemon
128
138
  return oops "rftpd.ended", exception, :job_transfer_unhandled, true
129
139
 
130
140
  else
131
- # Update job's status
132
- info "Job.process finished"
133
- status :finished
134
-
135
- # Notify rftpd.ended
136
- notify "rftpd.ended", 0
141
+ # All done !
142
+ @status = :finished
143
+ info "Job.process notify rftpd.ended"
144
+ client_notify "rftpd.ended", nil
137
145
  end
138
146
 
139
147
  end
140
148
 
141
- def describe
142
- # Update realtime info
143
- #u = up_time
144
- #set :uptime, u.round(2) unless u.nil?
145
-
146
- # Return the whole structure FIXME
147
- @params.merge({
148
- id: @id,
149
- uptime: up_time.round(2)
150
- })
151
- # @mutex.synchronize do
152
- # out = @params.clone
153
- # end
154
- end
155
-
156
149
  def get attribute
157
150
  @mutex.synchronize do
158
151
  @params || {}
@@ -160,11 +153,15 @@ module RestFtpDaemon
160
153
  end
161
154
  end
162
155
 
156
+ def set_queued
157
+ @status = :queued
158
+ end
159
+
163
160
  protected
164
161
 
165
- def up_time
166
- return 0 if @params[:started_at].nil?
167
- Time.now - @params[:started_at]
162
+ def age
163
+ return 0 if @started_at.nil?
164
+ (Time.now - @started_at).round(2)
168
165
  end
169
166
 
170
167
  def wander time
@@ -184,21 +181,21 @@ module RestFtpDaemon
184
181
  @mutex.synchronize do
185
182
  @params || {}
186
183
  # return unless @params.is_a? Enumerable
187
- @params[:updated_at] = Time.now
184
+ @updated_at = Time.now
188
185
  @params[attribute] = value
189
186
  end
190
187
  end
191
188
 
192
- def status status
193
- set :status, status
194
- end
195
-
196
189
  def expand_path path
197
190
  File.expand_path replace_tokens(path)
198
191
  end
199
192
 
200
193
  def expand_url path
201
- URI::parse replace_tokens(path) rescue nil
194
+ URI::parse replace_tokens(path)
195
+ end
196
+
197
+ def contains_brackets(item)
198
+ /\[.*\]/.match(item)
202
199
  end
203
200
 
204
201
  def replace_tokens path
@@ -213,34 +210,33 @@ module RestFtpDaemon
213
210
  newpath = path.clone
214
211
  vectors.each do |from, to|
215
212
  next if to.to_s.blank?
216
- #info "Job.replace_tokens #{Helpers.tokenize(from)} > #{to}"
217
213
  newpath.gsub! Helpers.tokenize(from), to
218
214
  end
219
215
 
220
216
  # Ensure result does not contain tokens after replacement
221
- raise RestFtpDaemon::JobUnresolvedTokens if Helpers.contains_tokens newpath
217
+ raise RestFtpDaemon::JobUnresolvedTokens if contains_brackets newpath
222
218
 
223
- # All OK, return this
224
- return newpath
219
+ # All OK, return this URL stripping multiple slashes
220
+ return newpath.gsub(/([^:])\/\//, '\1/')
225
221
  end
226
222
 
227
223
  def prepare
228
224
  # Init
229
- status :preparing
225
+ @status = :preparing
230
226
  @source_method = :file
231
227
  @target_method = nil
232
228
  @source_path = nil
233
229
  @target_url = nil
234
230
 
235
231
  # Check source
236
- raise RestFtpDaemon::JobMissingAttribute unless @params[:source]
237
- @source_path = expand_path @params[:source]
232
+ raise RestFtpDaemon::JobMissingAttribute unless @source
233
+ @source_path = expand_path @source
238
234
  set :source_path, @source_path
239
235
  set :source_method, :file
240
236
 
241
237
  # Check target
242
- raise RestFtpDaemon::JobMissingAttribute unless @params[:target]
243
- @target_url = expand_url @params[:target]
238
+ raise RestFtpDaemon::JobMissingAttribute unless @target
239
+ @target_url = expand_url @target
244
240
  set :target_url, @target_url.to_s
245
241
 
246
242
  if @target_url.kind_of? URI::FTP
@@ -255,22 +251,21 @@ module RestFtpDaemon
255
251
  # Check compliance
256
252
  raise RestFtpDaemon::JobTargetUnparseable if @target_url.nil?
257
253
  raise RestFtpDaemon::JobTargetUnsupported if @target_method.nil?
258
- #raise RestFtpDaemon::JobSourceNotFound unless File.exists? @source_path
259
254
  end
260
255
 
261
256
  def transfer
262
257
  # Method assertions and init
263
- status :checking_source
258
+ @status = :checking_source
264
259
  raise RestFtpDaemon::JobAssertionFailed unless @source_path && @target_url
265
260
  @transfer_sent = 0
266
- set :transfer_source_done, 0
261
+ set :source_processed, 0
267
262
 
268
263
  # Guess source file names using Dir.glob
269
264
  source_matches = Dir.glob @source_path
270
- info "Job.transfer sources: #{source_matches}"
265
+ info "Job.transfer sources #{source_matches.inspect}"
271
266
  raise RestFtpDaemon::JobSourceNotFound if source_matches.empty?
272
- set :transfer_source_count, source_matches.count
273
- set :transfer_source_files, source_matches
267
+ set :source_count, source_matches.count
268
+ set :source_files, source_matches
274
269
 
275
270
  # Guess target file name, and fail if present while we matched multiple sources
276
271
  target_name = Helpers.extract_filename @target_url.path
@@ -280,7 +275,12 @@ module RestFtpDaemon
280
275
  ftp_init
281
276
 
282
277
  # Connect remote server, login and chdir
283
- ftp_connect
278
+ ftp_connect_and_login
279
+
280
+ # Connect remote server, login and chdir
281
+ #path = '/' + Helpers.extract_dirname(@target_url.path).to_s
282
+ path = Helpers.extract_dirname(@target_url.path).to_s
283
+ ftp_chdir_or_buildpath path
284
284
 
285
285
  # Check source files presence and compute total size, they should be there, coming from Dir.glob()
286
286
  @transfer_total = 0
@@ -296,7 +296,7 @@ module RestFtpDaemon
296
296
  source_matches.each do |filename|
297
297
  ftp_transfer filename, target_name
298
298
  done += 1
299
- set :transfer_source_done, done
299
+ set :source_processed, done
300
300
  end
301
301
 
302
302
  # Add total transferred to counter
@@ -304,7 +304,7 @@ module RestFtpDaemon
304
304
 
305
305
  # Close FTP connexion
306
306
  info "Job.transfer disconnecting"
307
- status :disconnecting
307
+ @status = :disconnecting
308
308
  @ftp.close
309
309
  end
310
310
 
@@ -316,68 +316,127 @@ module RestFtpDaemon
316
316
  info "Job.oops si[#{signal_name}] er[#{error_name.to_s}] ex[#{exception.class}]"
317
317
 
318
318
  # Update job's internal status
319
- set :status, :failed
320
- set :error, error_name
319
+ @status = :failed
320
+ @error = error_name
321
+ set :error_name, error_name
321
322
  set :error_exception, exception.class
323
+ set :error_message, exception.message
322
324
 
323
325
  # Build status stack
324
- status = nil
326
+ notif_status = nil
325
327
  if include_backtrace
326
328
  set :error_backtrace, exception.backtrace
327
- status = {
329
+ notif_status = {
328
330
  backtrace: exception.backtrace,
329
331
  }
330
332
  end
331
333
 
334
+ # Increment counter for this error
335
+ $queue.counter_inc "err_#{error_name}"
336
+ $queue.counter_inc :jobs_failed
337
+
332
338
  # Prepare notification if signal given
333
339
  return unless signal_name
334
- notify signal_name, error_name, status
340
+ client_notify signal_name, error_name, notif_status
335
341
  end
336
342
 
337
343
  def ftp_init
338
344
  # Method assertions
339
- info "Job.ftp_init"
340
- status :ftp_init
345
+ info "Job.ftp_init asserts"
346
+ @status = :ftp_init
341
347
  raise RestFtpDaemon::JobAssertionFailed if @target_method.nil? || @target_url.nil?
342
348
 
349
+ info "Job.ftp_init target_method [#{@target_method}]"
343
350
  case @target_method
344
351
  when :ftp
345
- info "Job.ftp_init scheme: ftp"
346
352
  @ftp = Net::FTP.new
347
353
  when :ftps
348
- info "Job.transfer scheme: ftps"
349
354
  @ftp = DoubleBagFTPS.new
350
355
  @ftp.ssl_context = DoubleBagFTPS.create_ssl_context(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
351
356
  @ftp.ftps_mode = DoubleBagFTPS::EXPLICIT
352
357
  else
353
- info "Job.transfer scheme: other [#{@target_url.scheme}]"
358
+ info "Job.transfer unknown scheme [#{@target_url.scheme}]"
359
+ railse RestFtpDaemon::JobTargetUnsupported
354
360
  end
361
+
362
+ # Common setup
363
+ @ftp.debug_mode = DEBUG_FTP_COMMANDS
364
+ @ftp.passive = true
355
365
  end
356
366
 
357
- def ftp_connect
358
- #status :ftp_connect
367
+ def ftp_connect_and_login
368
+ #@status = :ftp_connect
359
369
  # connect_timeout_sec = (Settings.transfer.connect_timeout_sec rescue nil) || DEFAULT_CONNECT_TIMEOUT_SEC
360
370
 
361
371
  # Method assertions
362
- info "Job.ftp_connect connect"
363
- status :ftp_connect
372
+ host = @target_url.host
373
+ info "Job.ftp_connect connect [#{host}]"
374
+ @status = :ftp_connect
364
375
  raise RestFtpDaemon::JobAssertionFailed if @ftp.nil? || @target_url.nil?
365
- @ftp.connect(@target_url.host)
366
- @ftp.passive = true
376
+ @ftp.connect(host)
367
377
 
368
- info "Job.ftp_connect login"
369
- status :ftp_login
370
- ret = @ftp.login @target_url.user, @target_url.password
378
+ @status = :ftp_login
379
+ info "Job.ftp_connect login [#{@target_url.user}]"
380
+ @ftp.login @target_url.user, @target_url.password
381
+ end
371
382
 
372
- info "Job.ftp_connect chdir"
373
- status :ftp_chdir
374
- path = File.dirname @target_url.path
375
- ret = @ftp.chdir(path)
383
+ def ftp_chdir_or_buildpath path
384
+ # Method assertions
385
+ info "Job.ftp_chdir [#{path}] mkdir: #{@mkdir}"
386
+ raise RestFtpDaemon::JobAssertionFailed if path.nil?
387
+ @status = :ftp_chdir
388
+
389
+ # Extract directory from path
390
+ if @mkdir
391
+ # Split dir in parts
392
+ info "Job.ftp_chdir buildpath [#{path}]"
393
+ ftp_buildpath path
394
+ else
395
+ # Directly chdir if not mkdir requested
396
+ info "Job.ftp_chdir chdir [#{path}]"
397
+ @ftp.chdir path
398
+ end
376
399
  end
377
400
 
401
+ def ftp_buildpath path
402
+ # Init
403
+ pref = "Job.ftp_buildpath [#{path}]"
404
+
405
+ begin
406
+ # Try to chdir in this directory
407
+ @ftp.chdir(path)
408
+
409
+ rescue Net::FTPPermError => exception
410
+ # If not possible because the directory is missing
411
+ parent = Helpers.extract_parent(path)
412
+ info "#{pref} chdir failed - parent [#{parent}]"
413
+
414
+ # And only if we still have something to "dive up into"
415
+ if parent
416
+ # Do the same for the parent
417
+ ftp_buildpath parent
418
+
419
+ # Then finally create this dir and chdir
420
+ info "#{pref} > now mkdir [#{path}]"
421
+ @ftp.mkdir path
422
+
423
+ # And get into it (this chdir is not rescue'd on purpose in order to throw the ex)
424
+ info "#{pref} > now chdir [#{path}]"
425
+ @ftp.chdir(path)
426
+ end
427
+
428
+ end
429
+
430
+ # Now we were able to chdir inside, just tell it
431
+ info "#{pref} changed to [#{@ftp.pwd}]"
432
+ end
433
+
434
+
378
435
  def ftp_presence target_name
436
+ # FIXME / TODO: try with nlst
437
+
379
438
  # Method assertions
380
- status :ftp_presence
439
+ @status = :ftp_presence
381
440
  raise RestFtpDaemon::JobAssertionFailed if @ftp.nil? || @target_url.nil?
382
441
 
383
442
  # Get file list, sometimes the response can be an empty value
@@ -390,24 +449,21 @@ module RestFtpDaemon
390
449
  end
391
450
 
392
451
  def ftp_transfer source_match, target_name = nil
393
- #target_name
394
452
  # Method assertions
395
- info "Job.ftp_transfer source_match: #{source_match}"
453
+ info "Job.ftp_transfer source_match [#{source_match}]"
396
454
  raise RestFtpDaemon::JobAssertionFailed if @ftp.nil?
397
455
  raise RestFtpDaemon::JobAssertionFailed if source_match.nil?
398
- #raise RestFtpDaemon::JobAssertionFailed if @transfer_total.nil?
399
- status :ftp_transfer
400
456
 
401
457
  # Use source filename if target path provided none (typically with multiple sources)
402
458
  target_name ||= Helpers.extract_filename source_match
403
- info "Job.ftp_transfer target_name: #{target_name}"
459
+ info "Job.ftp_transfer target_name [#{target_name}]"
460
+ set :source_processing, target_name
404
461
 
405
462
  # Check for target file presence
406
- status :checking_target
407
- overwrite = !get(:overwrite).nil?
463
+ @status = :checking_target
408
464
  present = ftp_presence target_name
409
465
  if present
410
- if overwrite
466
+ if @overwrite
411
467
  # delete it first
412
468
  info "Job.ftp_transfer removing target file"
413
469
  @ftp.delete(target_name)
@@ -427,7 +483,7 @@ module RestFtpDaemon
427
483
  chunk_size = update_every_kb * 1024
428
484
  t0 = tstart = Time.now
429
485
  notified_at = Time.now
430
- status :uploading
486
+ @status = :uploading
431
487
  @ftp.putbinaryfile(source_match, target_name, chunk_size) do |block|
432
488
  # Update counters
433
489
  @transfer_sent += block.bytesize
@@ -443,42 +499,41 @@ module RestFtpDaemon
443
499
  set :progress, percent1
444
500
 
445
501
  # Log progress
446
- status = []
447
- status << "#{percent1} %"
448
- status << (Helpers.format_bytes @transfer_sent, "B")
449
- status << (Helpers.format_bytes @transfer_total, "B")
450
- status << (Helpers.format_bytes bitrate0, "bps")
451
- info "Job.ftp_transfer" + status.map{|txt| ("%#{DEFAULT_LOGS_PROGNAME_TRIM.to_i}s" % txt)}.join("\t")
502
+ stack = []
503
+ stack << "#{percent1} %"
504
+ stack << (Helpers.format_bytes @transfer_sent, "B")
505
+ stack << (Helpers.format_bytes @transfer_total, "B")
506
+ stack << (Helpers.format_bytes bitrate0, "bps")
507
+ info "Job.ftp_transfer" + stack.map{|txt| ("%#{DEFAULT_LOGS_PROGNAME_TRIM.to_i}s" % txt)}.join("\t")
452
508
 
453
509
  # Update time pointer
454
510
  t0 = Time.now
455
511
 
456
512
  # Notify if requested
457
513
  unless notify_after_sec.nil? || (notified_at + notify_after_sec > Time.now)
458
- status = {
514
+ notif_status = {
459
515
  progress: percent1,
460
516
  transfer_sent: @transfer_sent,
461
517
  transfer_total: @transfer_total,
462
518
  transfer_bitrate: bitrate0
463
519
  }
464
- notify "rftpd.progress", 0, status
520
+ client_notify "rftpd.progress", nil, notif_status
465
521
  notified_at = Time.now
466
522
  end
467
523
 
468
524
  end
469
525
 
470
526
  # Compute final bitrate
471
- #tbitrate0 = (8 * @transfer_total.to_f / (Time.now - tstart)).round(0)
472
- tbitrate0 = get_bitrate(@transfer_total, tstart).round(0)
473
- set :transfer_bitrate, tbitrate0
527
+ set :transfer_bitrate, get_bitrate(@transfer_total, tstart).round(0)
474
528
 
475
529
  # Done
476
- #set :progress, nil
530
+ set :source_processing, nil
477
531
  info "Job.ftp_transfer finished"
532
+ $queue.counter_inc :jobs_finished
478
533
  end
479
534
 
480
- def notify signal, error = 0, status = {}
481
- RestFtpDaemon::Notification.new get(:notify), {
535
+ def client_notify signal, error = nil, status = {}
536
+ RestFtpDaemon::Notification.new @notify, {
482
537
  id: @id,
483
538
  signal: signal,
484
539
  error: error,