rest-ftp-daemon 0.2.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 +12 -0
- data/Gemfile.lock +89 -0
- data/LICENSE.txt +20 -0
- data/README.md +88 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/bin/rest-ftp-daemon +16 -0
- data/lib/config.rb +2 -0
- data/lib/errors.rb +16 -0
- data/lib/rest-ftp-daemon.rb +242 -0
- data/lib/rest-ftp-daemon/config.ru +13 -0
- data/test/helper.rb +34 -0
- data/test/test_rest-ftp-daemon.rb +7 -0
- metadata +176 -0
    
        data/Gemfile
    ADDED
    
    
    
        data/Gemfile.lock
    ADDED
    
    | @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            GEM
         | 
| 2 | 
            +
              remote: http://rubygems.org/
         | 
| 3 | 
            +
              specs:
         | 
| 4 | 
            +
                activesupport (4.1.4)
         | 
| 5 | 
            +
                  i18n (~> 0.6, >= 0.6.9)
         | 
| 6 | 
            +
                  json (~> 1.7, >= 1.7.7)
         | 
| 7 | 
            +
                  minitest (~> 5.1)
         | 
| 8 | 
            +
                  thread_safe (~> 0.1)
         | 
| 9 | 
            +
                  tzinfo (~> 1.1)
         | 
| 10 | 
            +
                addressable (2.3.6)
         | 
| 11 | 
            +
                builder (3.2.2)
         | 
| 12 | 
            +
                descendants_tracker (0.0.4)
         | 
| 13 | 
            +
                  thread_safe (~> 0.3, >= 0.3.1)
         | 
| 14 | 
            +
                docile (1.1.5)
         | 
| 15 | 
            +
                faraday (0.9.0)
         | 
| 16 | 
            +
                  multipart-post (>= 1.2, < 3)
         | 
| 17 | 
            +
                git (1.2.7)
         | 
| 18 | 
            +
                github_api (0.12.0)
         | 
| 19 | 
            +
                  addressable (~> 2.3)
         | 
| 20 | 
            +
                  descendants_tracker (~> 0.0.4)
         | 
| 21 | 
            +
                  faraday (~> 0.8, < 0.10)
         | 
| 22 | 
            +
                  hashie (>= 3.2)
         | 
| 23 | 
            +
                  multi_json (>= 1.7.5, < 2.0)
         | 
| 24 | 
            +
                  nokogiri (~> 1.6.3)
         | 
| 25 | 
            +
                  oauth2
         | 
| 26 | 
            +
                hashie (3.2.0)
         | 
| 27 | 
            +
                highline (1.6.21)
         | 
| 28 | 
            +
                i18n (0.6.11)
         | 
| 29 | 
            +
                jeweler (2.0.1)
         | 
| 30 | 
            +
                  builder
         | 
| 31 | 
            +
                  bundler (>= 1.0)
         | 
| 32 | 
            +
                  git (>= 1.2.5)
         | 
| 33 | 
            +
                  github_api
         | 
| 34 | 
            +
                  highline (>= 1.6.15)
         | 
| 35 | 
            +
                  nokogiri (>= 1.5.10)
         | 
| 36 | 
            +
                  rake
         | 
| 37 | 
            +
                  rdoc
         | 
| 38 | 
            +
                json (1.8.1)
         | 
| 39 | 
            +
                jwt (1.0.0)
         | 
| 40 | 
            +
                mini_portile (0.6.0)
         | 
| 41 | 
            +
                minitest (5.4.0)
         | 
| 42 | 
            +
                multi_json (1.10.1)
         | 
| 43 | 
            +
                multi_xml (0.5.5)
         | 
| 44 | 
            +
                multipart-post (2.0.0)
         | 
| 45 | 
            +
                nokogiri (1.6.3.1)
         | 
| 46 | 
            +
                  mini_portile (= 0.6.0)
         | 
| 47 | 
            +
                oauth2 (1.0.0)
         | 
| 48 | 
            +
                  faraday (>= 0.8, < 0.10)
         | 
| 49 | 
            +
                  jwt (~> 1.0)
         | 
| 50 | 
            +
                  multi_json (~> 1.3)
         | 
| 51 | 
            +
                  multi_xml (~> 0.5)
         | 
| 52 | 
            +
                  rack (~> 1.2)
         | 
| 53 | 
            +
                rack (1.5.2)
         | 
| 54 | 
            +
                rack-protection (1.5.3)
         | 
