rest-ftp-daemon 0.6.0 → 0.9.0

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