rest-ftp-daemon 0.400.0 → 0.410.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +26 -6
- data/Rakefile +5 -3
- data/bin/rest-ftp-daemon +2 -1
- data/config.ru +8 -2
- data/lib/rest-ftp-daemon.rb +10 -2
- data/lib/rest-ftp-daemon/api/debug.rb +17 -5
- data/lib/rest-ftp-daemon/api/{job_presenter.rb → entities/job.rb} +25 -2
- data/lib/rest-ftp-daemon/api/entities/options.rb +15 -0
- data/lib/rest-ftp-daemon/api/jobs.rb +46 -13
- data/lib/rest-ftp-daemon/api/root-real.rb +80 -0
- data/lib/rest-ftp-daemon/api/root-test.rb +69 -0
- data/lib/rest-ftp-daemon/api/root.rb +21 -5
- data/lib/rest-ftp-daemon/constants.rb +18 -1
- data/lib/rest-ftp-daemon/counters.rb +0 -3
- data/lib/rest-ftp-daemon/exceptions.rb +16 -12
- data/lib/rest-ftp-daemon/helpers/api.rb +3 -3
- data/lib/rest-ftp-daemon/helpers/common.rb +12 -5
- data/lib/rest-ftp-daemon/helpers/views.rb +41 -11
- data/lib/rest-ftp-daemon/job.rb +118 -420
- data/lib/rest-ftp-daemon/job_queue.rb +34 -5
- data/lib/rest-ftp-daemon/jobs/dummy.rb +20 -0
- data/lib/rest-ftp-daemon/jobs/transfer.rb +319 -0
- data/lib/rest-ftp-daemon/jobs/video.rb +107 -0
- data/lib/rest-ftp-daemon/location.rb +125 -0
- data/lib/rest-ftp-daemon/path.rb +3 -0
- data/lib/rest-ftp-daemon/remote_ftp.rb +2 -2
- data/lib/rest-ftp-daemon/remote_sftp.rb +2 -2
- data/lib/rest-ftp-daemon/static/config.json +411 -0
- data/lib/rest-ftp-daemon/static/css/bootstrap.min.css +14 -0
- data/lib/rest-ftp-daemon/static/css/{bootstrap.css → bootstrap.old.css} +0 -0
- data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.svg +288 -0
- data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/lib/rest-ftp-daemon/static/swagger.html +83 -0
- data/lib/rest-ftp-daemon/static/swagger/css/print.css +1362 -0
- data/lib/rest-ftp-daemon/static/swagger/css/reset.css +125 -0
- data/lib/rest-ftp-daemon/static/swagger/css/screen.css +1489 -0
- data/lib/rest-ftp-daemon/static/swagger/css/style.css +250 -0
- data/lib/rest-ftp-daemon/static/swagger/css/typography.css +14 -0
- data/lib/rest-ftp-daemon/static/swagger/fonts/DroidSans-Bold.ttf +0 -0
- data/lib/rest-ftp-daemon/static/swagger/fonts/DroidSans.ttf +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/collapse.gif +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/expand.gif +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/explorer_icons.png +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/favicon-16x16.png +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/favicon-32x32.png +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/favicon.ico +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/logo_small.png +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/pet_store_api.png +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/throbber.gif +0 -0
- data/lib/rest-ftp-daemon/static/swagger/images/wordnik_api.png +0 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/backbone-min.js +15 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/es5-shim.js +2065 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/handlebars-4.0.5.js +4608 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/highlight.9.1.0.pack.js +2 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/highlight.9.1.0.pack_extended.js +34 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/jquery-1.8.0.min.js +2 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/jquery.ba-bbq.min.js +18 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/jquery.slideto.min.js +1 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/jquery.wiggle.min.js +8 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/js-yaml.min.js +3 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/jsoneditor.min.js +11 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/lodash.min.js +102 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/marked.js +1272 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/object-assign-pollyfill.js +23 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/swagger-oauth.js +347 -0
- data/lib/rest-ftp-daemon/static/swagger/lib/swagger-ui.min.js +10 -0
- data/lib/rest-ftp-daemon/uri.rb +9 -1
- data/lib/rest-ftp-daemon/views/dashboard.haml +1 -1
- data/lib/rest-ftp-daemon/views/dashboard_footer.haml +6 -3
- data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +3 -3
- data/lib/rest-ftp-daemon/views/dashboard_table.haml +25 -20
- data/lib/rest-ftp-daemon/workers/transfer.rb +14 -0
- data/rest-ftp-daemon.gemspec +14 -4
- data/spec/rest-ftp-daemon/features/swagger_spec.rb +24 -0
- data/spec/spec_helper.rb +4 -4
- metadata +119 -14
@@ -35,6 +35,35 @@ module RestFtpDaemon
|
|
35
35
|
log_info "JobQueue initialized (prefix: #{@prefix})"
|
36
36
|
end
|
37
37
|
|
38
|
+
def create_job params
|
39
|
+
# Build class name and chock if it exists
|
40
|
+
# klass = Kernel.const_get("Job#{params[:type].to_s.capitalize}") rescue nil
|
41
|
+
klass_name = "Job#{params[:type].to_s.capitalize}"
|
42
|
+
klass = Kernel.const_get(klass_name) rescue nil
|
43
|
+
|
44
|
+
# If object not found, don't create a job !
|
45
|
+
unless klass && klass < Job
|
46
|
+
message = "can't create [#{klass_name}] for type [#{params[:type]}]"
|
47
|
+
log_error "JobQueue.create_job: #{message}"
|
48
|
+
raise QueueCantCreateJob, message
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generate an ID and stack it
|
52
|
+
@mutex.synchronize do
|
53
|
+
@last_id += 1
|
54
|
+
end
|
55
|
+
job_id = prefixed_id(@last_id)
|
56
|
+
|
57
|
+
# Instantiate it and return the now object
|
58
|
+
log_info "JobQueue.create_job: creating [#{klass.name}] with ID [#{job_id}]"
|
59
|
+
job = klass.new(job_id, params)
|
60
|
+
|
61
|
+
# Push it on the queue
|
62
|
+
push job
|
63
|
+
|
64
|
+
return job
|
65
|
+
end
|
66
|
+
|
38
67
|
def generate_id
|
39
68
|
@mutex.synchronize do
|
40
69
|
@last_id += 1
|
@@ -121,13 +150,13 @@ module RestFtpDaemon
|
|
121
150
|
|
122
151
|
def push job
|
123
152
|
# Check that item responds to "priorty" method
|
124
|
-
raise "JobQueue.push: job should respond to priority
|
125
|
-
raise "JobQueue.push: job should respond to id
|
126
|
-
raise "JobQueue.push: job should respond to pool
|
127
|
-
raise "JobQueue.push: job should respond to reset" unless job.respond_to? :reset
|
153
|
+
raise "JobQueue.push: job should respond to: priority" unless job.respond_to? :priority
|
154
|
+
raise "JobQueue.push: job should respond to: id" unless job.respond_to? :id
|
155
|
+
raise "JobQueue.push: job should respond to: pool" unless job.respond_to? :pool
|
156
|
+
raise "JobQueue.push: job should respond to: reset" unless job.respond_to? :reset
|
128
157
|
|
129
158
|
@mutex.synchronize do
|
130
|
-
# Get this job's pool
|
159
|
+
# Get this job's pool & prepare queue of this pool
|
131
160
|
pool = job.pool
|
132
161
|
myqueue = (@queues[pool] ||= [])
|
133
162
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
class JobDummy < Job
|
3
|
+
|
4
|
+
# def initialize job_id, params = {}
|
5
|
+
# super
|
6
|
+
# end
|
7
|
+
|
8
|
+
def before
|
9
|
+
end
|
10
|
+
|
11
|
+
def work
|
12
|
+
log_info "JobDummy.work YEAH WE'RE PROCESSING, man !"
|
13
|
+
sleep 5
|
14
|
+
end
|
15
|
+
|
16
|
+
def after
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
#FIXME: move progress from Job/infos/transfer to Job/progress
|
2
|
+
|
3
|
+
module RestFtpDaemon
|
4
|
+
class JobTransfer < Job
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def before
|
9
|
+
# Prepare flags
|
10
|
+
flag_prepare :mkdir, false
|
11
|
+
flag_prepare :overwrite, false
|
12
|
+
flag_prepare :tempfile, true
|
13
|
+
|
14
|
+
# Some init
|
15
|
+
@transfer_sent = 0
|
16
|
+
set_info :source, :processed, 0
|
17
|
+
|
18
|
+
# Ensure source is FILE
|
19
|
+
raise RestFtpDaemon::SourceNotSupported, @source_loc.scheme unless source_uri.is_a? URI::FILE
|
20
|
+
|
21
|
+
# Prepare remote (case would be preferable but too hard to use,
|
22
|
+
# as target could be of a descendent class of URI:XXX and not matching directly)
|
23
|
+
case target_uri
|
24
|
+
when URI::FTP
|
25
|
+
log_info "JobTransfer.before target_method FTP"
|
26
|
+
@remote = RemoteFTP.new @target_loc.uri, log_prefix, debug: @config[:debug_ftp]
|
27
|
+
when URI::FTPES, URI::FTPS
|
28
|
+
log_info "JobTransfer.before target_method FTPES/FTPS"
|
29
|
+
@remote = RemoteFTP.new @target_loc.uri, log_prefix, debug: @config[:debug_ftps], ftpes: true
|
30
|
+
when URI::SFTP
|
31
|
+
log_info "JobTransfer.before target_method SFTP"
|
32
|
+
@remote = RemoteSFTP.new @target_loc.uri, log_prefix, debug: @config[:debug_sftp]
|
33
|
+
else
|
34
|
+
log_info "JobTransfer.before unknown scheme [#{@target_loc.scheme}]"
|
35
|
+
raise RestFtpDaemon::TargetNotSupported, @target_loc.scheme
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue RestFtpDaemon::AssertionFailed => exception
|
39
|
+
return oops :started, exception
|
40
|
+
|
41
|
+
# rescue URI::InvalidURIError => exception
|
42
|
+
# return oops :started, exception, "target_invalid"
|
43
|
+
end
|
44
|
+
|
45
|
+
def work
|
46
|
+
# Scan local source files from disk
|
47
|
+
set_status JOB_STATUS_CHECKING_SRC
|
48
|
+
sources = scan_local_paths @source_loc.path
|
49
|
+
set_info :source, :count, sources.count
|
50
|
+
set_info :source, :files, sources.collect(&:full)
|
51
|
+
log_info "JobTransfer.work sources #{sources.collect(&:name)}"
|
52
|
+
raise RestFtpDaemon::SourceNotFound if sources.empty?
|
53
|
+
|
54
|
+
# Guess target file name, and fail if present while we matched multiple sources
|
55
|
+
raise RestFtpDaemon::TargetDirectoryError if @target_loc.name && sources.count>1
|
56
|
+
|
57
|
+
# Connect to remote server and login
|
58
|
+
set_status JOB_STATUS_CONNECTING
|
59
|
+
@remote.connect
|
60
|
+
|
61
|
+
# Prepare target path or build it if asked
|
62
|
+
set_status JOB_STATUS_CHDIR
|
63
|
+
@remote.chdir_or_create @target_loc.dir, @mkdir
|
64
|
+
|
65
|
+
# Compute total files size
|
66
|
+
@transfer_total = sources.collect(&:size).sum
|
67
|
+
set_info :work, :total, @transfer_total
|
68
|
+
|
69
|
+
# Reset counters
|
70
|
+
@last_data = 0
|
71
|
+
@last_time = Time.now
|
72
|
+
|
73
|
+
# Handle each source file matched, and start a transfer
|
74
|
+
source_processed = 0
|
75
|
+
targets = []
|
76
|
+
sources.each do |source|
|
77
|
+
# Compute target filename
|
78
|
+
target_final = @target_loc.clone
|
79
|
+
|
80
|
+
# Add the source file name if none found in the target path
|
81
|
+
unless full_target.name
|
82
|
+
target_final.name = source.name
|
83
|
+
end
|
84
|
+
|
85
|
+
# Do the transfer, for each file
|
86
|
+
remote_push source, target_final
|
87
|
+
|
88
|
+
# Add it to transferred target names
|
89
|
+
targets << target_final.path
|
90
|
+
set_info :target, :files, targets
|
91
|
+
|
92
|
+
# Update counters
|
93
|
+
set_info :source, :processed, source_processed += 1
|
94
|
+
end
|
95
|
+
|
96
|
+
rescue SocketError => exception
|
97
|
+
return oops :ended, exception, "conn_socket_error"
|
98
|
+
|
99
|
+
rescue EOFError => exception
|
100
|
+
return oops :ended, exception, "conn_eof"
|
101
|
+
|
102
|
+
rescue Errno::EHOSTDOWN => exception
|
103
|
+
return oops :ended, exception, "conn_host_is_down"
|
104
|
+
|
105
|
+
rescue Errno::EPIPE=> exception
|
106
|
+
return oops :ended, exception, "conn_broken_pipe"
|
107
|
+
|
108
|
+
rescue Errno::ENETUNREACH => exception
|
109
|
+
return oops :ended, exception, "conn_unreachable"
|
110
|
+
|
111
|
+
rescue Errno::ECONNRESET => exception
|
112
|
+
return oops :ended, exception, "conn_reset_by_peer"
|
113
|
+
|
114
|
+
rescue Errno::ENOTCONN => exception
|
115
|
+
return oops :ended, exception, "conn_failed"
|
116
|
+
|
117
|
+
rescue Errno::ECONNREFUSED => exception
|
118
|
+
return oops :ended, exception, "conn_refused"
|
119
|
+
|
120
|
+
rescue Timeout::Error, Errno::ETIMEDOUT, Net::ReadTimeout => exception
|
121
|
+
return oops :ended, exception, "conn_timed_out"
|
122
|
+
|
123
|
+
rescue OpenSSL::SSL::SSLError => exception
|
124
|
+
return oops :ended, exception, "conn_openssl_error"
|
125
|
+
|
126
|
+
rescue Net::FTPReplyError => exception
|
127
|
+
return oops :ended, exception, "ftp_reply_error"
|
128
|
+
|
129
|
+
rescue Net::FTPTempError => exception
|
130
|
+
return oops :ended, exception, "ftp_temp_error"
|
131
|
+
|
132
|
+
rescue Net::FTPPermError => exception
|
133
|
+
return oops :ended, exception, "ftp_perm_error"
|
134
|
+
|
135
|
+
rescue Net::FTPProtoError => exception
|
136
|
+
return oops :ended, exception, "ftp_proto_error"
|
137
|
+
|
138
|
+
rescue Net::FTPError => exception
|
139
|
+
return oops :ended, exception, "ftp_error"
|
140
|
+
|
141
|
+
rescue Net::SFTP::StatusException => exception
|
142
|
+
return oops :ended, exception, "sftp_exception"
|
143
|
+
|
144
|
+
rescue Net::SSH::HostKeyMismatch => exception
|
145
|
+
return oops :ended, exception, "sftp_key_mismatch"
|
146
|
+
|
147
|
+
rescue Net::SSH::AuthenticationFailed => exception
|
148
|
+
return oops :ended, exception, "sftp_auth_failed"
|
149
|
+
|
150
|
+
rescue Errno::EMFILE => exception
|
151
|
+
return oops :ended, exception, "too_many_open_files"
|
152
|
+
|
153
|
+
rescue Errno::EINVAL => exception
|
154
|
+
return oops :ended, exception, "invalid_argument", true
|
155
|
+
|
156
|
+
# rescue Encoding::UndefinedConversionError => exception
|
157
|
+
# return oops :ended, exception, "encoding_error", true
|
158
|
+
|
159
|
+
rescue RestFtpDaemon::SourceNotFound => exception
|
160
|
+
return oops :ended, exception
|
161
|
+
|
162
|
+
rescue RestFtpDaemon::TargetFileExists => exception
|
163
|
+
return oops :ended, exception
|
164
|
+
|
165
|
+
rescue RestFtpDaemon::TargetDirectoryError => exception
|
166
|
+
return oops :ended, exception
|
167
|
+
|
168
|
+
rescue RestFtpDaemon::TargetPermissionError => exception
|
169
|
+
return oops :ended, exception
|
170
|
+
|
171
|
+
rescue RestFtpDaemon::AssertionFailed => exception
|
172
|
+
return oops :ended, exception
|
173
|
+
end
|
174
|
+
|
175
|
+
def after
|
176
|
+
# Close FTP connexion and free up memory
|
177
|
+
log_info "JobTransfer.after"
|
178
|
+
@remote.close
|
179
|
+
|
180
|
+
# Free-up remote object
|
181
|
+
@remote = nil
|
182
|
+
|
183
|
+
# Update job status
|
184
|
+
set_status JOB_STATUS_DISCONNECTING
|
185
|
+
@finished_at = Time.now
|
186
|
+
|
187
|
+
# Update counters
|
188
|
+
RestFtpDaemon::Counters.instance.increment :jobs, :finished
|
189
|
+
RestFtpDaemon::Counters.instance.add :data, :transferred, @transfer_total
|
190
|
+
end
|
191
|
+
|
192
|
+
def remote_push source, target
|
193
|
+
# Method assertions
|
194
|
+
raise RestFtpDaemon::AssertionFailed, "remote_push/remote" if @remote.nil?
|
195
|
+
raise RestFtpDaemon::AssertionFailed, "remote_push/source" if source.nil?
|
196
|
+
raise RestFtpDaemon::AssertionFailed, "remote_push/target" if target.nil?
|
197
|
+
|
198
|
+
# Use source filename if target path provided none (typically with multiple sources)
|
199
|
+
log_info "JobTransfer.remote_push [#{source.name}]: [#{source.full}] > [#{target.full}]"
|
200
|
+
set_info :source, :current, source.name
|
201
|
+
|
202
|
+
# Compute temp target name
|
203
|
+
tempname = nil
|
204
|
+
if @tempfile
|
205
|
+
tempname = "#{target.name}.temp-#{identifier(JOB_TEMPFILE_LEN)}"
|
206
|
+
log_debug "JobTransfer.remote_push tempname [#{tempname}]"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Remove any existing version if expected, or test its presence
|
210
|
+
if @overwrite
|
211
|
+
@remote.remove! target
|
212
|
+
elsif size = @remote.present?(target)
|
213
|
+
log_debug "JobTransfer.remote_push existing (#{format_bytes size, 'B'})"
|
214
|
+
raise RestFtpDaemon::TargetFileExists
|
215
|
+
end
|
216
|
+
|
217
|
+
# Start transfer
|
218
|
+
transfer_started_at = Time.now
|
219
|
+
@progress_at = 0
|
220
|
+
@notified_at = transfer_started_at
|
221
|
+
|
222
|
+
# Start the transfer, update job status after each block transfer
|
223
|
+
set_status JOB_STATUS_UPLOADING
|
224
|
+
@remote.push source, target, tempname do |transferred, name|
|
225
|
+
# Update transfer statistics
|
226
|
+
progress transferred, name
|
227
|
+
|
228
|
+
# Touch my worker status
|
229
|
+
touch_job
|
230
|
+
end
|
231
|
+
|
232
|
+
# Compute final bitrate
|
233
|
+
global_transfer_bitrate = get_bitrate @transfer_total, (Time.now - transfer_started_at)
|
234
|
+
set_info :work, :bitrate, global_transfer_bitrate.round(0)
|
235
|
+
|
236
|
+
# Done
|
237
|
+
set_info :source, :current, nil
|
238
|
+
end
|
239
|
+
|
240
|
+
def progress transferred, name = ""
|
241
|
+
|
242
|
+
|
243
|
+
# What's current time ?
|
244
|
+
now = Time.now
|
245
|
+
notify_after = @config[:notify_after]
|
246
|
+
|
247
|
+
# Update counters
|
248
|
+
@transfer_sent += transferred
|
249
|
+
set_info :work, :sent, @transfer_sent
|
250
|
+
|
251
|
+
# Update job info
|
252
|
+
percent0 = (100.0 * @transfer_sent / @transfer_total).round(0)
|
253
|
+
set_info :work, :progress, percent0
|
254
|
+
|
255
|
+
# Update job status after each NOTIFY_UPADE_STATUS
|
256
|
+
progressed_ago = (now.to_f - @progress_at.to_f)
|
257
|
+
if (!JOB_UPDATE_INTERVAL.to_f.zero?) && (progressed_ago > JOB_UPDATE_INTERVAL.to_f)
|
258
|
+
@current_bitrate = running_bitrate @transfer_sent
|
259
|
+
set_info :work, :bitrate, @current_bitrate.round(0)
|
260
|
+
|
261
|
+
# Log progress
|
262
|
+
stack = []
|
263
|
+
stack << "#{percent0} %"
|
264
|
+
stack << (format_bytes @transfer_sent, "B")
|
265
|
+
stack << (format_bytes @transfer_total, "B")
|
266
|
+
stack << (format_bytes @current_bitrate.round(0), "bps")
|
267
|
+
stack2 = stack.map { |txt| ("%#{LOG_PIPE_LEN.to_i}s" % txt) }.join("\t")
|
268
|
+
log_debug "progress #{stack2} \t#{name}"
|
269
|
+
|
270
|
+
# Remember when we last did it
|
271
|
+
@progress_at = now
|
272
|
+
end
|
273
|
+
|
274
|
+
# Notify if requested
|
275
|
+
notified_ago = (now.to_f - @notified_at.to_f)
|
276
|
+
if (!notify_after.nil?) && (notified_ago > notify_after)
|
277
|
+
# Prepare and send notification
|
278
|
+
notif_status = {
|
279
|
+
progress: percent0,
|
280
|
+
transfer_sent: @transfer_sent,
|
281
|
+
transfer_total: @transfer_total,
|
282
|
+
transfer_bitrate: @current_bitrate.round(0),
|
283
|
+
}
|
284
|
+
client_notify :progress, status: notif_status
|
285
|
+
|
286
|
+
# Remember when we last did it
|
287
|
+
@notified_at = now
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
|
293
|
+
def get_bitrate delta_data, delta_time
|
294
|
+
return nil if delta_time.nil? || delta_time.zero?
|
295
|
+
8 * delta_data.to_f.to_f / delta_time
|
296
|
+
end
|
297
|
+
|
298
|
+
def running_bitrate current_data
|
299
|
+
return if @last_time.nil?
|
300
|
+
|
301
|
+
# Compute deltas
|
302
|
+
@last_data ||= 0
|
303
|
+
delta_data = current_data - @last_data
|
304
|
+
delta_time = Time.now - @last_time
|
305
|
+
|
306
|
+
# Update counters
|
307
|
+
@last_time = Time.now
|
308
|
+
@last_data = current_data
|
309
|
+
|
310
|
+
# Return bitrate
|
311
|
+
get_bitrate delta_data, delta_time
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# NewRelic instrumentation
|
316
|
+
# add_transaction_tracer :prepare, category: :task
|
317
|
+
# add_transaction_tracer :run, category: :task
|
318
|
+
|
319
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# FIXME: handle overwrite
|
2
|
+
# FIXME: progress over multiple files
|
3
|
+
# FIXME: open movie files to guess total runtime
|
4
|
+
# FIXME: analyze media files at prepare
|
5
|
+
|
6
|
+
require 'streamio-ffmpeg'
|
7
|
+
|
8
|
+
module RestFtpDaemon
|
9
|
+
class JobVideo < Job
|
10
|
+
|
11
|
+
# Process job
|
12
|
+
def before
|
13
|
+
log_info "JobVideo.before source_loc.path: #{@source_loc.path}"
|
14
|
+
log_info "JobVideo.before target_loc.path: #{@target_loc.path}"
|
15
|
+
|
16
|
+
# Ensure source and target are FILE
|
17
|
+
raise RestFtpDaemon::SourceNotSupported, @source_loc.scheme unless source_uri.is_a? URI::FILE
|
18
|
+
raise RestFtpDaemon::TargetNotSupported, @target.scheme unless target_uri.is_a? URI::FILE
|
19
|
+
end
|
20
|
+
|
21
|
+
def work
|
22
|
+
# Guess source files from disk
|
23
|
+
set_status JOB_STATUS_TRANSFORMING
|
24
|
+
sources = scan_local_paths @source_loc.path
|
25
|
+
raise RestFtpDaemon::SourceNotFound if sources.empty?
|
26
|
+
|
27
|
+
# Add the source file name if none found in the target path
|
28
|
+
target_final = @target_loc.clone
|
29
|
+
target_final.name = @source_loc.name unless target_final.name
|
30
|
+
log_info "JobVideo.work target_final.path [#{target_final.path}]"
|
31
|
+
|
32
|
+
# Ensure target directory exists
|
33
|
+
log_info "JobVideo.work mkdir_p [#{@target_loc.dir}]"
|
34
|
+
FileUtils.mkdir_p @target_loc.dir
|
35
|
+
|
36
|
+
# Do the work, for each file
|
37
|
+
set_info :source, :current, @source_loc.name
|
38
|
+
video_command @source_loc, target_final
|
39
|
+
|
40
|
+
# Done
|
41
|
+
set_info :source, :current, nil
|
42
|
+
|
43
|
+
rescue FFMPEG::Error => exception
|
44
|
+
return oops :ended, exception, "ffmpeg_error"
|
45
|
+
end
|
46
|
+
|
47
|
+
def after
|
48
|
+
# Done
|
49
|
+
set_info :source, :current, nil
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def video_command source, target
|
55
|
+
log_info "JobVideo.video_command [#{source.name}]: [#{source.path}] > [#{target.path}]"
|
56
|
+
set_info :source, :current, source.name
|
57
|
+
|
58
|
+
# Read info about source file
|
59
|
+
movie = FFMPEG::Movie.new(source.path)
|
60
|
+
|
61
|
+
# Build options
|
62
|
+
ffmpeg_custom_options = {
|
63
|
+
audio_codec: @video_ac,
|
64
|
+
video_codec: @video_vc,
|
65
|
+
custom: ffmpeg_custom_option_array,
|
66
|
+
}
|
67
|
+
set_info :work, :ffmpeg_custom_options, ffmpeg_custom_options
|
68
|
+
|
69
|
+
# Build command
|
70
|
+
movie.transcode(target.path, ffmpeg_custom_options) do |ffmpeg_progress|
|
71
|
+
set_info :work, :ffmpeg_progress, ffmpeg_progress
|
72
|
+
|
73
|
+
percent0 = (100.0 * ffmpeg_progress).round(0)
|
74
|
+
set_info :work, :progress, percent0
|
75
|
+
|
76
|
+
log_debug "progress #{ffmpeg_progress}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def ffmpeg_custom_option_array
|
81
|
+
# Ensure options ar in the correct format
|
82
|
+
return [] unless @video_custom.is_a? Hash
|
83
|
+
# video_custom_parts = @video_custom.to_s.scan(/(?:\w|"[^"]*")+/)
|
84
|
+
|
85
|
+
# Build the final array
|
86
|
+
custom_parts = []
|
87
|
+
@video_custom.each do |name, value|
|
88
|
+
custom_parts << "-#{name}"
|
89
|
+
custom_parts << value.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
# Return this
|
93
|
+
return custom_parts
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# require "stringio"
|
101
|
+
# def capture_stderr
|
102
|
+
# real_stderr, $stderr = $stderr, StringIO.new
|
103
|
+
# yield
|
104
|
+
# $stderr.string
|
105
|
+
# ensure
|
106
|
+
# $stderr = real_stderr
|
107
|
+
# end
|