| 55 | 
            +
                  rack
         | 
| 56 | 
            +
                rake (10.3.2)
         | 
| 57 | 
            +
                rdoc (3.12.2)
         | 
| 58 | 
            +
                  json (~> 1.4)
         | 
| 59 | 
            +
                shoulda (3.5.0)
         | 
| 60 | 
            +
                  shoulda-context (~> 1.0, >= 1.0.1)
         | 
| 61 | 
            +
                  shoulda-matchers (>= 1.4.1, < 3.0)
         | 
| 62 | 
            +
                shoulda-context (1.2.1)
         | 
| 63 | 
            +
                shoulda-matchers (2.6.2)
         | 
| 64 | 
            +
                  activesupport (>= 3.0.0)
         | 
| 65 | 
            +
                simplecov (0.9.0)
         | 
| 66 | 
            +
                  docile (~> 1.1.0)
         | 
| 67 | 
            +
                  multi_json
         | 
| 68 | 
            +
                  simplecov-html (~> 0.8.0)
         | 
| 69 | 
            +
                simplecov-html (0.8.0)
         | 
| 70 | 
            +
                sinatra (1.4.5)
         | 
| 71 | 
            +
                  rack (~> 1.4)
         | 
| 72 | 
            +
                  rack-protection (~> 1.4)
         | 
| 73 | 
            +
                  tilt (~> 1.3, >= 1.3.4)
         | 
| 74 | 
            +
                thread_safe (0.3.4)
         | 
| 75 | 
            +
                tilt (1.4.1)
         | 
| 76 | 
            +
                tzinfo (1.2.1)
         | 
| 77 | 
            +
                  thread_safe (~> 0.1)
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            PLATFORMS
         | 
| 80 | 
            +
              ruby
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            DEPENDENCIES
         | 
| 83 | 
            +
              bundler (~> 1.0)
         | 
| 84 | 
            +
              jeweler (~> 2.0.1)
         | 
| 85 | 
            +
              json
         | 
| 86 | 
            +
              rdoc (~> 3.12)
         | 
| 87 | 
            +
              shoulda
         | 
| 88 | 
            +
              simplecov
         | 
| 89 | 
            +
              sinatra
         | 
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            Copyright (c) 2014 Bruno Medici Consultant
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 4 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 5 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 6 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 7 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 8 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 9 | 
            +
            the following conditions:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 12 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 15 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 16 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 17 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 18 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 19 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 20 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,88 @@ | |
| 1 | 
            +
            ## rest-ftp-daemon ##
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This is a fairly basic FTP client daemon, driven by REST-based webservice calls.
         | 
| 4 | 
            +
            As of today, its main features are :
         | 
| 5 | 
            +
             | 
| 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
         | 
| 11 | 
            +
             | 
| 12 | 
            +
             | 
| 13 | 
            +
            ## Quick setup ##
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            This project requires ruby >= 1.9 and rubygems installed.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Furthermore, gems are required to run it:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
             ``` gem install sinatra ```
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Starting the daemon:
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ``` thin start ```
         | 
| 24 | 
            +
             | 
| 25 | 
            +
             | 
| 26 | 
            +
            ## Basic usage ##
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Starting a job transferring file named "file.ova"
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
         | 
| 31 | 
            +
              '{"source":"~/file.ova","target":"ftp://anonymous@localhost/incoming/dest2.ova"}' "http://localhost:3000/jobs"
         | 
| 32 | 
            +
             | 
| 33 | 
            +
             | 
| 34 | 
            +
            Starting a job transferring file named "dmg"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
         | 
| 37 | 
            +
              '{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg"}' "http://localhost:3000/jobs"
         | 
| 38 | 
            +
             | 
| 39 | 
            +
             | 
| 40 | 
            +
            Delete a specific job
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
         | 
| 43 | 
            +
             | 
| 44 | 
            +
             | 
| 45 | 
            +
             | 
