sp-job 0.1.17 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +40 -17
- data/VERSION +1 -1
- data/bin/console +1 -1
- data/bin/unique-file +55 -0
- data/lib/sp-job.rb +9 -8
- data/lib/sp/job/back_burner.rb +301 -42
- data/lib/sp/job/broker.rb +59 -21
- data/lib/sp/job/common.rb +288 -156
- data/lib/sp/job/job_db_adapter.rb +63 -0
- data/lib/sp/job/{engine.rb → jwt.rb} +14 -12
- data/lib/sp/job/mail_queue.rb +60 -0
- data/lib/sp/job/pg_connection.rb +68 -76
- data/lib/sp/job/unique_file.rb +73 -0
- data/lib/sp/job/uploaded_image_converter.rb +35 -16
- data/lib/sp/job/worker.rb +1 -8
- data/lib/sp/job/worker_thread.rb +57 -0
- data/lib/tasks/configure.rake +66 -16
- data/sp-job.gemspec +10 -8
- metadata +23 -17
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            module SP
         | 
| 2 | 
            +
              module Job
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                unless RUBY_ENGINE == 'jruby'  # TODO suck in the base class from SP-DUH
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  class JobDbAdapter < ::SP::Duh::JSONAPI::Adapters::Db
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    private
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      # Implement the JSONAPI request by direct querying of the JSONAPI function in the database
         | 
| 11 | 
            +
                      def do_request_on_the_db(method, path, params)
         | 
| 12 | 
            +
                        jsonapi_query = %Q[ SELECT * FROM public.jsonapi($1, $2, $3, $4, $5, $6, $7, $8, $9) ]
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                        response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id, company_id, company_schema, sharded_schema, accounting_schema, accounting_prefix
         | 
| 15 | 
            +
                        response.first if response.first
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      def explicit_do_request_on_the_db(exp_accounting_schema, exp_accounting_prefix, method, path, params)
         | 
| 19 | 
            +
                        jsonapi_query = %Q[ SELECT * FROM public.jsonapi($1, $2, $3, $4, $5, $6, $7, $8, $9) ]
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id, company_id, company_schema, sharded_schema, exp_accounting_schema, exp_accounting_prefix
         | 
| 22 | 
            +
                        response.first if response.first
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      def user_id           ; service.parameters.user_id ; end
         | 
| 26 | 
            +
                      def company_id        ; service.parameters.company_id ; end
         | 
| 27 | 
            +
                      def company_schema    ; service.parameters.company_schema.nil? ? nil : service.parameters.company_schema ; end
         | 
| 28 | 
            +
                      def sharded_schema    ; service.parameters.sharded_schema.nil? ? nil : service.parameters.sharded_schema ; end
         | 
| 29 | 
            +
                      def accounting_schema ; service.parameters.accounting_schema.nil? ? nil : service.parameters.accounting_schema ; end
         | 
| 30 | 
            +
                      def accounting_prefix ; service.parameters.accounting_prefix.nil? ? nil : service.parameters.accounting_prefix ; end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      def params_for_body(params)
         | 
| 33 | 
            +
                        params.blank? ?  '' : params.to_json
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      def params_for_query(params)
         | 
| 37 | 
            +
                        query = ""
         | 
| 38 | 
            +
                        if !params.blank?
         | 
| 39 | 
            +
                          case
         | 
| 40 | 
            +
                            when params.is_a?(Array)
         | 
| 41 | 
            +
                              # query = params.join('&')
         | 
| 42 | 
            +
                              query = params.map{ |v| URI.encode(URI.encode(v), "&") }.join('&')
         | 
| 43 | 
            +
                            when params.is_a?(Hash)
         | 
| 44 | 
            +
                              query = params.map do |k,v|
         | 
| 45 | 
            +
                                if v.is_a?(String)
         | 
| 46 | 
            +
                                  "#{k}=\"#{URI.encode(URI.encode(v), "&")}\""
         | 
| 47 | 
            +
                                else
         | 
| 48 | 
            +
                                  "#{k}=#{v}"
         | 
| 49 | 
            +
                                end
         | 
| 50 | 
            +
                              end.join('&')
         | 
| 51 | 
            +
                            else
         | 
