rest-ftp-daemon 0.30.1 → 0.41

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.
@@ -0,0 +1,49 @@
1
+ module RestFtpDaemon
2
+
3
+ class Common
4
+
5
+ protected
6
+
7
+ def initialize
8
+ # Logger
9
+ @logger = ActiveSupport::Logger.new APP_LOGTO, 'daily'
10
+ end
11
+
12
+ def id
13
+ end
14
+
15
+ def progname
16
+ end
17
+
18
+ def info message, level = 0
19
+ # progname = "Job [#{id}]" unless id.nil?
20
+ # progname = "Worker [#{id}]" unless worker_id.nil?
21
+ @logger.add(Logger::INFO, "#{' '*(level+1)} #{message}", progname)
22
+ end
23
+
24
+ def notify signal, error = 0, status = {}
25
+ # Skip is not callback URL defined
26
+ url = get :notify
27
+ if url.nil?
28
+ info "Skipping notification (no valid url provided) sig[#{signal}] e[#{error}] s#{status.inspect}"
29
+ return
30
+ end
31
+
32
+ # Build notification
33
+ n = RestFtpDaemon::Notification.new
34
+ n.job_id = id
35
+ n.url = url
36
+ n.signal = signal
37
+ n.error = error.inspect
38
+ n.status = status
39
+
40
+ # Now, send the notification
41
+ info "Queuing notification key[#{n.key}] sig[#{signal}] url[#{url}]"
42
+ Thread.new(n) do |thread|
43
+ n.notify
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -1,8 +1,15 @@
1
1
  module RestFtpDaemon
2
2
  # Global config
3
- VERSION = "0.30.1"
3
+ NAME = 'rest-ftp-daemon'
4
+ VERSION = "0.41"
5
+ PORT = 3000
4
6
 
5
7
  # Transfer config
6
8
  TRANSFER_CHUNK_SIZE = 100000
7
- THREAD_SLEEP_BEFORE_DIE = 30
9
+ THREAD_SLEEP_BEFORE_DIE = 10
10
+
11
+ # Logging
12
+ APP_LOGTO = "/tmp/#{NAME}.log"
13
+ LOG_TRIM_PROGNAME = 18
14
+
8
15
  end
@@ -16,6 +16,11 @@ module RestFtpDaemon
16
16
  class JobSourceNotFound < RestFtpDaemonException; end
17
17
  class JobTargetMissing < RestFtpDaemonException; end
18
18
  class JobTargetUnparseable < RestFtpDaemonException; end
19
- class JobTargetPermission < RestFtpDaemonException; end
19
+ #class JobTargetPermission < RestFtpDaemonException; end
20
+ class JobTargetFileExists < RestFtpDaemonException; end
21
+
22
+ class NotificationMissingUrl < RestFtpDaemonException; end
23
+ class NotificationMissingSignal < RestFtpDaemonException; end
24
+
20
25
 
21
26
  end
@@ -1,25 +1,50 @@
1
1
  module RestFtpDaemon
2
- class Job
2
+ class Job < RestFtpDaemon::Common
3
3
 
4
4
  def initialize(id, params={})
5
+ # Call super
6
+ super()
7
+
5
8
  # Grab params
6
9
  @params = params
7
10
  @target = nil
8
11
  @source = nil
9
12
 
13
+ # Logger
14
+ #@logger = ActiveSupport::Logger.new APP_LOGTO, 'daily'
15
+
10
16
  # Init context
11
17
  set :id, id
12
18
  set :started_at, Time.now
13
19
  set :status, :initialized
20
+
21
+ # Send first notification
22
+ notify "rftpd.queued"
23
+ end
24
+
25
+ # def job_id
26
+ # get :id
27
+ # end
28
+
29
+ def progname
30
+ job_id = get(:id)
31
+ "JOB #{job_id}"
14
32
  end
15
33
 
16
34
  def id
17
- @params[:id]
35
+ get :id
36
+ end
37
+
38
+ def priority
39
+ get :priority
40
+ end
41
+ def get_status
42
+ get :status
18
43
  end
19
44
 
20
45
  def process
21
46
  # Init
22
- "process [#{@id}] starting"
47
+ info "Job.process starting"
23
48
  set :status, :starting
24
49
  set :error, 0
25
50
 
@@ -30,11 +55,13 @@ module RestFtpDaemon
30
55
  # Process
31
56
  transfer
32
57
 
33
- rescue Net::FTPPermError
58
+ rescue Net::FTPPermError => exception
59
+ info "Job.process failed [Net::FTPPermError]"
34
60
  set :status, :failed
35
61
  set :error, exception.class