| 46 | 
            +
            ## Getting status ##
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              GET /jobs
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            Would return:
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              [
         | 
| 53 | 
            +
                {
         | 
| 54 | 
            +
                  "id": "bob-49126-8",
         | 
| 55 | 
            +
                  "process": "sleep",
         | 
| 56 | 
            +
                  "status": "transferring",
         | 
| 57 | 
            +
                  "context": {
         | 
| 58 | 
            +
                    "source": "~\/file.ova",
         | 
| 59 | 
            +
                    "target": "ftp:\/\/anonymous@localhost\/incoming\/dest2.ova",
         | 
| 60 | 
            +
                    "code": -1,
         | 
| 61 | 
            +
                    "errmsg": "running",
         | 
| 62 | 
            +
                    "source_size": 1849036800,
         | 
| 63 | 
            +
                    "progress": 1.9,
         | 
| 64 | 
            +
                    "transferred": 34800000
         | 
| 65 | 
            +
                  }
         | 
| 66 | 
            +
                },
         | 
| 67 | 
            +
                {
         | 
| 68 | 
            +
                  "id": "bob-49126-9",
         | 
| 69 | 
            +
                  "process": "sleep",
         | 
| 70 | 
            +
                  "status": "transferring",
         | 
| 71 | 
            +
                  "context": {
         | 
| 72 | 
            +
                    "source": "~\/file.dmg",
         | 
| 73 | 
            +
                    "target": "ftp:\/\/anonymous@localhost\/incoming\/dest4.dmg",
         | 
| 74 | 
            +
                    "code": -1,
         | 
| 75 | 
            +
                    "errmsg": "running",
         | 
| 76 | 
            +
                    "source_size": 37109074,
         | 
| 77 | 
            +
                    "progress": 32.9,
         | 
| 78 | 
            +
                    "transferred": 12200000
         | 
| 79 | 
            +
                  }
         | 
| 80 | 
            +
                }
         | 
| 81 | 
            +
              ]
         | 
| 82 | 
            +
             | 
| 83 | 
            +
             | 
| 84 | 
            +
            ### About ###
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            Bruno MEDICI
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            http://bmconseil.com/
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'rubygems'
         | 
| 4 | 
            +
            require 'bundler'
         | 
| 5 | 
            +
            begin
         | 
| 6 | 
            +
              Bundler.setup(:default, :development)
         | 
| 7 | 
            +
            rescue Bundler::BundlerError => e
         | 
| 8 | 
            +
              $stderr.puts e.message
         | 
| 9 | 
            +
              $stderr.puts "Run `bundle install` to install missing gems"
         | 
| 10 | 
            +
              exit e.status_code
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
            require 'rake'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require 'jeweler'
         | 
| 15 | 
            +
            Jeweler::Tasks.new do |gem|
         | 
| 16 | 
            +
              # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
         | 
| 17 | 
            +
              gem.name = "rest-ftp-daemon"
         | 
| 18 | 
            +
              gem.homepage = "http://github.com/bmedici/rest-ftp-daemon"
         | 
| 19 | 
            +
              gem.license = "MIT"
         | 
| 20 | 
            +
              gem.summary = "RESTful FTP client daemon"
         | 
| 21 | 
            +
              gem.description = "A fairly basic FTP client daemon, driven by RESTful webservice calls"
         | 
| 22 | 
            +
              gem.email = "rest-ftp-daemon@bmconseil.com"
         | 
| 23 | 
            +
              gem.authors = ["Bruno"]
         | 
| 24 | 
            +
              # dependencies defined in Gemfile
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
            Jeweler::RubygemsDotOrgTasks.new
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            require 'rake/testtask'
         | 
| 29 | 
            +
            Rake::TestTask.new(:test) do |test|
         | 
| 30 | 
            +
              test.libs << 'lib' << 'test'
         | 
| 31 | 
            +
              test.pattern = 'test/**/test_*.rb'
         | 
| 32 | 
            +
              test.verbose = true
         | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            desc "Code coverage detail"
         | 
| 36 | 
            +
            task :simplecov do
         | 
| 37 | 
            +
              ENV['COVERAGE'] = "true"
         | 
| 38 | 
            +
              Rake::Task['test'].execute
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            task :default => :test
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            require 'rdoc/task'
         | 
| 44 | 
            +
            Rake::RDocTask.new do |rdoc|
         | 
| 45 | 
            +
              version = File.exist?('VERSION') ? File.read('VERSION') : ""
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              rdoc.rdoc_dir = 'rdoc'
         | 
| 48 | 
            +
              rdoc.title = "rest-ftp-daemon #{version}"
         | 
| 49 | 
            +
              rdoc.rdoc_files.include('README*')
         | 
| 50 | 
            +
              rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 51 | 
            +
            end
         | 
    
        data/VERSION
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            0.2.0
         | 
    
        data/bin/rest-ftp-daemon
    ADDED
    
    | @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Libs and init
         | 