| 52 | 
            +
                              query = params.to_s
         | 
| 53 | 
            +
                          end
         | 
| 54 | 
            +
                        end
         | 
| 55 | 
            +
                        query
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            #
         | 
| 2 | 
            -
            #  | 
| 2 | 
            +
            # Helper to obtain tokens to access toconline API's.
         | 
| 3 3 | 
             
            #
         | 
| 4 | 
            -
            #  | 
| 4 | 
            +
            # And this is the mix-in we'll apply to Job execution classes
         | 
| 5 5 | 
             
            #
         | 
| 6 6 | 
             
            # sp-job is free software: you can redistribute it and/or modify
         | 
| 7 7 | 
             
            # it under the terms of the GNU Affero General Public License as published by
         | 
| @@ -19,16 +19,18 @@ | |
| 19 19 | 
             
            # encoding: utf-8
         | 
| 20 20 | 
             
            #
         | 
| 21 21 |  | 
| 22 | 
            +
            require 'jwt' # https://github.com/jwt/ruby-jwt
         | 
| 23 | 
            +
             | 
| 22 24 | 
             
            module SP
         | 
| 23 25 | 
             
              module Job
         | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            +
              	class JWTHelper
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            		# encode & sign jwt
         | 
| 29 | 
            +
            		def self.encode(key:, payload:)
         | 
| 30 | 
            +
            			rsa_private = OpenSSL::PKey::RSA.new( File.read( key ) )
         | 
| 31 | 
            +
            			return JWT.encode payload, rsa_private, 'RS256', { :typ => "JWT" }
         | 
| 32 | 
            +
            		end #self.encodeJWT
         | 
| 26 33 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                    end
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
              end
         | 
| 34 | 
            -
            end
         | 
| 34 | 
            +
                end # end class 'JWT'
         | 
| 35 | 
            +
              end # module Job
         | 
| 36 | 
            +
            end# module SP
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file is part of sp-job.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # sp-job is free software: you can redistribute it and/or modify
         | 
| 7 | 
            +
            # it under the terms of the GNU Affero General Public License as published by
         | 
| 8 | 
            +
            # the Free Software Foundation, either version 3 of the License, or
         | 
| 9 | 
            +
            # (at your option) any later version.
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # sp-job is distributed in the hope that it will be useful,
         | 
| 12 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 13 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 14 | 
            +
            # GNU General Public License for more details.
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # You should have received a copy of the GNU Affero General Public License
         | 
| 17 | 
            +
            # along with sp-job.  If not, see <http://www.gnu.org/licenses/>.
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
            # encoding: utf-8
         | 
| 20 | 
            +
            #
         | 
| 21 | 
            +
            # require 'sp-job'
         | 
| 22 | 
            +
            # require 'sp/job/back_burner'
         | 
| 23 | 
            +
            # require 'sp/job/mail_queue'
         | 
| 24 | 
            +
            #
         | 
| 25 | 
            +
            # class MailQueue < ::SP::Job::MailQueue
         | 
| 26 | 
            +
            #
         | 
| 27 | 
            +
            #    #  Overide methods if needed!
         | 
| 28 | 
            +
            #
         | 
| 29 | 
            +
            # end
         | 
| 30 | 
            +
            #
         | 
| 31 | 
            +
            # Backburner.work('mail-queue')
         | 
| 32 | 
            +
            #
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            module SP
         | 
| 35 | 
            +
              module Job
         | 
| 36 | 
            +
                class MailQueue
         | 
| 37 | 
            +
                  extend SP::Job::Common
         | 
| 38 | 
            +
                  include Backburner::Queue
         | 
| 39 | 
            +
                  queue 'mail-queue'
         | 
| 40 | 
            +
                  queue_respond_timeout 30
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def self.perform (job)
         | 
| 43 | 
            +
                    email = synchronous_send_email(
         | 
| 44 | 
            +
                      body:        job[:body],
         | 
| 45 | 
            +
                      template:    job[:template],
         | 
| 46 | 
            +
                      to:          job[:to],
         | 
| 47 | 
            +
                      reply_to:    job[:reply_to],
         | 
| 48 | 
            +
                      subject:     job[:subject],
         | 
| 49 | 
            +
                      attachments: job[:attachments]
         | 
| 50 | 
            +
                    )
         | 
