rest-ftp-daemon 0.6.0 → 0.9.0

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.
data/Gemfile.lock CHANGED
@@ -13,7 +13,7 @@ GEM
13
13
  thread_safe (~> 0.3, >= 0.3.1)
14
14
  faraday (0.9.0)
15
15
  multipart-post (>= 1.2, < 3)
16
- git (1.2.7)
16
+ git (1.2.8)
17
17
  github_api (0.12.0)
18
18
  addressable (~> 2.3)
19
19
  descendants_tracker (~> 0.0.4)
@@ -53,7 +53,7 @@ GEM
53
53
  rack-protection (1.5.3)
54
54
  rack
55
55
  rake (10.3.2)
56
- rdoc (3.12.2)
56
+ rdoc (4.1.1)
57
57
  json (~> 1.4)
58
58
  shoulda (3.5.0)
59
59
  shoulda-context (~> 1.0, >= 1.0.1)
data/README.md CHANGED
@@ -1,90 +1,120 @@
1
- ## rest-ftp-daemon ##
1
+ rest-ftp-daemon
2
+ ====================================================================================
3
+
4
+ This is a pretty simple FTP client daemon, controlled through a RESTfull API.
2
5
 
3
- This is a fairly basic FTP client daemon, driven by REST-based webservice calls.
4
6
  As of today, its main features are :
5
7
 
6
- * Start a job through a basic structure posted with PUT /jobs
7
- * Spawn a dedicated thread to handle this job in its own contexte
8
- * Report transfer progress, error and activity for each job
9
- * Gather jobs status into the main process to get a global view
10
- * Report JSON status of workers on GET /jobs/ for automated monitoring
8
+ * Delegate a transfer job, ```PUT```'ing posting simple JSON structure
9
+ * Spawn a dedicated thread to handle this job in its own context
10
+ * Report transfer status, progress and errors for each delegated job
11
+ * Expose JSON status of workers on ```GET /jobs/``` for automated monitoring
11
12
 
12
13
 
13
- ## Quick setup ##
14
+ Installation
15
+ ------------------------------------------------------------------------------------
14
16
 
15
- This project requires ruby >= 1.9 and rubygems installed.
17
+ This project is available as a rubygem, requires on ruby >= 1.9 and rubygems installed.
16
18
 
17
- Quickly install the gem from rubygems.org:
19
+ Get and install the gem from rubygems.org:
18
20
 
19
- ``` gem install rest-ftp-daemon ```
21
+ ```
22
+ gem install rest-ftp-daemon
23
+ ```
20
24
 
21
25
  Start the daemon:
22
26
 
23
- ``` rest-ftp-daemon start ```
27
+ ```
28
+ rest-ftp-daemon start
29
+ ```
30
+
31
+ Check that the daemon is running and giving status info
32
+
33
+ ```
34
+ http://localhost:3000/
35
+ ```
24
36
 
25
- For now, daemon logs to APP_LOGTO defined in lib/config.rb
37
+ For now, daemon logs to ```APP_LOGTO``` defined in ```lib/config.rb```
26
38
 
27
39
 
28
- ## Basic usage ##
40
+ Usage examples
41
+ ------------------------------------------------------------------------------------
29
42
 
30
- Starting a job transferring file named "file.ova"
43
+ Start a job to transfer a file named "file.ova" to a local FTP server
31
44
 
32
- curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
33
- '{"source":"~/file.ova","target":"ftp://anonymous@localhost/incoming/dest2.ova"}' "http://localhost:3000/jobs"
45
+ ```
46
+ curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
47
+ '{"source":"~/file.ova","target":"ftp://anonymous@localhost/incoming/dest2.ova"}' "http://localhost:3000/jobs"
48
+ ```
34
49
 
50
+ Start a job to transfer a file named "file.dmg" to a local FTP server
35
51
 
36
- Starting a job transferring file named "dmg"
52
+ ```
53
+ curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
54
+ '{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg"}' "http://localhost:3000/jobs"
55
+ ```
37
56
 
38
- curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
39
- '{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg"}' "http://localhost:3000/jobs"
57
+ Get status of a specific job based on its name
40
58
 
59
+ ```
60
+ curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
61
+ ```
41
62
 