| 4 | 
            +
            require 'thin'
         | 
| 5 | 
            +
            APP_ROOT = File.dirname(__FILE__) + '/../'
         | 
| 6 | 
            +
            DEFAULT_PORT = 3000
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # Prepare thin
         | 
| 9 | 
            +
            rackup_file = File.expand_path "#{APP_ROOT}/lib/rest-ftp-daemon/config.ru"
         | 
| 10 | 
            +
            argv = ARGV
         | 
| 11 | 
            +
            argv << ["-R", rackup_file] unless ARGV.include?("-R")
         | 
| 12 | 
            +
            argv << ["-p", DEFAULT_PORT.to_s] unless ARGV.include?("-p")
         | 
| 13 | 
            +
            argv << ["-e", "production"] unless ARGV.include?("-e")
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # Start thin
         | 
| 16 | 
            +
            Thin::Runner.new(argv.flatten).run!
         | 
    
        data/lib/config.rb
    ADDED
    
    
    
        data/lib/errors.rb
    ADDED
    
    | @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # Error codes
         | 
| 2 | 
            +
            # O: all ok
         | 
| 3 | 
            +
            # 1x: request body errors
         | 
| 4 | 
            +
            # 2x: transfer errors
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            ERR_OK                            = 0
         | 
| 7 | 
            +
            ERR_BUSY                          = -1
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ERR_REQ_SOURCE_MISSING            = 11
         | 
| 10 | 
            +
            ERR_REQ_TARGET_MISSING            = 12
         | 
| 11 | 
            +
            ERR_REQ_TARGET_SCHEME             = 13
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ERR_JOB_SOURCE_NOTFOUND            = 21
         | 
| 14 | 
            +
            ERR_JOB_TARGET_UNPARSEABLE         = 22
         | 
| 15 | 
            +
            ERR_JOB_PERMISSION                 = 24
         | 
| 16 | 
            +
             | 
| @@ -0,0 +1,242 @@ | |
| 1 | 
            +
            class RestFtpDaemon < Sinatra::Base
         | 
| 2 | 
            +
              helpers Sinatra::JSON
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              # General config
         | 
| 5 | 
            +
              configure :development, :production do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # Create new thread group
         | 
| 8 | 
            +
                @@threads = ThreadGroup.new
         | 
| 9 | 
            +
                #set :dummy, true
         | 
| 10 | 
            +
                # set :sessions, false
         | 
| 11 | 
            +
                # set :logging, true
         | 
| 12 | 
            +
                # set :root, APP_ROOT + '/lib/'
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # Server initialization
         | 
| 16 | 
            +
              def initialize
         | 
| 17 | 
            +
                # Setup logger
         | 
| 18 | 
            +
                @logger = Logger.new(APP_ROOT + '/main.log','daily')
         | 
| 19 | 
            +
                @logger.level = Logger::INFO
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # Other stuff
         | 
| 22 | 
            +
                @@last_worker_id = 0
         | 
| 23 | 
            +
                @@hostname = `hostname`.chomp
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                super
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              # Server global status
         | 
| 29 | 
            +
              get "/" do
         | 
| 30 | 
            +
                content_type :json
         | 
| 31 | 
            +
                json get_status
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              # List jobs
         | 
| 35 | 
            +
              get "/jobs" do
         | 
| 36 | 
            +
                # Build response
         | 
| 37 | 
            +
                content_type :json
         | 
| 38 | 
            +
                json get_jobs
         | 
| 39 | 
            +
                #@@threads.count
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              # List jobs
         | 
| 43 | 
            +
              delete "/jobs/:name" do
         | 
| 44 | 
            +
                # Kill this job
         | 
| 45 | 
            +
                ret = delete_job params[:name]
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                # Fail if no process has been killed
         | 
| 48 | 
            +
                error 404 if ret<1
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                # Build response
         | 
| 51 | 
            +
                content_type :json
         | 
| 52 | 
            +
                json nil
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              # Spawn a new thread for this new job
         | 
| 56 | 
            +
              post '/jobs' do
         | 
| 57 | 
            +
                request.body.rewind
         | 
| 58 | 
            +
                payload = JSON.parse request.body.read
         | 
| 59 | 
            +
                info "POST / with #{payload.to_json}"
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # Spawn a thread
         | 
| 62 | 
            +
                #config = {}
         | 
| 63 | 
            +
                result = new_job payload
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                # Build response
         | 
| 66 | 
            +
                content_type :json
         | 