| 51 | 
            +
                    logger.info "mailto: #{job[:to]} - #{job[:subject]}"
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def self.on_failure (e, job)
         | 
| 55 | 
            +
                    logger.info "Mail to #{job[:to]} failed"
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                end # MailQueue
         | 
| 59 | 
            +
              end # Job
         | 
| 60 | 
            +
            end # SP
         | 
    
        data/lib/sp/job/pg_connection.rb
    CHANGED
    
    | @@ -27,20 +27,6 @@ module SP | |
| 27 27 |  | 
| 28 28 | 
             
                class PGConnection
         | 
| 29 29 |  | 
| 30 | 
            -
                  private
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  #
         | 
| 33 | 
            -
                  # Private Data
         | 
| 34 | 
            -
                  #
         | 
| 35 | 
            -
                  @owner      = nil
         | 
| 36 | 
            -
                  @config     = nil
         | 
| 37 | 
            -
                  @connection = nil
         | 
| 38 | 
            -
                  @treshold   = -1
         | 
| 39 | 
            -
                  @counter    = 0
         | 
| 40 | 
            -
                  @statements = []
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  public
         | 
| 43 | 
            -
             | 
| 44 30 | 
             
                  #
         | 
| 45 31 | 
             
                  # Public Attributes
         | 
| 46 32 | 
             
                  #
         | 
| @@ -52,13 +38,14 @@ module SP | |
| 52 38 | 
             
                  # @param owner
         | 
| 53 39 | 
             
                  # @param config
         | 
| 54 40 | 
             
                  #
         | 
| 55 | 
            -
                  def initialize (owner:, config:)
         | 
| 41 | 
            +
                  def initialize (owner:, config:, multithreaded: false)
         | 
| 42 | 
            +
                    @mutex      = multithreaded ? Mutex.new : ::SP::Job::FauxMutex.new
         | 
| 56 43 | 
             
                    @owner      = owner
         | 
| 57 44 | 
             
                    @config     = config
         | 
| 58 | 
            -
                    @connection | 
| 45 | 
            +
                    @connection = nil
         | 
| 59 46 | 
             
                    @treshold   = -1
         | 
| 60 47 | 
             
                    @counter    = 0
         | 
| 61 | 
            -
                    @ | 
| 48 | 
            +
                    @id_cache   = {}
         | 
| 62 49 | 
             
                    min = @config[:min_queries_per_conn]
         | 
| 63 50 | 
             
                    max = @config[:max_queries_per_conn]
         | 
| 64 51 | 
             
                    if (!max.nil? && max > 0) || (!min.nil? && min > 0)
         | 
| @@ -66,7 +53,7 @@ module SP | |
| 66 53 | 
             
                      new_min, new_max = [min, max].minmax
         | 
| 67 54 | 
             
                      new_min = new_min if new_min <= 0
         | 
| 68 55 | 
             
                      if new_min + new_min > 0
         | 