36
62
 
37
63
  rescue RestFtpDaemonException => exception
64
+ info "Job.process failed [RestFtpDaemonException::#{exception.class}]"
38
65
  set :status, :failed
39
66
  set :error, exception.class
40
67
 
@@ -43,8 +70,14 @@ module RestFtpDaemon
43
70
  # set :error, exception.class
44
71
 
45
72
  else
73
+ info "Job.process finished"
74
+ # set :error, 0
75
+ #set :status, :wandering
76
+
77
+ # Wait for a few seconds before marking the job as finished
78
+ # info "#{prefix} wander for #{RestFtpDaemon::THREAD_SLEEP_BEFORE_DIE} sec"
79
+ # wander RestFtpDaemon::THREAD_SLEEP_BEFORE_DIE
46
80
  set :status, :finished
47
- set :error, 0
48
81
  end
49
82
 
50
83
  end
@@ -62,12 +95,6 @@ module RestFtpDaemon
62
95
  @params
63
96
  end
64
97
 
65
- def wander time
66
- @wander_for = time
67
- @wander_started = Time.now
68
- sleep time
69
- end
70
-
71
98
  def status text
72
99
  @status = text
73
100
  end
@@ -79,20 +106,33 @@ module RestFtpDaemon
79
106
  Time.now - @params[:started_at]
80
107
  end
81
108
 
109
+ def wander time
110
+ info "Job.wander #{time}"
111
+ @wander_for = time
112
+ @wander_started = Time.now
113
+ sleep time
114
+ info "Job.wandered ok"
115
+ end
116
+
82
117
  def wandering_time
83
118
  return if @wander_started.nil? || @wander_for.nil?
84
119
  @wander_for.to_f - (Time.now - @wander_started)
85
120
  end
86
121
 
87
- def exception_handler(actor, reason)
88
- set :status, :crashed
89
- set :error, reason
90
- end
122
+ # def exception_handler(actor, reason)
123
+ # set :status, :crashed
124
+ # set :error, reason
125
+ # end
91
126
 
92
127
  def set attribute, value
93
128
  return unless @params.is_a? Enumerable
94
129
  @params[:updated_at] = Time.now
95
- @params[attribute] = value
130
+ @params[attribute.to_s] = value
131
+ end
132
+
133
+ def get attribute
134
+ return nil unless @params.is_a? Enumerable
135
+ @params[attribute.to_s]
96
136
  end
97
137
 
98
138
  def prepare
@@ -124,8 +164,9 @@ module RestFtpDaemon
124
164
  end
125
165
 
126
166
  def transfer
127
- # Init
167
+ # Send first notification
128
168
  transferred = 0
169
+ notify "rftpd.started"
129
170
 
130
171
  # Ensure @source and @target are there
131
172
  set :status, :checking_source
@@ -142,15 +183,19 @@ module RestFtpDaemon
142
183
  set :status, :checking_target
143
184
  ftp = Net::FTP.new(@target.host)
144
185
  ftp.passive = true
145
- ftp.login
186
+ ftp.login @target.user, @target.password
146
187
  ftp.chdir(target_path)
147
188
 
148
189
  # Check for target file presence
149
- results = ftp.list(target_name)
150
- #info "ftp.list: #{results}"
151
- unless results.count.zero?
152
- ftp.close
153
- raise JobTargetPermission
190
+ if get(:overwrite).nil?
191
+ results = ftp.list(target_name)
192
+ #results = ftp.list()
193
+
194
+ unless results.count.zero?
195
+ ftp.close
196
+ notify "rftpd.ended", RestFtpDaemon::JobTargetFileExists
197
+ raise RestFtpDaemon::JobTargetFileExists
198
+ end
154
199
  end
155
200
 
156
201
  # Do transfer
@@ -166,6 +211,7 @@ module RestFtpDaemon
166
211
  end
167
212
 
168
213
  # Close FTP connexion
214
+ notify "rftpd.ended"
169
215
  ftp.close
170
216
  end
171
217
 
@@ -0,0 +1,105 @@
1
+ require 'thread'
2
+
3
+ module RestFtpDaemon
4
+ class JobQueue < Queue
5
+
6
+ def initialize
7
+ @queued = []
8
+ @popped = []
9
+
10
+ @waiting = []
11
+ @queued.taint # enable tainted communication
12
+ @waiting.taint
13
+ self.taint
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def queued
18
+ @queued
19
+ end
20
+ def queued_size
21
+ @queued.length
22
+ end
23
+
24
+ def popped
25
+ @popped
26
+ end
27
+ def popped_size
28
+ @popped.length
29
+ end
30
+
31
+ def all
32
+ @queued + @popped
33
+ end
34
+ def all_size
35
+ popped_size + queued_size
36
+ end
37
+
38
+ def push(obj)
39
+ # Check that itme responds to "priorty" method
40
+ raise "object should respond to priority method" unless obj.respond_to? :priority
41
+
42
+ @mutex.synchronize{
43
+ @queued.push obj
44
+ begin
45
+ t = @waiting.shift
46
+ t.wakeup if t
47
+ rescue ThreadError
48
+ retry
49
+ end
50
+ }
51
+ end
52
+ alias << push
53
+ alias enq push
54
+
55
+ #
56
+ # Retrieves data from the queue. If the queue is empty, the calling thread is
57
+ # suspended until data is pushed onto the queue. If +non_block+ is true, the
58
+ # thread isn't suspended, and an exception is raised.
59
+ #
60
+ def pop(non_block=false)
61
+ @mutex.synchronize{
62
+ while true
63
+ if @queued.empty?
64
+ raise ThreadError, "queue empty" if non_block
65
+ @waiting.push Thread.current
66
+ @mutex.sleep
67
+ else
68
+ return pick
69
+ end
70
+ end
71
+ }
72
+ end
73
+ alias shift pop
74
+ alias deq pop
75
+
76
+ def empty?
77
+ @queued.empty?
78
+ end
79
+
80
+ def clear
81
+ @queued.clear
82
+ end
83
+
84
+ def num_waiting
85
+ @waiting.size
86
+ end
87
+
88
+ protected
89
+
90
+ def pick
91
+ # Sort jobs by priority and get the biggest one
92
+ picked = @queued.sort { |a,b| a.priority.to_i <=> b.priority.to_i }.last
93
+
94
+ # Delete it from the queue
95
+ @queued.delete_if { |item| item == picked } unless picked.nil?
96
+
97
+ # Stack it to popped items
98
+ @popped.push picked
99
+
100
+ # Return picked
101
+ picked
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,7 @@
1
+ class Logger
2
+ def format_message(severity, timestamp, progname, msg)
3
+ stamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
4
+ progname = "%-#{RestFtpDaemon::LOG_TRIM_PROGNAME}s" % progname
5
+ "#{stamp} #{severity} #{progname} #{msg}\n"
6
+ end
7
+ end
@@ -0,0 +1,80 @@
1
+ module RestFtpDaemon
2
+ class Notification < RestFtpDaemon::Common
3
+ attr_accessor :job_id
4
+ attr_accessor :signal
5
+ attr_accessor :error
6
+ attr_accessor :message
7
+ attr_accessor :status
8
+ attr_accessor :url
9
+ attr_accessor :job
10
+ attr_accessor :key
11
+
12
+ def initialize
13
+ #def initialize(job, signal, error, status)
14
+ # Grab params
15
+ #@job = job
16
+ # @signal = signal
17
+ # @error = error
18
+ # @status = status
19
+ @status = {}
20
+ @error = 0
21
+ @message = nil
22
+
23
+ # Generate a random key
24
+ @key = SecureRandom.hex(2)
25
+
26
+ # Call super
27
+ super
28
+
29
+ end
30
+
31
+ def progname
32
+ "NOTIF #{@key}"
33
+ end
34
+
35
+
36
+ # def status key, val
37
+ # @status[key.to_s] = val
38
+ # end
39
+
40
+ def notify
41
+ # Check context
42
+ raise NotificationMissingUrl unless @url
43
+ raise NotificationMissingSignal unless @signal
44
+ #sraise NotifImpossible unless @status
45
+
46
+ # Params
47
+ params = {
48
+ id: @job_id,
49
+ host: get_hostname,
50
+ signal: @signal,
51
+ error: @error,
52
+ }
53
+
54
+ # Add status only if present
55
+ params["status"] = @status unless @status.empty?
56
+ params["message"] = @message unless @message.to_s.blank?
57
+
58
+ # Log this notification
59
+ info "notify params: #{params.inspect}"
60
+
61
+ # Prepare query
62
+ uri = URI(@url)
63
+ headers = {"Content-Type" => "application/json",
64
+ "Accept" => "application/json"}
65
+
66
+ # Post the notification
67
+ http = Net::HTTP.new(uri.host, uri.port)
68
+ response = http.post(uri.path, params.to_json, headers)
69
+
70
+ info "notify reply: #{response.body.strip}"
71
+ end
72
+
73
+ protected
74
+
75
+ def get_hostname
76
+ `hostname`.chomp
77
+ end
78
+
79
+ end
80
+ end