| 67 | 
            +
                json result
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              protected
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def process_job
         | 
| 73 | 
            +
                # Init
         | 
| 74 | 
            +
                info "process_job: starting"
         | 
| 75 | 
            +
                context = Thread.current[:context]
         | 
| 76 | 
            +
                transferred = 0
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # Check source
         | 
| 79 | 
            +
                job_source = File.expand_path(context["source"])
         | 
| 80 | 
            +
                if !(File.exists? job_source)
         | 
| 81 | 
            +
                  job_status ERR_JOB_SOURCE_NOTFOUND, :ERR_JOB_SOURCE_NOTFOUND
         | 
| 82 | 
            +
                  return
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
                info "process_job: job_source: #{job_source}"
         | 
| 85 | 
            +
                source_size = File.size job_source
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                # Check target
         | 
| 88 | 
            +
                job_target = context["target"]
         | 
| 89 | 
            +
                target = URI(job_target) rescue nil
         | 
| 90 | 
            +
                if job_target.nil? || target.nil?
         | 
| 91 | 
            +
                  job_status ERR_JOB_TARGET_UNPARSEABLE, :ERR_JOB_TARGET_UNPARSEABLE
         | 
| 92 | 
            +
                  return
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
                info "process_job: job_target: #{job_target}"
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                # Split URI
         | 
| 97 | 
            +
                target_path = File.dirname target.path
         | 
| 98 | 
            +
                target_name = File.basename target.path
         | 
| 99 | 
            +
                info "ftp_transfer: job_target.host [#{target.host}]"
         | 
| 100 | 
            +
                info "ftp_transfer: target_path [#{target_path}]"
         | 
| 101 | 
            +
                info "ftp_transfer: target_name [#{target_name}]"
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                # Prepare FTP transfer
         | 
| 104 | 
            +
                ftp = Net::FTP.new(target.host)
         | 
| 105 | 
            +
                ftp.passive = true
         | 
| 106 | 
            +
                ftp.login
         | 
| 107 | 
            +
                ftp.chdir(target_path)
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                 # Do transfer
         | 
| 110 | 
            +
                info "source: starting stransfer"
         | 
| 111 | 
            +
                Thread.current[:status] = :transferring
         | 
| 112 | 
            +
                job_status ERR_BUSY, :running
         | 
| 113 | 
            +
                job_set :source_size, source_size
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                begin
         | 
| 116 | 
            +
                  ftp.putbinaryfile(job_source, target_name, TRANSFER_CHUNK_SIZE) do |block|
         | 
| 117 | 
            +
                    # Update thread info
         | 
| 118 | 
            +
                    percent = (100.0 * transferred / source_size).round(1)
         | 
| 119 | 
            +
                    info "transferring [#{percent} %] of [#{target_name}]"
         | 
| 120 | 
            +
                    job_set :progress, percent
         | 
| 121 | 
            +
                    job_set :transferred, transferred
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    # Update counters
         | 
| 124 | 
            +
                    transferred += TRANSFER_CHUNK_SIZE
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                rescue Net::FTPPermError
         | 
| 128 | 
            +
                  Thread.current[:status] = :failed
         | 
| 129 | 
            +
                  job_status ERR_JOB_PERMISSION, :ERR_JOB_PERMISSION
         | 
| 130 | 
            +
                  info "source: FAILED: PERMISSIONS ERROR"
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                else
         | 
| 133 | 
            +
                  Thread.current[:status] = :finished
         | 
| 134 | 
            +
                  job_status ERR_OK, :finished
         | 
| 135 | 
            +
                  info "source: finished stransfer"
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                ftp.close
         | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
              def get_status
         | 
| 142 | 
            +
                {
         | 
| 143 | 
            +
                greeting: "REST FTP daemon: up and running",
         | 
| 144 | 
            +
                jobs_count: @@threads.list.count,
         | 
| 145 | 
            +
                }
         | 
| 146 | 
            +
              end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
              def get_jobs
         | 
| 149 | 
            +
                output = []
         | 
| 150 | 
            +
                @@threads.list.each do |thread|
         | 
| 151 | 
            +
                  output << {
         | 
| 152 | 
            +
                  :id => thread[:name],
         | 
| 153 | 
            +
                  :process => thread.status,
         | 
| 154 | 
            +
                  :status =>  thread[:status],
         | 
| 155 | 
            +
                  :context => thread[:context],
         | 
| 156 | 
            +
                  }
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
                output
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
             | 
