sp-job 0.1.17 → 0.2.2

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