42
- Delete a specific job
63
+ Delete a specific job based on its name
43
64
 
44
- curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
65
+ ```
66
+ curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
67
+ ```
45
68
 
46
69
 
70
+ Getting status
71
+ ------------------------------------------------------------------------------------
47
72
 
48
- ## Getting status ##
73
+ The server exposes jobs list on ``` GET /jobs ```
49
74
 
50
- GET /jobs
75
+ ```
76
+ http://localhost:3000/jobs
77
+ ```
51
78
 
52
- Would return:
79
+ This query will return a job list :
53
80
 
54
- [
55
- {
56
- "id": "bob-49126-8",
57
- "process": "sleep",
58
- "status": "transferring",
59
- "context": {
60
- "source": "~\/file.ova",
61
- "target": "ftp:\/\/anonymous@localhost\/incoming\/dest2.ova",
62
- "code": -1,
63
- "errmsg": "running",
64
- "source_size": 1849036800,
65
- "progress": 1.9,
66
- "transferred": 34800000
67
- }
68
- },
69
- {
70
- "id": "bob-49126-9",
71
- "process": "sleep",
72
- "status": "transferring",
73
- "context": {
74
- "source": "~\/file.dmg",
75
- "target": "ftp:\/\/anonymous@localhost\/incoming\/dest4.dmg",
76
- "code": -1,
77
- "errmsg": "running",
78
- "source_size": 37109074,
79
- "progress": 32.9,
80
- "transferred": 12200000
81
- }
82
- }
83
- ]
81
+ ```
82
+ [
83
+ {
84
+ "source": "~/file.dmg",
85
+ "target": "ftp://anonymous@localhost/incoming/dest2.dmg",
86
+ "worker_name": "bob-92439-1",
87
+ "created": "2014-08-01 16:53:08 +0200",
88
+ "id": 16,
89
+ "runtime": 17.4,
90
+ "status": "graceful_ending",
91
+ "source_size": 37109074,
92
+ "error": 0,
93
+ "errmsg": "finished",
94
+ "progress": 100.0,
95
+ "transferred": 37100000
96
+ },
97
+ {
98
+ "source": "~/file.ova",
99
+ "target": "ftp://anonymous@localhost/incoming/dest2.ova",
100
+ "worker_name": "bob-92439-2",
101
+ "created": "2014-08-01 16:53:12 +0200",
102
+ "id": 17,
103
+ "runtime": 13.8,
104
+ "status": "uploading",
105
+ "source_size": 1849036800,
106
+ "error": -1,
107
+ "errmsg": "uploading",
108
+ "progress": 36.1,
109
+ "transferred": 668300000
110
+ }
111
+ ]
112
+ ```
84
113
 
85
114
 
86
- ### About ###
115
+ About
116
+ ------------------------------------------------------------------------------------
87
117
 
88
- Bruno MEDICI
118
+ Bruno MEDICI Consultant
89
119
 
90
120
  http://bmconseil.com/
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ Jeweler::Tasks.new do |gem|
18
18
  gem.homepage = "http://github.com/bmedici/rest-ftp-daemon"
19
19
  gem.license = "MIT"
20
20
  gem.summary = "RESTful FTP client daemon"
21
- gem.description = "A fairly basic FTP client daemon, driven by RESTful webservice calls"
21
+ gem.description = "This is a pretty simple FTP client daemon, controlled through a RESTfull API"
22
22
  gem.email = "rest-ftp-daemon@bmconseil.com"
23
23
  gem.authors = ["Bruno"]
24
24
  # dependencies defined in Gemfile
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.0
1
+ 0.9.0
data/bin/rest-ftp-daemon CHANGED
@@ -5,9 +5,11 @@ require 'thin'
5
5
 
6
6
  # Initialize some local constants
7
7
  APP_ROOT = File.dirname(__FILE__) + '/../'
8
+ APP_NAME = 'rest-ftp-daemon'
8
9
  APP_VER = File.read "#{APP_ROOT}/VERSION"
9
10
  APP_STARTED = Time.now
10
11
  APP_DEFAULT_PORT = 3000
12
+ APP_LOGTO = "/tmp/#{APP_NAME}.log"
11
13
 
12
14
  # Prepare thin