| 162 | 
            +
              def delete_job name
         | 
| 163 | 
            +
                count = 0
         | 
| 164 | 
            +
                @@threads.list.collect do |thread|
         | 
| 165 | 
            +
                  next unless thread[:name] == name
         | 
| 166 | 
            +
                  Thread.kill(thread)
         | 
| 167 | 
            +
                  count += 1
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
                count
         | 
| 170 | 
            +
              end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
              def new_job context = {}
         | 
| 173 | 
            +
                info "new_job: creating thread"
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                # Generate name
         | 
| 176 | 
            +
                @@last_worker_id +=1
         | 
| 177 | 
            +
                host = @@hostname.split('.')[0]
         | 
| 178 | 
            +
                name = "#{host}-#{Process.pid.to_s}-#{@@last_worker_id}"
         | 
| 179 | 
            +
                info "new_job: creating thread [#{name}]"
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                # Parse parameters
         | 
| 182 | 
            +
                job_source = context["source"]
         | 
| 183 | 
            +
                job_target = context["target"]
         | 
| 184 | 
            +
                return { code: ERR_REQ_SOURCE_MISSING, errmsg: :ERR_REQ_SOURCE_MISSING} if job_source.nil?
         | 
| 185 | 
            +
                return { code: ERR_REQ_TARGET_MISSING, errmsg: :ERR_REQ_TARGET_MISSING} if job_target.nil?
         | 
| 186 | 
            +
                #return { code: ERR_TRX_SOURCE_FILE_NOT_FOUND, errmsg: "ERR_TRX_SOURCE_FILE_NOT_FOUND [#{job_source}]"} unless File.exists? job_source
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                # Parse dest URI
         | 
| 189 | 
            +
                target = URI(job_target)
         | 
| 190 | 
            +
                info target.scheme
         | 
| 191 | 
            +
                return { code: ERR_REQ_TARGET_SCHEME, errmsg: :ERR_REQ_TARGET_SCHEME} unless target.scheme == "ftp"
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                # Create thread
         | 
| 194 | 
            +
                job = Thread.new(name, job) do
         | 
| 195 | 
            +
                  # Initialize context
         | 
| 196 | 
            +
                  Thread.current[:name] = name
         | 
| 197 | 
            +
                  Thread.current[:created] = Time.now.to_f;
         | 
| 198 | 
            +
                  Thread.current[:status] = :thread_initializing
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                  # Store job info
         | 
| 201 | 
            +
                  Thread.current[:context] = context
         | 
| 202 | 
            +
                  job_status ERR_BUSY, :thread_initializing
         | 
| 203 | 
            +
                  Thread.abort_on_exception = true
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  # Do the job
         | 
| 206 | 
            +
                  info "new_job: thread running"
         | 
| 207 | 
            +
                  process_job
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  # Sleep a few seconds before dying
         | 
| 210 | 
            +
                  Thread.current[:status] = :thread_ending
         | 
| 211 | 
            +
                  sleep THREAD_SLEEP_BEFORE_DIE
         | 
| 212 | 
            +
                  info "new_job: thread finished"
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                # Keep thread in thread group
         | 
| 216 | 
            +
                info "new_job: attaching thread [#{name}] to group"
         | 
| 217 | 
            +
                @@threads.add job
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                return { code: 0, errmsg: 'success', name: name, context: context }
         | 
| 220 | 
            +
              end
         | 
| 221 | 
            +
             | 
| 222 | 
            +
              def log level, msg=""
         | 
| 223 | 
            +
                @logger.send(level.to_s, msg)
         | 
| 224 | 
            +
              end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
              def info msg=""
         | 
| 227 | 
            +
                log :info, msg
         | 
| 228 | 
            +
              end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
              def job_status code, errmsg
         | 
| 231 | 
            +
                Thread.current[:context] ||= {}
         | 
| 232 | 
            +
                Thread.current[:context][:code] = code
         | 
| 233 | 
            +
                Thread.current[:context][:errmsg] = errmsg
         | 
| 234 | 
            +
              end
         | 
| 235 | 
            +
             | 
| 236 | 
            +
              def job_set attribute, value
         | 
| 237 | 
            +
                Thread.current[:context] ||= {}
         | 
| 238 | 
            +
                Thread.current[:context][attribute] = value
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
             | 
| 242 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            # Main libs
         | 
| 2 | 
            +
            require 'sinatra'
         | 