| 69 | 
            -
                        @treshold = (new_min + ( | 
| 56 | 
            +
                        @treshold = (new_min + (new_max - new_min) * rand).to_i
         | 
| 70 57 | 
             
                      else
         | 
| 71 58 | 
             
                        @treshold = new_min.to_i
         | 
| 72 59 | 
             
                      end
         | 
| @@ -78,98 +65,103 @@ module SP | |
| 78 65 | 
             
                  #        Previous one ( if any ) will be closed first.
         | 
| 79 66 | 
             
                  #
         | 
| 80 67 | 
             
                  def connect ()
         | 
| 81 | 
            -
                     | 
| 82 | 
            -
             | 
| 68 | 
            +
                    @mutex.synchronize {
         | 
| 69 | 
            +
                      _disconnect()
         | 
| 70 | 
            +
                      @connection = PG.connect(@config[:conn_str])
         | 
| 71 | 
            +
                    }
         | 
| 83 72 | 
             
                  end
         | 
| 84 73 |  | 
| 85 74 | 
             
                  #
         | 
| 86 75 | 
             
                  # Close currenly open database connection.
         | 
| 87 76 | 
             
                  #
         | 
| 88 77 | 
             
                  def disconnect ()
         | 
| 89 | 
            -
                     | 
| 90 | 
            -
                       | 
| 91 | 
            -
                     | 
| 92 | 
            -
                    while @statements.count > 0 do
         | 
| 93 | 
            -
                        @connection.exec("DEALLOCATE #{@statements.pop()}")
         | 
| 94 | 
            -
                    end
         | 
| 95 | 
            -
                    @connection.close
         | 
| 96 | 
            -
                    @connection = nil
         | 
| 97 | 
            -
                    @counter = 0
         | 
| 78 | 
            +
                    @mutex.synchronize {
         | 
| 79 | 
            +
                      _disconnect()
         | 
| 80 | 
            +
                    }
         | 
| 98 81 | 
             
                  end
         | 
| 99 82 |  | 
| 100 83 | 
             
                  #
         | 
| 101 | 
            -
                  #  | 
| 84 | 
            +
                  # Execute a prepared SQL statement.
         | 
| 102 85 | 
             
                  #
         | 
| 103 | 
            -
                  # @param query
         | 
| 104 | 
            -
                  #
         | 
| 105 | 
            -
                  # @return  | 
| 86 | 
            +
                  # @param query the SQL query with data binding
         | 
| 87 | 
            +
                  # @param args all the args for the query
         | 
| 88 | 
            +
                  # @return query result.
         | 
| 106 89 | 
             
                  #
         | 
| 107 | 
            -
                  def  | 
| 108 | 
            -
                     | 
| 109 | 
            -
                       | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
                       | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
                       | 
| 118 | 
            -
             | 
| 90 | 
            +
                  def exec (query, *args)
         | 
| 91 | 
            +
                    @mutex.synchronize {
         | 
| 92 | 
            +
                      if nil == @connection
         | 
| 93 | 
            +
                        _connect()
         | 
| 94 | 
            +
                      end
         | 
| 95 | 
            +
                      _check_life_span()
         | 
| 96 | 
            +
                      unless @id_cache.has_key? query
         | 
| 97 | 
            +
                        id = "p#{Digest::MD5.hexdigest(query)}"
         | 
| 98 | 
            +
                        @connection.prepare(id, query)
         | 
| 99 | 
            +
                        @id_cache[query] = id
         | 
| 100 | 
            +
                      else
         | 
| 101 | 
            +
                        id = @id_cache[query]
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                      @connection.exec_prepared(id, args)
         | 
| 104 | 
            +
                    }
         | 
| 119 105 | 
             
                  end
         | 
| 120 106 |  | 
| 121 107 | 
             
                  #
         | 
| 122 | 
            -
                  # Execute a  | 
| 123 | 
            -
                  #
         | 
| 124 | 
            -
                  # @param id
         | 
| 125 | 
            -
                  # @param args
         | 
| 108 | 
            +
                  # Execute a query,
         | 
| 126 109 | 
             
                  #
         | 
| 127 | 
            -
                  # @ | 
| 110 | 
            +
                  # @param query
         | 
| 128 111 | 
             
                  #
         | 
| 129 | 
            -
                  def  | 
| 130 | 
            -
                     | 
| 131 | 
            -
             | 
| 112 | 
            +
                  def query (query:)
         | 
| 113 | 
            +
                    @mutex.synchronize {
         | 
| 114 | 
            +
                      unless query.nil?
         | 
| 115 | 
            +
                        _check_life_span()
         | 
| 116 | 
            +
                        @connection.exec(query)
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
                    }
         | 
| 132 119 | 
             
                  end
         | 
| 133 120 |  | 
| 134 121 | 
             
                  #
         | 
| 135 | 
            -
                  #  | 
| 122 | 
            +
                  # Call this to check if the database is not a production database where it's
         | 
| 123 | 
            +
                  # dangerous to make development stuff. It checks the presence of a magic parameter
         | 
| 124 | 
            +
                  # on the PG configuration that marks the database as a development arena
         | 
| 136 125 | 
             
                  #
         | 
| 137 | 
            -
                   | 
| 138 | 
            -
             | 
| 139 | 
            -
                  #
         | 
| 140 | 
            -
                  def dealloc_statement (id:)
         | 
| 141 | 
            -
                    if nil == id
         | 
| 142 | 
            -
                      while @statements.count > 0 do
         | 
| 143 | 
            -
                          @connection.exec("DEALLOCATE #{@statements.pop()}")
         | 
| 144 | 
            -
                      end
         | 
| 145 | 
            -
                    else
         | 
| 146 | 
            -
                      @statements.delete!(id)
         | 
| 147 | 
            -
                      @connection.exec("DEALLOCATE #{id}")
         | 
| 148 | 
            -
                    end
         | 
| 126 | 
            +
                  def safety_check ()
         | 
| 127 | 
            +
                    SP::Duh::Db::safety_check(@connection)
         | 
| 149 128 | 
             
                  end
         | 
| 150 129 |  | 
| 151 130 | 
             
                  #
         | 
| 152 | 
            -
                  #  | 
| 153 | 
            -
                  #
         | 
| 154 | 
            -
                  # @param query
         | 
| 131 | 
            +
                  # Returns the configured connection string
         | 
| 155 132 | 
             
                  #
         | 
| 156 | 
            -
                  def  | 
| 157 | 
            -
                     | 
| 158 | 
            -
                      check_life_span()
         | 
| 159 | 
            -
                      @connection.exec(query)
         | 
| 160 | 
            -
                    end
         | 
| 133 | 
            +
                  def conn_str
         | 
| 134 | 
            +
                    @config[:conn_str]
         | 
| 161 135 | 
             
                  end
         | 
| 162 136 |  | 
| 163 137 | 
             
                  private
         | 
| 164 138 |  | 
| 139 | 
            +
                  def _connect () 
         | 
| 140 | 
            +
                    _disconnect()
         | 
| 141 | 
            +
                    @connection = PG.connect(@config[:conn_str])
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  def _disconnect ()
         | 
| 145 | 
            +
                    if @connection.nil?
         | 
| 146 | 
            +
                      return
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                    @connection.exec("DEALLOCATE ALL")
         | 
| 150 | 
            +
                    @id_cache = {}
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    @connection.close
         | 
| 153 | 
            +
                    @connection = nil
         | 
| 154 | 
            +
                    @counter = 0
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 165 157 | 
             
                  #
         | 
| 166 158 | 
             
                  # Check connection life span
         | 
| 167 159 | 
             
                  #
         | 
| 168 | 
            -
                  def  | 
| 160 | 
            +
                  def _check_life_span ()
         | 
| 169 161 | 
             
                    return unless @treshold > 0
         | 
| 170 162 | 
             
                    @counter += 1
         | 
| 171 163 | 
             
                    if @counter > @treshold
         | 
| 172 | 
            -
                       | 
| 164 | 
            +
                      _connect()
         | 
| 173 165 | 
             
                    end
         | 
| 174 166 | 
             
                  end
         | 
| 175 167 |  | 
| @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file is part of sp-job.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # And this is the mix-in we'll apply to Job execution classes
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            # sp-job is free software: you can redistribute it and/or modify
         | 
| 9 | 
            +
            # it under the terms of the GNU Affero General Public License as published by
         | 
| 10 | 
            +
            # the Free Software Foundation, either version 3 of the License, or
         | 
| 11 | 
            +
            # (at your option) any later version.
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # sp-job is distributed in the hope that it will be useful,
         | 
| 14 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 15 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 16 | 
            +
            # GNU General Public License for more details.
         | 
| 17 | 
            +
            #
         | 
| 18 | 
            +
            # You should have received a copy of the GNU Affero General Public License
         | 
| 19 | 
            +
            # along with sp-job.  If not, see <http://www.gnu.org/licenses/>.
         | 
| 20 | 
            +
            #
         | 
| 21 | 
            +
            # encoding: utf-8
         | 
| 22 | 
            +
            #
         | 
| 23 | 
            +
            require 'ffi'
         | 
| 24 | 
            +
            require 'os'
         | 
| 25 | 
            +
            require 'fileutils'
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            module SP
         | 
| 28 | 
            +
              module Job
         | 
| 29 | 
            +
                module Unique
         | 
| 30 | 
            +
                  module File
         | 
| 31 | 
            +
                    extend FFI::Library
         | 
| 32 | 
            +
                    ffi_lib 'c'
         | 
| 33 | 
            +
                    attach_function :puts, [ :string ], :int
         | 
| 34 | 
            +
                    attach_function :mkstemps, [:string, :int], :int
         | 
| 35 | 
            +
                    attach_function :fcntl, [:int, :int, :pointer], :int
         | 
| 36 | 
            +
                    attach_function :close, [ :int ], :int
         | 
| 37 | 
            +
                    unless OS.mac?
         | 
| 38 | 
            +
                      attach_function :readlink, [ :string, :pointer, :int ], :int
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    #
         | 
| 42 | 
            +
                    # Creates a uniquely named file inside the specified folder. The created file is empty
         | 
| 43 | 
            +
                    #
         | 
| 44 | 
            +
                    # @param a_folder Folder where the file will be created
         | 
| 45 | 
            +
                    # @param a_suffix file suffix, warning must include the .
         | 
| 46 | 
            +
                    # @return Absolute file path
         | 
| 47 | 
            +
                    #
         | 
| 48 | 
            +
                    def self.create (a_folder, a_extension)
         | 
| 49 | 
            +
                      FileUtils.mkdir_p(a_folder) if !Dir.exist?(a_folder)
         | 
| 50 | 
            +
                      fd = ::SP::Job::Unique::File.mkstemps("#{a_folder}/XXXXXX#{a_extension}", a_extension.length)
         | 
| 51 | 
            +
                      return nil if fd < 0
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                      ptr = FFI::MemoryPointer.new(:char, 8192)     # Assumes max path is less that this
         | 
| 54 | 
            +
                      if OS.mac?
         | 
| 55 | 
            +
                        r = ::SP::Job::Unique::File.fcntl(fd, 50, ptr) # 50 is F_GETPATH in OSX
         | 
| 56 | 
            +
                      else
         | 
| 57 | 
            +
                        r = ::SP::Job::Unique::File.readlink("/proc/self/fd/#{fd}", ptr, 8192)
         | 
| 58 | 
            +
                        if r > 0 && r < 8192
         | 
| 59 | 
            +
                          r = 0
         | 
| 60 | 
            +
                        end
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
                      ::SP::Job::Unique::File.close(fd)
         | 
| 63 | 
            +
                      return nil if r != 0
         | 
| 64 | 
            +
                      return nil if ptr.null?
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                      rv = ptr.read_string
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      return rv
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| @@ -28,9 +28,7 @@ | |
| 28 28 | 
             
            # require 'sp/job/back_burner'
         | 
| 29 29 | 
             
            # require 'sp/job/uploaded_image_converter'
         | 
| 30 30 | 
             
            # 
         | 
| 31 | 
            -
            # class CLASSNAME
         | 
| 32 | 
            -
            #   extend SP::Job::Common
         | 
| 33 | 
            -
            #   extend SP::Job::UploadedImageConverter
         | 
| 31 | 
            +
            # class CLASSNAME < ::SP::Job::UploadedImageConverter
         | 
| 34 32 | 
             
            # 
         | 
| 35 33 | 
             
            #   def self.perform(job)
         | 
| 36 34 | 
             
            # 
         | 
| @@ -48,23 +46,40 @@ | |
| 48 46 |  | 
| 49 47 | 
             
            module SP
         | 
| 50 48 | 
             
              module Job
         | 
| 51 | 
            -
                 | 
| 49 | 
            +
                class UploadedImageConverter
         | 
| 52 50 | 
             
                  extend SP::Job::Common
         | 
| 53 51 |  | 
| 54 52 | 
             
                  def self.perform (job)
         | 
| 55 53 |  | 
| 56 | 
            -
                     | 
| 54 | 
            +
                    raise_error(message: 'i18n_entity_id_must_be_defined') if job[:entity_id].nil? || job[:entity_id].to_i == 0
         | 
| 57 55 |  | 
| 58 56 | 
             
                    step        = 100 / (job[:copies].size + 1)
         | 
| 59 | 
            -
                     | 
| 60 | 
            -
                     | 
| 57 | 
            +
                    progress    = step
         | 
| 58 | 
            +
                    original    = File.join(config[:paths][:temporary_uploads], job[:original])
         | 
| 59 | 
            +
                    destination = File.join(config[:paths][:uploads_storage], job[:entity], id_to_path(job[:entity_id]), job[:folder])
         | 
| 61 60 |  | 
| 62 61 | 
             
                    #
         | 
| 63 | 
            -
                    #  | 
| 62 | 
            +
                    # Check the original image, check format and limits
         | 
| 64 63 | 
             
                    # 
         | 
| 65 64 | 
             
                    FileUtils::mkdir_p destination
         | 
| 66 | 
            -
                    image  | 
| 67 | 
            -
                     | 
| 65 | 
            +
                    update_progress(progress: progress, message: 'i18n_reading_original_$image', image: job[:original])
         | 
| 66 | 
            +
                    img_info = %x[identify #{original}]
         | 
| 67 | 
            +
                    m = %r[.*\.ul\s(\w+)\s(\d+)x(\d+)\s.*].match img_info
         | 
| 68 | 
            +
                    if $?.success? == false
         | 
| 69 | 
            +
                      return report_error(message: 'i18n_invalid_image', info: "Image #{original} can't be identified '#{img_info}'")
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                    if m.nil? || m.size != 4 
         | 
| 72 | 
            +
                      return report_error(message: 'i18n_invalid_image', info: "Image #{original} can't be identified '#{img_info}'")
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                    unless config[:options][:formats].include? m[1]
         | 
| 75 | 
            +
                      return report_error(message: 'i18n_unsupported_$format', format: m[1])
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                    if m[2].to_i > config[:options][:max_width]
         | 
| 78 | 
            +
                      return report_error(message: 'i18n_image_too_wide_$width$max_width', width: m[2], max_width:  config[:options][:max_width])
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                    if m[3].to_i > config[:options][:max_height]
         | 
| 81 | 
            +
                      return report_error(message: 'i18n_image_too_tall_$height$max_height', height: m[3], max_height: config[:options][:max_height])
         | 
| 82 | 
            +
                    end
         | 
| 68 83 |  | 
| 69 84 | 
             
                    barrier = true # To force progress on first scalling
         | 
| 70 85 |  | 
| @@ -72,18 +87,22 @@ module SP | |
| 72 87 | 
             
                    # Iterate the copies array
         | 
| 73 88 | 
             
                    #
         | 
| 74 89 | 
             
                    job[:copies].each do |copy|
         | 
| 75 | 
            -
                       | 
| 76 | 
            -
                       | 
| 77 | 
            -
                         | 
| 90 | 
            +
                      %x[convert #{original} -geometry #{copy[:geometry]} #{File.join(destination, copy[:name])}]
         | 
| 91 | 
            +
                      unless $?.success?
         | 
| 92 | 
            +
                        raise_error(message: 'i18n_internal_error', info: "convert failed to scale #{original} to #{copy[:geometry]}")
         | 
| 78 93 | 
             
                      end
         | 
| 79 | 
            -
                       | 
| 80 | 
            -
                      update_progress( | 
| 94 | 
            +
                      progress += step
         | 
| 95 | 
            +
                      update_progress(progress: progress, message: 'i18n_scalling_image_$name$geometry', name: copy[:name], geometry: copy[:geometry], barrier: barrier)
         | 
| 81 96 | 
             
                      logger.debug("Scaled to geometry #{copy[:geometry]}")
         | 
| 82 97 | 
             
                      barrier = false
         | 
| 83 98 | 
             
                    end
         | 
| 84 99 |  | 
| 85 100 | 
             
                    # Closing arguments, all done
         | 
| 86 | 
            -
                     | 
| 101 | 
            +
                    send_response(message: 'i18n_image_conversion_complete', link: File.join('/',job[:entity], id_to_path(job[:entity_id]), job[:folder], 'logo_template.png'))
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    # Remove original file
         | 
| 104 | 
            +
                    FileUtils::rm_f(original) if config[:options][:delete_originals] 
         | 
| 105 | 
            +
             | 
| 87 106 | 
             
                  end
         | 
| 88 107 |  | 
| 89 108 | 
             
                end # UploadedImageConverter
         |