13
15
  rackup_file = File.expand_path "#{APP_ROOT}/lib/config.ru"
@@ -15,7 +17,6 @@ argv = ARGV
15
17
  argv << ["-R", rackup_file] unless ARGV.include?("-R")
16
18
  argv << ["-p", APP_DEFAULT_PORT.to_s] unless ARGV.include?("-p")
17
19
  argv << ["-e", "production"] unless ARGV.include?("-e")
18
- argv << ["-d"] unless ARGV.include?("-d")
20
+ argv << ["--stats", "/stats"] unless ARGV.include?("--stats")
19
21
 
20
- # Start thin
21
22
  Thin::Runner.new(argv.flatten).run!
data/lib/config.rb CHANGED
@@ -1,7 +1,3 @@
1
- # App configuration
2
- APP_NAME = 'rest-ftp-daemon'
3
- APP_LOGTO = "/tmp/#{APP_NAME}.log"
4
-
5
1
  # Transfer configuration
6
2
  TRANSFER_CHUNK_SIZE = 100000
7
3
  THREAD_SLEEP_BEFORE_DIE = 120
data/lib/errors.rb CHANGED
@@ -1,8 +1,3 @@
1
- # Error codes
2
- # O: all ok
3
- # 1x: request body errors
4
- # 2x: transfer errors
5
-
6
1
  ERR_OK = 0
7
2
  ERR_BUSY = -1
8
3
 
@@ -13,4 +8,5 @@ ERR_REQ_TARGET_SCHEME = 13
13
8
  ERR_JOB_SOURCE_NOTFOUND = 21
14
9
  ERR_JOB_TARGET_UNPARSEABLE = 22
15
10
  ERR_JOB_PERMISSION = 24
11
+ ERR_JOB_TARGET_PRESENT = 25
16
12
 
@@ -0,0 +1,14 @@
1
+ class Thread
2
+
3
+ def job
4
+ # Basic fields
5
+ job = self[:job]
6
+
7
+ # Calculated fields
8
+ job[:id] = self[:id]
9
+ job[:runtime] = (Time.now - job[:created]).round(1)
10
+
11
+ return job
12
+ end
13
+
14
+ end
@@ -4,18 +4,22 @@ class RestFtpDaemon < Sinatra::Base
4
4
  configure :development, :production do
5
5
 
6
6
  # Create new thread group
7
- @@threads = ThreadGroup.new
7
+ @@workers = ThreadGroup.new
8
+
9
+ # Logging configuration
10
+ #use Rack::CommonLogger, logger
8
11
 
9
12
  # Some other configuration
10
- set :sessions, false
11
- # set :logging, true
13
+ disable :sessions
14
+ disable :logging
12
15
  end
13
16
 
14
17
  # Server initialization
15
18
  def initialize
16
19
  # Setup logger
17
20
  @logger = Logger.new(APP_LOGTO, 'daily')
18
- @logger.level = Logger::INFO
21
+ #@logger = Logger.new
22
+ #@logger.level = Logger::INFO
19
23
 
20
24
  # Other stuff
21
25
  @@last_worker_id = 0
@@ -42,23 +46,34 @@ class RestFtpDaemon < Sinatra::Base
42
46
  # Build response
43
47
  content_type :json
44
48
  JSON.pretty_generate get_jobs
45
- #@@threads.count
46
49
  end
47
50
 
48
- # List jobs
49
- delete "/jobs/:name" do
51
+ # Get job info
52
+ get "/jobs/:id" do
50
53
  # Debug query
51
- info "DELETE /jobs/#{params[:name]}"
54
+ info "GET /jobs/#{params[:id]}"
52
55
 
53
- # Kill this job
54
- ret = delete_job params[:name]
56
+ # Find this process by name
57
+ found = find_job params[:id]
55
58
 
56
- # Fail if no process has been killed
57
- error 404 if ret<1
59
+ # Build response
60
+ error 404 and return if found.nil?
61
+ content_type :json
62
+ JSON.pretty_generate found
63
+ end
64
+
65
+ # Delete jobs
66
+ delete "/jobs/:id" do
67
+ # Debug query
68
+ info "DELETE /jobs/#{params[:name]}"
69
+
70
+ # Find and kill this job
71
+ found = delete_job params[:id]
58
72
 