| 3 | 
            +
            require 'sinatra/base'
         | 
| 4 | 
            +
            require "sinatra/json"
         | 
| 5 | 
            +
            require "sinatra/config_file"
         | 
| 6 | 
            +
            require 'net/ftp'
         | 
| 7 | 
            +
            require 'json'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # My local libs
         | 
| 10 | 
            +
            Dir[APP_ROOT+"/lib/*.rb"].each {|file| require File.expand_path file }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            # Start application
         | 
| 13 | 
            +
            run RestFtpDaemon
         | 
    
        data/test/helper.rb
    ADDED
    
    | @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            require 'simplecov'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module SimpleCov::Configuration
         | 
| 4 | 
            +
              def clean_filters
         | 
| 5 | 
            +
                @filters = []
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
            end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            SimpleCov.configure do
         | 
| 10 | 
            +
              clean_filters
         | 
| 11 | 
            +
              load_adapter 'test_frameworks'
         | 
| 12 | 
            +
            end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ENV["COVERAGE"] && SimpleCov.start do
         | 
| 15 | 
            +
              add_filter "/.rvm/"
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
            require 'rubygems'
         | 
| 18 | 
            +
            require 'bundler'
         | 
| 19 | 
            +
            begin
         | 
| 20 | 
            +
              Bundler.setup(:default, :development)
         | 
| 21 | 
            +
            rescue Bundler::BundlerError => e
         | 
| 22 | 
            +
              $stderr.puts e.message
         | 
| 23 | 
            +
              $stderr.puts "Run `bundle install` to install missing gems"
         | 
| 24 | 
            +
              exit e.status_code
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
            require 'test/unit'
         | 
| 27 | 
            +
            require 'shoulda'
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
         | 
| 30 | 
            +
            $LOAD_PATH.unshift(File.dirname(__FILE__))
         | 
| 31 | 
            +
            require 'rest-ftp-daemon'
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            class Test::Unit::TestCase
         | 
| 34 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,176 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: rest-ftp-daemon
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Bruno
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2014-07-31 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies:
         | 
| 14 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 15 | 
            +
              name: sinatra
         | 
| 16 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 17 | 
            +
                none: false
         | 
| 18 | 
            +
                requirements:
         | 
| 19 | 
            +
                - - ! '>='
         | 
| 20 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            +
                    version: '0'
         | 
| 22 | 
            +
              type: :runtime
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements:
         | 
| 27 | 
            +
                - - ! '>='
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            +
                    version: '0'
         | 
| 30 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 31 | 
            +
              name: json
         | 
| 32 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 33 | 
            +
                none: false
         | 
| 34 | 
            +
                requirements:
         | 
| 35 | 
            +
                - - ! '>='
         | 
| 36 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 37 | 
            +
                    version: '0'
         | 
| 38 | 
            +
              type: :runtime
         | 
| 39 | 
            +
              prerelease: false
         | 
| 40 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements:
         | 
| 43 | 
            +
                - - ! '>='
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            +
                    version: '0'
         | 
| 46 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 47 | 
            +
              name: shoulda
         | 
| 48 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 49 | 
            +
                none: false
         | 
| 50 | 
            +
                requirements:
         | 
| 51 | 
            +
                - - ! '>='
         | 
| 52 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 53 | 
            +
                    version: '0'
         | 
| 54 | 
            +
              type: :development
         | 
| 55 | 
            +
              prerelease: false
         | 
| 56 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 57 | 
            +
                none: false
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ! '>='
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '0'
         | 
| 62 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 63 | 
            +
              name: rdoc
         | 
| 64 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                none: false
         | 
| 66 | 
            +
                requirements:
         | 
| 67 | 
            +
                - - ~>
         | 
| 68 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 69 | 
            +
                    version: '3.12'
         | 
| 70 | 
            +
              type: :development
         | 
| 71 | 
            +
              prerelease: false
         | 
| 72 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 73 | 
            +
                none: false
         | 
| 74 | 
            +
                requirements:
         | 
| 75 | 
            +
                - - ~>
         | 
| 76 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 77 | 
            +
                    version: '3.12'
         | 
| 78 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 79 | 
            +
              name: bundler
         | 
| 80 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 81 | 
            +
                none: false
         | 
| 82 | 
            +
                requirements:
         | 
| 83 | 
            +
                - - ~>
         | 
| 84 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 85 | 
            +
                    version: '1.0'
         | 
