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.
@@ -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
- # Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
2
+ # Helper to obtain tokens to access toconline API's.
3
3
  #
4
- # This file is part of sp-job.
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
- class Engine < ::Rails::Engine
25
- isolate_namespace SP::Job
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
- initializer :append_migrations do |app|
28
- unless app.root.to_s.match root.to_s
29
- app.config.paths["db/migrate"] += config.paths["db/migrate"].expanded
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
@@ -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 = nil
45
+ @connection = nil
59
46
  @treshold = -1
60
47
  @counter = 0
61
- @statements = []
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 + (new_min - new_min) * rand).to_i
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
- disconnect()
82
- @connection = PG.connect(@config[:conn_str])
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
- if @connection.nil?
90
- return
91
- end
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
- # Prepare an SQL statement.
84
+ # Execute a prepared SQL statement.
102
85
  #
103
- # @param query
104
- #
105
- # @return Statement id.
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 prepare_statement (query:)
108
- if nil == @connection
109
- connect()
110
- end
111
- id = "#{@owner}_#{Digest::MD5.hexdigest(query)}"
112
- if @statements.include? id
113
- return id
114
- else
115
- @statements << id
116
- @connection.prepare(@statements.last, query)
117
- return @statements.last
118
- end
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 previously prepared SQL statement.
123
- #
124
- # @param id
125
- # @param args
108
+ # Execute a query,
126
109
  #
127
- # @return PG result
110
+ # @param query
128
111
  #
129
- def execute_statement (id:, args:)
130
- check_life_span()
131
- @connection.exec_prepared(id, args)
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
- # Destroy a previously prepared SQL statement.
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
- # @param id
138
- # @param args
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
- # Execute a query,
153
- #
154
- # @param query
131
+ # Returns the configured connection string
155
132
  #
156
- def query (query:)
157
- unless query.nil?
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 check_life_span ()
160
+ def _check_life_span ()
169
161
  return unless @treshold > 0
170
162
  @counter += 1
171
163
  if @counter > @treshold
172
- connect()
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
- module UploadedImageConverter
49
+ class UploadedImageConverter
52
50
  extend SP::Job::Common
53
51
 
54
52
  def self.perform (job)
55
53
 
56
- throw Exception.new("i18n_entity_id_must_be_defined") if job[:entity_id].nil? || job[:entity_id].to_i == 0
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
- original = File.join($config[:paths][:temporary_uploads], job[:original])
60
- destination = File.join($config[:paths][:uploads_storage], job[:entity], id_to_path(job[:entity_id]), job[:folder])
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
- # Read the original image, any format that image magic can handle will be ok
62
+ # Check the original image, check format and limits
64
63
  #
65
64
  FileUtils::mkdir_p destination
66
- image = Magick::Image.read(original).first
67
- update_progress(step: step, message: 'i18n_reading_original_$image', image: job[:original])
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
- img_copy = image.copy()
76
- img_copy.change_geometry(copy[:geometry].to_s) do |cols, rows, img|
77
- img.resize!(cols, rows)
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
- img_copy.write(File.join(destination, copy[:name]))
80
- update_progress(step: step, message: 'i18n_scalling_image_$name$geometry', name: copy[:name], geometry: copy[:geometry], barrier: barrier)
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
- update_progress(status: 'completed', message: 'i18n_image_conversion_complete', link: File.join('/',job[:entity], id_to_path(job[:entity_id]), job[:folder], 'logo_template.png'))
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