59
73
  # Build response
74
+ error 404 and return if found.nil?
60
75
  content_type :json
61
- JSON.pretty_generate nil
76
+ JSON.pretty_generate found
62
77
  end
63
78
 
64
79
  # Spawn a new thread for this new job
@@ -83,23 +98,25 @@ class RestFtpDaemon < Sinatra::Base
83
98
  def process_job
84
99
  # Init
85
100
  info "process_job: starting"
86
- context = Thread.current[:context]
101
+ job = Thread.current.job
102
+ job_status :started
87
103
  transferred = 0
88
104
 
89
105
  # Check source
90
- job_source = File.expand_path(context["source"])
106
+ job_source = File.expand_path(job["source"])
91
107
  if !(File.exists? job_source)
92
- job_status ERR_JOB_SOURCE_NOTFOUND, :ERR_JOB_SOURCE_NOTFOUND
108
+ job_error ERR_JOB_SOURCE_NOTFOUND, :ERR_JOB_SOURCE_NOTFOUND
93
109
  return
94
110
  end
95
111
  info "process_job: job_source: #{job_source}"
96
112
  source_size = File.size job_source
113
+ job_set :source_size, source_size
97
114
 
98
115
  # Check target
99
- job_target = context["target"]
116
+ job_target = job["target"]
100
117
  target = URI(job_target) rescue nil
101
118
  if job_target.nil? || target.nil?
102
- job_status ERR_JOB_TARGET_UNPARSEABLE, :ERR_JOB_TARGET_UNPARSEABLE
119
+ job_error ERR_JOB_TARGET_UNPARSEABLE, :ERR_JOB_TARGET_UNPARSEABLE
103
120
  return
104
121
  end
105
122
  info "process_job: job_target: #{job_target}"
@@ -117,77 +134,102 @@ class RestFtpDaemon < Sinatra::Base
117
134
  ftp.login
118
135
  ftp.chdir(target_path)
119
136
 
137
+
138
+ # Check if target file is found
139
+ info "source: checking target file"
140
+ job_status :checking_target
141
+ job_error ERR_BUSY, :checking_target
142
+
143
+ results = ftp.list(target_name)
144
+ info "ftp.list: #{results}"
145
+ unless results.count.zero?
146
+ job_error ERR_JOB_TARGET_PRESENT, :ERR_JOB_TARGET_PRESENT
147
+ info "target: existing: ERR_JOB_TARGET_PRESENT"
148
+ ftp.close
149
+ return
150
+ end
151
+
152
+
120
153
  # Do transfer
121
154
  info "source: starting stransfer"
122
- Thread.current[:status] = :transferring
123
- job_status ERR_BUSY, :running
124
- job_set :source_size, source_size
155
+ #Thread.current[:status] = :transferring
156
+ job_status :uploading
157
+ job_error ERR_BUSY, :uploading
125
158
 
126
159
  begin
127
160
  ftp.putbinaryfile(job_source, target_name, TRANSFER_CHUNK_SIZE) do |block|
128
161
  # Update thread info
129
162
  percent = (100.0 * transferred / source_size).round(1)
130
- info "transferring [#{percent} %] of [#{target_name}]"
131
163
  job_set :progress, percent
132
164
  job_set :transferred, transferred
165
+ info "transferring [#{percent} %] of [#{target_name}]"
133
166
 
134
167
  # Update counters
135
168
  transferred += TRANSFER_CHUNK_SIZE
136
169
  end
137
170
 
138
171
  rescue Net::FTPPermError
139
- Thread.current[:status] = :failed
140
- job_status ERR_JOB_PERMISSION, :ERR_JOB_PERMISSION
172
+ #job_status :failed
173
+ job_error ERR_JOB_PERMISSION, :ERR_JOB_PERMISSION
141
174
  info "source: FAILED: PERMISSIONS ERROR"
142
175
 
143
176
  else
144
- Thread.current[:status] = :finished
145
- job_status ERR_OK, :finished
177
+ #job_status :finished
178
+ job_error ERR_OK, :finished
146
179
  info "source: finished stransfer"
147
180
  end
148
181
 
182
+ # Close FTP connexion
149
183
  ftp.close