| 86 | 
            +
              type: :development
         | 
| 87 | 
            +
              prerelease: false
         | 
| 88 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 89 | 
            +
                none: false
         | 
| 90 | 
            +
                requirements:
         | 
| 91 | 
            +
                - - ~>
         | 
| 92 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 93 | 
            +
                    version: '1.0'
         | 
| 94 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 95 | 
            +
              name: jeweler
         | 
| 96 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 97 | 
            +
                none: false
         | 
| 98 | 
            +
                requirements:
         | 
| 99 | 
            +
                - - ~>
         | 
| 100 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 101 | 
            +
                    version: 2.0.1
         | 
| 102 | 
            +
              type: :development
         | 
| 103 | 
            +
              prerelease: false
         | 
| 104 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 105 | 
            +
                none: false
         | 
| 106 | 
            +
                requirements:
         | 
| 107 | 
            +
                - - ~>
         | 
| 108 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 109 | 
            +
                    version: 2.0.1
         | 
| 110 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 111 | 
            +
              name: simplecov
         | 
| 112 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 113 | 
            +
                none: false
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - ! '>='
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '0'
         | 
| 118 | 
            +
              type: :development
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                none: false
         | 
| 122 | 
            +
                requirements:
         | 
| 123 | 
            +
                - - ! '>='
         | 
| 124 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            +
                    version: '0'
         | 
| 126 | 
            +
            description: A fairly basic FTP client daemon, driven by RESTful webservice calls
         | 
| 127 | 
            +
            email: rest-ftp-daemon@bmconseil.com
         | 
| 128 | 
            +
            executables:
         | 
| 129 | 
            +
            - rest-ftp-daemon
         | 
| 130 | 
            +
            extensions: []
         | 
| 131 | 
            +
            extra_rdoc_files:
         | 
| 132 | 
            +
            - LICENSE.txt
         | 
| 133 | 
            +
            - README.md
         | 
| 134 | 
            +
            files:
         | 
| 135 | 
            +
            - Gemfile
         | 
| 136 | 
            +
            - Gemfile.lock
         | 
| 137 | 
            +
            - LICENSE.txt
         | 
| 138 | 
            +
            - README.md
         | 
| 139 | 
            +
            - Rakefile
         | 
| 140 | 
            +
            - VERSION
         | 
| 141 | 
            +
            - bin/rest-ftp-daemon
         | 
| 142 | 
            +
            - lib/config.rb
         | 
| 143 | 
            +
            - lib/errors.rb
         | 
| 144 | 
            +
            - lib/rest-ftp-daemon.rb
         | 
| 145 | 
            +
            - lib/rest-ftp-daemon/config.ru
         | 
| 146 | 
            +
            - test/helper.rb
         | 
| 147 | 
            +
            - test/test_rest-ftp-daemon.rb
         | 
| 148 | 
            +
            homepage: http://github.com/bmedici/rest-ftp-daemon
         | 
| 149 | 
            +
            licenses:
         | 
| 150 | 
            +
            - MIT
         | 
| 151 | 
            +
            post_install_message: 
         | 
| 152 | 
            +
            rdoc_options: []
         | 
| 153 | 
            +
            require_paths:
         | 
| 154 | 
            +
            - lib
         | 
| 155 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 156 | 
            +
              none: false
         | 
| 157 | 
            +
              requirements:
         | 
| 158 | 
            +
              - - ! '>='
         | 
| 159 | 
            +
                - !ruby/object:Gem::Version
         | 
| 160 | 
            +
                  version: '0'
         | 
| 161 | 
            +
                  segments:
         | 
| 162 | 
            +
                  - 0
         | 
| 163 | 
            +
                  hash: 3830976928581563014
         | 
| 164 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 165 | 
            +
              none: false
         | 
| 166 | 
            +
              requirements:
         | 
| 167 | 
            +
              - - ! '>='
         | 
| 168 | 
            +
                - !ruby/object:Gem::Version
         | 
| 169 | 
            +
                  version: '0'
         | 
| 170 | 
            +
            requirements: []
         | 
| 171 | 
            +
            rubyforge_project: 
         | 
| 172 | 
            +
            rubygems_version: 1.8.23
         | 
| 173 | 
            +
            signing_key: 
         | 
| 174 | 
            +
            specification_version: 3
         | 
| 175 | 
            +
            summary: RESTful FTP client daemon
         | 
| 176 | 
            +
            test_files: []
         |