rest-ftp-daemon 0.85.2 → 0.90.1
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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +16 -0
- data/bin/rest-ftp-daemon +52 -35
- data/lib/rest-ftp-daemon/api/jobs.rb +20 -13
- data/lib/rest-ftp-daemon/api/root.rb +12 -21
- data/lib/rest-ftp-daemon/constants.rb +3 -3
- data/lib/rest-ftp-daemon/exceptions.rb +2 -0
- data/lib/rest-ftp-daemon/helpers.rb +26 -2
- data/lib/rest-ftp-daemon/job.rb +128 -64
- data/lib/rest-ftp-daemon/job_queue.rb +59 -19
- data/lib/rest-ftp-daemon/notification.rb +4 -7
- data/lib/rest-ftp-daemon/static/css/bootstrap.css +2 -2
- data/lib/rest-ftp-daemon/static/css/main.css +17 -0
- data/lib/rest-ftp-daemon/views/dashboard.haml +43 -4
- data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +55 -74
- data/lib/rest-ftp-daemon/views/dashboard_workers.haml +1 -1
- data/lib/rest-ftp-daemon/worker_pool.rb +1 -1
- data/rest-ftp-daemon.yml.sample +4 -3
- metadata +3 -4
- data/test/helper.rb +0 -34
- data/test/test_rest-ftp-daemon.rb +0 -7
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
module RestFtpDaemon
|
4
5
|
class JobQueue < Queue
|
6
|
+
attr_reader :queued
|
7
|
+
attr_reader :popped
|
5
8
|
|
6
9
|
def initialize
|
7
10
|
# # Logger
|
@@ -17,6 +20,12 @@ module RestFtpDaemon
|
|
17
20
|
self.taint
|
18
21
|
@mutex = Mutex.new
|
19
22
|
|
23
|
+
# Identifiers generator
|
24
|
+
@last_id = 0
|
25
|
+
#@prefix = SecureRandom.hex(IDENT_JOB_LEN)
|
26
|
+
@prefix = Helpers.identifier IDENT_JOB_LEN
|
27
|
+
info "queue initialized with prefix: #{@prefix}"
|
28
|
+
|
20
29
|
# Mutex for counters
|
21
30
|
@counters = {}
|
22
31
|
@mutex_counters = Mutex.new
|
@@ -39,6 +48,13 @@ module RestFtpDaemon
|
|
39
48
|
|
40
49
|
end
|
41
50
|
|
51
|
+
def generate_id
|
52
|
+
rand(36**8).to_s(36)
|
53
|
+
@last_id ||= 0
|
54
|
+
@last_id += 1
|
55
|
+
prefixed_id @last_id
|
56
|
+
end
|
57
|
+
|
42
58
|
def counter_add name, value
|
43
59
|
@mutex_counters.synchronize do
|
44
60
|
@counters[name] ||= 0
|
@@ -60,27 +76,45 @@ module RestFtpDaemon
|
|
60
76
|
end
|
61
77
|
end
|
62
78
|
|
63
|
-
def by_status status
|
79
|
+
# def by_status status
|
80
|
+
# return [] if status.nil?
|
81
|
+
|
82
|
+
# # Select jobs from the queue if their status is (status)
|
83
|
+
# all.select { |item| item.get(:status) == status.to_sym }
|
84
|
+
# end
|
85
|
+
|
86
|
+
def popped_reverse_sorted_by_status status
|
64
87
|
return [] if status.nil?
|
65
88
|
|
66
89
|
# Select jobs from the queue if their status is (status)
|
67
|
-
|
90
|
+
ordered_popped.reverse.select { |item| item.get(:status) == status.to_sym }
|
68
91
|
end
|
69
92
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
@popped
|
93
|
+
def popped_counts_by_status
|
94
|
+
statuses = {}
|
95
|
+
@popped.group_by { |job| job.get(:status) }.map { |status, jobs| statuses[status] = jobs.size }
|
96
|
+
statuses
|
75
97
|
end
|
98
|
+
|
76
99
|
def all
|
100
|
+
# queued2 = @queued.clone
|
101
|
+
# return queued2.merge(@popped)
|
77
102
|
@queued + @popped
|
78
103
|
end
|
79
104
|
def all_size
|
80
105
|
@queued.length + @popped.length
|
81
106
|
end
|
82
107
|
|
83
|
-
def
|
108
|
+
def find_by_id id, prefixed = false
|
109
|
+
# Build a prefixed id if expected
|
110
|
+
id = prefixed_id(id) if prefixed
|
111
|
+
info "find_by_id (#{id}, #{prefixed}) > #{id}"
|
112
|
+
|
113
|
+
# Search in both queues
|
114
|
+
@queued.select { |item| item.id == id }.last || @popped.select { |item| item.id == id }.last
|
115
|
+
end
|
116
|
+
|
117
|
+
def push obj
|
84
118
|
# Check that item responds to "priorty" method
|
85
119
|
raise "JobQueue.push: object should respond to priority method" unless obj.respond_to? :priority
|
86
120
|
|
@@ -97,11 +131,7 @@ module RestFtpDaemon
|
|
97
131
|
alias << push
|
98
132
|
alias enq push
|
99
133
|
|
100
|
-
|
101
|
-
# Retrieves data from the queue. If the queue is empty, the calling thread is
|
102
|
-
# suspended until data is pushed onto the queue. If +non_block+ is true, the
|
103
|
-
# thread isn't suspended, and an exception is raised.
|
104
|
-
#
|
134
|
+
|
105
135
|
def pop(non_block=false)
|
106
136
|
@mutex.synchronize do
|
107
137
|
while true
|
@@ -110,7 +140,7 @@ module RestFtpDaemon
|
|
110
140
|
@waiting.push Thread.current
|
111
141
|
@mutex.sleep
|
112
142
|
else
|
113
|
-
return
|
143
|
+
return pick_one
|
114
144
|
end
|
115
145
|
end
|
116
146
|
end
|
@@ -130,9 +160,20 @@ module RestFtpDaemon
|
|
130
160
|
@waiting.size
|
131
161
|
end
|
132
162
|
|
163
|
+
def ordered_queue
|
164
|
+
@queued.sort_by { |item| [item.priority.to_i, - item.id.to_i] }
|
165
|
+
end
|
166
|
+
|
167
|
+
def ordered_popped
|
168
|
+
@popped.sort_by { |item| [item.get(:updated_at)] }
|
169
|
+
end
|
133
170
|
|
134
171
|
protected
|
135
172
|
|
173
|
+
def prefixed_id id
|
174
|
+
"#{@prefix}.#{id}"
|
175
|
+
end
|
176
|
+
|
136
177
|
def conchita_loop
|
137
178
|
info "conchita starting with: #{@conchita.inspect}"
|
138
179
|
loop do
|
@@ -175,21 +216,20 @@ module RestFtpDaemon
|
|
175
216
|
@logger.add(Logger::INFO, "#{' '*(level+1)} #{message}", progname) unless @logger.nil?
|
176
217
|
end
|
177
218
|
|
178
|
-
def
|
219
|
+
def pick_one # called inside a mutex/sync
|
179
220
|
# Sort jobs by priority and get the biggest one
|
180
|
-
picked =
|
221
|
+
picked = ordered_queue.last
|
181
222
|
return nil if picked.nil?
|
182
223
|
|
183
|
-
#
|
224
|
+
# Move it away from the queue to the @popped array
|
184
225
|
@queued.delete_if { |item| item == picked }
|
185
|
-
|
186
|
-
# Stack it to popped items
|
187
226
|
@popped.push picked
|
188
227
|
|
189
228
|
# Return picked
|
190
229
|
picked
|
191
230
|
end
|
192
231
|
|
232
|
+
|
193
233
|
private
|
194
234
|
|
195
235
|
def info message, level = 0
|
@@ -32,12 +32,13 @@ module RestFtpDaemon
|
|
32
32
|
|
33
33
|
# Params
|
34
34
|
body = {
|
35
|
-
id: params[:id],
|
35
|
+
id: params[:id].to_s,
|
36
36
|
signal: params[:signal],
|
37
37
|
error: params[:error],
|
38
|
-
host:
|
38
|
+
host: Settings['host'].to_s,
|
39
39
|
}
|
40
|
-
body[:status] = params[:status]
|
40
|
+
body[:status] = params[:status] if (params[:status].is_a? Enumerable)
|
41
|
+
# && (!params[:status].empty?)
|
41
42
|
|
42
43
|
# Send message in a thread
|
43
44
|
Thread.new do |thread|
|
@@ -59,9 +60,5 @@ module RestFtpDaemon
|
|
59
60
|
|
60
61
|
protected
|
61
62
|
|
62
|
-
def get_hostname
|
63
|
-
`hostname`.chomp
|
64
|
-
end
|
65
|
-
|
66
63
|
end
|
67
64
|
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
%head
|
4
4
|
%meta{:charset => "utf-8"}/
|
5
5
|
%link{ href:"/css/bootstrap.css" , rel: "stylesheet"}
|
6
|
+
%link{ href:"/css/main.css" , rel: "stylesheet"}
|
6
7
|
|
7
|
-
%title="#{Settings.
|
8
|
+
%title="#{Settings['host']} [#{Settings.namespace}] #{APP_NAME}"
|
8
9
|
|
9
10
|
:css
|
10
11
|
|
@@ -47,6 +48,10 @@
|
|
47
48
|
.btn.btn-default.btn-success IP
|
48
49
|
.btn.btn-default= @info_ipaddr
|
49
50
|
|
51
|
+
.btn-group.btn-group-sm
|
52
|
+
.btn.btn-default.btn-success Host
|
53
|
+
.btn.btn-default= Settings['host']
|
54
|
+
|
50
55
|
.btn-group.btn-group-sm
|
51
56
|
.btn.btn-default.btn-warning Load
|
52
57
|
.btn.btn-default= @info_load.round(1)
|
@@ -65,9 +70,43 @@
|
|
65
70
|
|
66
71
|
|
67
72
|
.row
|
68
|
-
.col-md-
|
69
|
-
|
70
|
-
|
73
|
+
.col-md-10
|
74
|
+
|
75
|
+
%h2
|
76
|
+
Jobs
|
77
|
+
|
78
|
+
.btn-group.btn-group-md
|
79
|
+
- klass = @only.nil? ? "btn-info" : ""
|
80
|
+
%a.btn.btn-default{href: "?only=", class: klass}
|
81
|
+
ALL (#{@jobs_queued.size} + #{@jobs_popped.size})
|
82
|
+
.btn-group.btn-group-md
|
83
|
+
- @counts.each do |status, count|
|
84
|
+
- klass = (status == @only) ? "btn-info" : ""
|
85
|
+
%a.btn.btn-default{href: "?only=#{status}", class: klass}
|
86
|
+
#{status} (#{count})
|
87
|
+
|
88
|
+
%table.table.table-striped.table-hover.table-condensed
|
89
|
+
%tr
|
90
|
+
%th JID
|
91
|
+
%th prio
|
92
|
+
%th worker
|
93
|
+
%th source
|
94
|
+
%th target
|
95
|
+
%th status
|
96
|
+
%th error
|
97
|
+
%th progress
|
98
|
+
%th.text-right size
|
99
|
+
%th.text-right bitrate
|
100
|
+
|
101
|
+
- if @only.nil? && !@jobs_queued.empty?
|
102
|
+
= render :dashboard_jobs, {jobs: @jobs_queued, counts: @counts}
|
103
|
+
%tr
|
104
|
+
%th{colspan: 10}
|
105
|
+
|
106
|
+
= render :dashboard_jobs, {jobs: @jobs_popped, counts: @counts}
|
107
|
+
|
108
|
+
|
109
|
+
.col-md-2
|
71
110
|
= render :dashboard_workers, {}
|
72
111
|
|
73
112
|
= render :dashboard_tokens, {tokens: Settings.endpoints || {}}
|
@@ -1,75 +1,56 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
=
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
%
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
%
|
23
|
-
|
24
|
-
%
|
25
|
-
|
26
|
-
%
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
%
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
=
|
56
|
-
%td
|
57
|
-
.label.label-info= job.get :source_method
|
58
|
-
.label.label-primary= job.get :target_method
|
59
|
-
%td= Helpers.format_bytes(size, "B")
|
60
|
-
%td= status
|
61
|
-
%td= error
|
62
|
-
%td
|
63
|
-
- unless progress.nil?
|
64
|
-
.progress
|
65
|
-
.progress-bar{style:"width: #{progress}%;"}
|
66
|
-
= Helpers.format_bytes job.get(:transfer_sent), "B"
|
67
|
-
%td
|
68
|
-
- unless progress.nil?
|
69
|
-
= "#{progress}%"
|
70
|
-
- if (bitrate = job.get :transfer_bitrate)
|
71
|
-
= "|"
|
72
|
-
= Helpers.format_bytes(bitrate, "bps")
|
73
|
-
|
74
|
-
%td= job.wid
|
1
|
+
- jobs.each do |job|
|
2
|
+
- error = job.get :error
|
3
|
+
- status = job.get :status
|
4
|
+
- size = job.get :transfer_total
|
5
|
+
- progress = job.get :progress
|
6
|
+
- transfer_source_count = job.get(:transfer_source_count) || 0
|
7
|
+
- transfer_source_done = job.get(:transfer_source_done) || 0
|
8
|
+
|
9
|
+
- if error!=0 && !error.nil?
|
10
|
+
- trclass = "danger"
|
11
|
+
- elsif status == :uploading
|
12
|
+
- trclass = "info"
|
13
|
+
- elsif status == :finished
|
14
|
+
- trclass = "success"
|
15
|
+
- else
|
16
|
+
- trclass = "warning"
|
17
|
+
|
18
|
+
%tr{class: trclass}
|
19
|
+
|
20
|
+
%td= job.id
|
21
|
+
|
22
|
+
%td= job.get :priority
|
23
|
+
|
24
|
+
%td= job.wid
|
25
|
+
|
26
|
+
%td.fixed{title: job.get(:source_path)}
|
27
|
+
= Helpers.job_method_label job.get(:source_method)
|
28
|
+
= job.get :source
|
29
|
+
|
30
|
+
%td.fixed{title: job.get(:target_url)}
|
31
|
+
= Helpers.job_method_label job.get(:target_method)
|
32
|
+
= job.get :target
|
33
|
+
|
34
|
+
%td
|
35
|
+
= status
|
36
|
+
- if (transfer_source_count > 0) #&& (transfer_source_done < transfer_source_count)
|
37
|
+
%small= " #{transfer_source_done}/#{transfer_source_count}"
|
38
|
+
|
39
|
+
%td
|
40
|
+
= error
|
41
|
+
- unless progress.nil? || status == :finished
|
42
|
+
%small= "#{progress}%"
|
43
|
+
|
44
|
+
%td
|
45
|
+
- unless progress.nil?
|
46
|
+
.progress
|
47
|
+
.progress-bar{style:"width: #{progress}%;"}
|
48
|
+
= Helpers.format_bytes job.get(:transfer_sent), "B"
|
49
|
+
|
50
|
+
%td.nobr.text-right
|
51
|
+
= Helpers.format_bytes(size, "B")
|
52
|
+
|
53
|
+
%td.nobr.text-right
|
54
|
+
- if (bitrate = job.get :transfer_bitrate)
|
55
|
+
= Helpers.format_bytes(bitrate, "bps")
|
75
56
|
|
data/rest-ftp-daemon.yml.sample
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
defaults: &defaults
|
2
|
-
daemonize:
|
3
|
-
port:
|
2
|
+
daemonize: true
|
3
|
+
port: 3000
|
4
4
|
workers: 2
|
5
5
|
#adminpwd: "admin"
|
6
|
-
pidfile: /tmp/rftpd.pid
|
7
6
|
user: rftpd
|
8
7
|
group: rftpd
|
8
|
+
host: <%= `hostname`.chomp.split('.').first %>
|
9
9
|
|
10
10
|
transfer:
|
11
11
|
#update_every_kb: 500
|
@@ -26,3 +26,4 @@ preprod:
|
|
26
26
|
|
27
27
|
production:
|
28
28
|
<<: *defaults
|
29
|
+
port: 3200
|