150
184
  end
151
185
 
152
186
  def get_status
153
187
  info "> get_status"
154
-
155
188
  {
156
189
  app_name: APP_NAME,
157
190
  hostname: @@hostname,
158
191
  version: APP_VER,
159
192
  started: APP_STARTED,
160
193
  uptime: (Time.now - APP_STARTED).round(1),
161
- jobs_count: @@threads.list.count,
194
+ jobs_count: @@workers.list.count,
162
195
  }
163
196
  end
164
197
 
165
198
  def get_jobs
166
199
  info "> get_jobs"
167
200
 
168
- output = []
169
- @@threads.list.each do |thread|
170
- output << {
171
- :id => thread[:name],
172
- :process => thread.status,
173
- :status => thread[:status],
174
- :context => thread[:context],
175
- }
176
- end
177
- output
201
+ # Collect info's
202
+ @@workers.list.map { |thread| thread.job }
178
203
  end
179
204
 
205
+ def delete_job id
206
+ info "> delete_job(#{id})"
180
207
 
181
- def delete_job name
182
- info "> delete_job(#{name})"
208
+ # Find jobs with this id
209
+ jobs = jobs_with_id id
183
210
 
184
- count = 0
185
- @@threads.list.collect do |thread|
186
- next unless thread[:name] == name
187
- Thread.kill(thread)
188
- count += 1
189
- end
190
- count
211
+ # Kill them
212
+ jobs.each{ |thread| Thread.kill(thread) }
213
+
214
+ # Return the first one
215
+ return nil if jobs.empty?
216
+ jobs.first.job
217
+ end
218
+
219
+ def find_job id
220
+ info "> find_job(#{id})"
221
+
222
+ # Find jobs with this id
223
+ jobs = jobs_with_id id
224
+
225
+ # Return the first one
226
+ return nil if jobs.empty?
227
+ jobs.first.job
228
+ end
229
+
230
+ def jobs_with_id id
231
+ info "> find_jobs_by_id(#{id})"
232
+ @@workers.list.select{ |thread| thread[:id].to_s == id.to_s }
191
233
  end
192
234
 
193
235
  def new_job context = {}
@@ -196,15 +238,15 @@ class RestFtpDaemon < Sinatra::Base
196
238
  # Generate name
197
239
  @@last_worker_id +=1
198
240
  host = @@hostname.split('.')[0]
199
- name = "#{host}-#{Process.pid.to_s}-#{@@last_worker_id}"
200
- info "new_job: creating thread [#{name}]"
241
+ worker_id = @@last_worker_id
242
+ worker_name = "#{host}-#{Process.pid.to_s}-#{worker_id}"
243
+ info "new_job: creating thread [#{worker_name}]"
201
244
 
202
245
  # Parse parameters
203
246
  job_source = context["source"]
204
247
  job_target = context["target"]
205
248
  return { code: ERR_REQ_SOURCE_MISSING, errmsg: :ERR_REQ_SOURCE_MISSING} if job_source.nil?
206
249
  return { code: ERR_REQ_TARGET_MISSING, errmsg: :ERR_REQ_TARGET_MISSING} if job_target.nil?
207
- #return { code: ERR_TRX_SOURCE_FILE_NOT_FOUND, errmsg: "ERR_TRX_SOURCE_FILE_NOT_FOUND [#{job_source}]"} unless File.exists? job_source
208
250
 
209
251
  # Parse dest URI
210
252
  target = URI(job_target)
@@ -212,47 +254,51 @@ class RestFtpDaemon < Sinatra::Base
212
254
  return { code: ERR_REQ_TARGET_SCHEME, errmsg: :ERR_REQ_TARGET_SCHEME} unless target.scheme == "ftp"
213
255
 
214
256
  # Create thread
215
- job = Thread.new(name, job) do
216
- # Initialize context
217
- Thread.current[:name] = name
218
- Thread.current[:created] = Time.now.to_f;
219
- Thread.current[:status] = :thread_initializing
220
-
221
- # Store job info
222
- Thread.current[:context] = context
223
- job_status ERR_BUSY, :thread_initializing
257
+ job = Thread.new(worker_id, worker_name, job) do
258
+ # Tnitialize thread
224
259
  Thread.abort_on_exception = true
