rest-ftp-daemon 0.90.1 → 0.94.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,