260
+ job_status :initializing
261
+ job_error ERR_OK
262
+
263
+ # Initialize job info
264
+ Thread.current[:job] = {}
265
+ Thread.current[:job].merge! context if context.is_a? Enumerable
266
+ Thread.current[:id] = worker_id
267
+ job_set :worker_name, worker_name
268
+ job_set :created, Time.now
225
269
 
226
270
  # Do the job
227
271
  info "new_job: thread running"
228
272
  process_job
229
273
 
230
274
  # Sleep a few seconds before dying
231
- Thread.current[:status] = :thread_ending
275
+ job_status :graceful_ending
232
276
  sleep THREAD_SLEEP_BEFORE_DIE
277
+ job_status :ended
233
278
  info "new_job: thread finished"
234
279
  end
235
280
 
236
281
  # Keep thread in thread group
237
- info "new_job: attaching thread [#{name}] to group"
238
- @@threads.add job
282
+ info "new_job: attaching thread [#{worker_name}] to group"
283
+ @@workers.add job
239
284
 
240
- return { code: 0, errmsg: 'success', name: name, context: context }
285
+ return { code: 0, errmsg: 'success', worker_id: worker_id, context: context }
241
286
  end
242
287
 
243
288
  def info msg=""
244
- @logger.send :info, msg
289
+ @logger.info msg
245
290
  end
246
291
 
247
- def job_status code, errmsg
248
- Thread.current[:context] ||= {}
249
- Thread.current[:context][:code] = code
250
- Thread.current[:context][:errmsg] = errmsg
292
+ def job_error error, errmsg = nil
293
+ job_set :error, error
294
+ job_set :errmsg, errmsg
295
+ end
296
+ def job_status status
297
+ job_set :status, status
251
298
  end
252
299
 
253
- def job_set attribute, value
254
- Thread.current[:context] ||= {}
255
- Thread.current[:context][attribute] = value
300
+ def job_set attribute, value, thread = Thread.current
301
+ thread[:job][attribute] = value if thread[:job].is_a? Enumerable
256
302
  end
257
303
 
258
304
 
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rest-ftp-daemon"
8
- s.version = "0.5.0"
8
+ s.version = "0.7.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Bruno"]
12
- s.date = "2014-07-31"
13
- s.description = "A fairly basic FTP client daemon, driven by RESTful webservice calls"
12
+ s.date = "2014-08-01"
13
+ s.description = "This is a pretty simple FTP client daemon, controlled through a RESTfull API"
14
14
  s.email = "rest-ftp-daemon@bmconseil.com"
15
15
  s.executables = ["rest-ftp-daemon"]
16
16
  s.extra_rdoc_files = [
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "lib/config.rb",
29
29
  "lib/config.ru",
30
30
  "lib/errors.rb",
31
+ "lib/extend_threads.rb",
31
32
  "lib/rest-ftp-daemon.rb",
32
33
  "rest-ftp-daemon.gemspec",
33
34
  "test/helper.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-ftp-daemon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-07-31 00:00:00.000000000 Z
12
+ date: 2014-08-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -91,7 +91,8 @@ dependencies:
91
91
  - - ~>
92
92
  - !ruby/object:Gem::Version
93
93
  version: 2.0.1
94
- description: A fairly basic FTP client daemon, driven by RESTful webservice calls
94
+ description: This is a pretty simple FTP client daemon, controlled through a RESTfull
95
+ API
95
96
  email: rest-ftp-daemon@bmconseil.com
96
97
  executables:
97
98
  - rest-ftp-daemon
@@ -110,6 +111,7 @@ files:
110
111
  - lib/config.rb
111
112
  - lib/config.ru
112
113
  - lib/errors.rb
114
+ - lib/extend_threads.rb
113
115
  - lib/rest-ftp-daemon.rb
114
116
  - rest-ftp-daemon.gemspec
115
117
  - test/helper.rb
@@ -129,7 +131,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
129
131
  version: '0'
130
132
  segments:
131
133
  - 0
132
- hash: -4110508232365468259
134
+ hash: 3650939871823663291
133
135
  required_rubygems_version: !ruby/object:Gem::Requirement
134
136
  none: false
135
137
  requirements: