waxx 0.1.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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE +201 -0
  4. data/README.md +879 -0
  5. data/bin/waxx +120 -0
  6. data/lib/waxx/app.rb +173 -0
  7. data/lib/waxx/conf.rb +54 -0
  8. data/lib/waxx/console.rb +204 -0
  9. data/lib/waxx/csrf.rb +14 -0
  10. data/lib/waxx/database.rb +80 -0
  11. data/lib/waxx/encrypt.rb +38 -0
  12. data/lib/waxx/error.rb +60 -0
  13. data/lib/waxx/html.rb +33 -0
  14. data/lib/waxx/http.rb +268 -0
  15. data/lib/waxx/init.rb +273 -0
  16. data/lib/waxx/irb.rb +44 -0
  17. data/lib/waxx/irb_env.rb +18 -0
  18. data/lib/waxx/json.rb +23 -0
  19. data/lib/waxx/mongodb.rb +221 -0
  20. data/lib/waxx/mysql2.rb +234 -0
  21. data/lib/waxx/object.rb +115 -0
  22. data/lib/waxx/patch.rb +138 -0
  23. data/lib/waxx/pdf.rb +69 -0
  24. data/lib/waxx/pg.rb +246 -0
  25. data/lib/waxx/process.rb +270 -0
  26. data/lib/waxx/req.rb +116 -0
  27. data/lib/waxx/res.rb +98 -0
  28. data/lib/waxx/server.rb +304 -0
  29. data/lib/waxx/sqlite3.rb +237 -0
  30. data/lib/waxx/supervisor.rb +47 -0
  31. data/lib/waxx/test.rb +162 -0
  32. data/lib/waxx/util.rb +57 -0
  33. data/lib/waxx/version.rb +3 -0
  34. data/lib/waxx/view.rb +389 -0
  35. data/lib/waxx/waxx.rb +73 -0
  36. data/lib/waxx/x.rb +103 -0
  37. data/lib/waxx.rb +50 -0
  38. data/skel/README.md +11 -0
  39. data/skel/app/app/app.rb +39 -0
  40. data/skel/app/app/error/app_error.rb +16 -0
  41. data/skel/app/app/error/dhtml.rb +9 -0
  42. data/skel/app/app/error/html.rb +8 -0
  43. data/skel/app/app/error/json.rb +8 -0
  44. data/skel/app/app/error/pdf.rb +13 -0
  45. data/skel/app/app/log/app_log.rb +13 -0
  46. data/skel/app/app.rb +20 -0
  47. data/skel/app/home/home.rb +16 -0
  48. data/skel/app/home/html.rb +145 -0
  49. data/skel/app/html.rb +192 -0
  50. data/skel/app/usr/email.rb +66 -0
  51. data/skel/app/usr/html.rb +115 -0
  52. data/skel/app/usr/list.rb +51 -0
  53. data/skel/app/usr/password.rb +54 -0
  54. data/skel/app/usr/record.rb +98 -0
  55. data/skel/app/usr/usr.js +67 -0
  56. data/skel/app/usr/usr.rb +277 -0
  57. data/skel/app/waxx/waxx.rb +109 -0
  58. data/skel/bin/README.md +1 -0
  59. data/skel/db/README.md +11 -0
  60. data/skel/db/app/0-init.sql +88 -0
  61. data/skel/lib/README.md +1 -0
  62. data/skel/log/README.md +1 -0
  63. data/skel/opt/dev/config.yaml +1 -0
  64. data/skel/opt/prod/config.yaml +1 -0
  65. data/skel/opt/stage/config.yaml +1 -0
  66. data/skel/opt/test/config.yaml +1 -0
  67. data/skel/private/README.md +1 -0
  68. data/skel/public/lib/site.css +202 -0
  69. data/skel/public/lib/waxx/w.ico +0 -0
  70. data/skel/public/lib/waxx/w.png +0 -0
  71. data/skel/public/lib/waxx/waxx.js +111 -0
  72. data/skel/tmp/pids/README.md +1 -0
  73. data.tar.gz.sig +0 -0
  74. metadata +140 -0
  75. metadata.gz.sig +3 -0
@@ -0,0 +1,304 @@
1
+ # Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
2
+ # Released under the Apache Version 2 License. See LICENSE.txt.
3
+
4
+ ##
5
+ # This is the core of waxx.
6
+ # Process:
7
+ # start: A TCP server is setup listening on Waxx['server']['host'] on port Waxx['server']['port']
8
+ # setup_threads: The thread pool is created with dedicated database connection(s)
9
+ # loop: The requests are appended to the queue and the threads take one and:
10
+ # process_request: The request is parsed (query, string, multipart, etc). The "x" vairable is defined
11
+ # run_app: The proc/lambda is called with the x variable and any other optional variables
12
+ # finish: The response is sent to the client, background jobs are processed, the thread takes the next request
13
+ module Waxx::Server
14
+ extend self
15
+
16
+ attr :last_load_time
17
+ attr :queue
18
+
19
+ def parse_uri(r)
20
+ Waxx.debug "parse_uri"
21
+ meth, uri, ver = r.split(" ")
22
+ path, params = uri.split("?", 2)
23
+ _, app, act, arg = path.split(".").first.split("/", 4)
24
+ app = Waxx['default']['app'] if app.to_s == ''
25
+ act = App[app.to_sym][:default] if act.to_s == '' and App[app.to_sym]
26
+ act = Waxx['default']['act'] if act.to_s == ''
27
+ oid = arg.split("/").first.gsub(/[^0-9]/,"").to_i rescue 0
28
+ ext = path =~ /\./ ? path.split(".").last.downcase : Waxx['default']['ext']
29
+ args = arg.split("/") rescue []
30
+ get = Waxx::Http.query_string_to_hash(params).freeze
31
+ Waxx.debug "parse_uri.oid #{oid}"
32
+ [meth, uri, app, act, oid, args, ext, get]
33
+ end
34
+
35
+ def default_cookies
36
+ {
37
+ "#{Waxx['cookie']['user']['name']}" => {uk: Waxx.random_string(20), la: Time.now.to_i},
38
+ "#{Waxx['cookie']['agent']['name']}" => {la: Time.now.to_i}
39
+ }
40
+ end
41
+
42
+ def csrf?(x)
43
+ Waxx.debug "csrf?"
44
+ if %w(PATCH POST PUT DELETE).include? x.req.meth
45
+ if not Waxx::Csrf.ok?(x)
46
+ true
47
+ end
48
+ puts "Cleared CSRF"
49
+ end
50
+ false
51
+ end
52
+
53
+ def default_response_headers(req, ext)
54
+ {
55
+ "Content-Type" => (Waxx::Http.content_types()[ext.to_sym] || "text/html; charset=utf-8"),
56
+ "App-Server" => "waxx/1.0"
57
+ }
58
+ end
59
+
60
+ def serve_file(io, uri)
61
+ return false if Waxx['file'].nil?
62
+ file = "#{Waxx/:opts/:base}/#{Waxx/:file/:path}#{uri.gsub("..","").split("?").first}"
63
+ file = file + "/index.html" if File.directory?(file)
64
+ return false unless File.exist? file
65
+ ext = file.split(".").last
66
+ io.print([
67
+ "HTTP/1.1 200 OK",
68
+ "Content-Type: #{(Waxx::Http.content_types/ext || 'octet-stream')}",
69
+ "Content-Length: #{File.size(file)}",
70
+ "",
71
+ File.open(file,"rb") {|fh| fh.read}
72
+ ].join("\r\n"))
73
+ io.close
74
+ ::Thread.current[:status] = "idle"
75
+ true
76
+ end
77
+
78
+ def process_request(io, db)
79
+ begin
80
+ Waxx.debug "process request", 4
81
+ ::Thread.current[:status] = "working"
82
+ start_time = Time.new
83
+ r = io.gets
84
+ #Waxx.debug r, 9
85
+ meth, uri, app, act, oid, args, ext, get = parse_uri(r)
86
+ #Waxx.debug([meth, uri, app, act, oid, args, ext, get].join(" "), 8)
87
+ if meth == "GET" and Waxx['file'] and Waxx['file']['serve']
88
+ #Waxx.debug "server_file"
89
+ return if serve_file(io, uri)
90
+ end
91
+ #Waxx.debug "no-file"
92
+ env, head = Waxx::Http.parse_head(io)
93
+ #Waxx.debug head, 9
94
+ cookie = Waxx::Http.parse_cookie(env['Cookie'])
95
+ begin
96
+ usr = cookie[Waxx['cookie']['user']['name']] ? JSON.parse(::App.decrypt(cookie[Waxx['cookie']['user']['name']][0])) : default_cookies[Waxx['cookie']['user']['name']]
97
+ rescue => e
98
+ Waxx.debug e.to_s, 1
99
+ usr = default_cookies[Waxx['cookie']['user']['name']]
100
+ end
101
+ begin
102
+ ua = cookie[Waxx['cookie']['agent']['name']] ? JSON.parse(::App.decrypt(cookie[Waxx['cookie']['agent']['name']][0])) : {}
103
+ rescue => e
104
+ Waxx.debug e.to_s, 1
105
+ ua = {}
106
+ end
107
+ post, data = Waxx::Http.parse_data(env, meth, io, head)
108
+ req = Waxx::Req.new(env, data, meth, uri, get, post, cookie, start_time).freeze
109
+ res = Waxx::Res.new(io, 200, default_response_headers(req, ext), [], [], [])
110
+ jobs = []
111
+ x = Waxx::X.new(req, res, usr, ua, db, meth.downcase.to_sym, app, act, oid, args, ext, jobs).freeze
112
+ if csrf?(x)
113
+ Waxx::App.csrf_failure(x)
114
+ finish(x, io)
115
+ return
116
+ end
117
+ run_app(x)
118
+ finish(x, io)
119
+ rescue => e
120
+ fatal_error(x, e)
121
+ finish(x, io)
122
+ end
123
+ end
124
+
125
+ def run_app(x)
126
+ Waxx.debug "run_request"
127
+ app = x.app.to_sym
128
+ if App[app]
129
+ act = App[app][x.act.to_sym] ? x.act.to_sym : App[app][x.act] ? x.act : :not_found
130
+ if App[app][act]
131
+ if App.access?(x, acl:App[app][act][:acl])
132
+ return App.run(x, app, act, x.meth.to_sym, x.args)
133
+ else
134
+ return App.login_needed(x)
135
+ end
136
+ end
137
+ end
138
+ App.not_found(x)
139
+ end
140
+
141
+ def set_cookies(x)
142
+ return if x.usr/:no_cookies
143
+ x.res.cookie(
144
+ name: Waxx['cookie']['user']['name'],
145
+ value: App.encrypt(x.usr.to_json),
146
+ secure: Waxx['cookie']['user']['secure']
147
+ )
148
+ x.res.cookie(
149
+ name: Waxx['cookie']['agent']['name'],
150
+ value: App.encrypt(x.ua.to_json),
151
+ expires: Time.now + (Waxx['cookie']['agent']['expires_years'].to_i * 31536000),
152
+ secure: Waxx['cookie']['agent']['secure']
153
+ )
154
+ end
155
+
156
+ def finish(x, io)
157
+ # Set last activity
158
+ x.usr['la'] = Time.new.to_i
159
+ set_cookies(x)
160
+ Waxx.debug "Process time: #{(Time.new - x.req.start_time)*1000} ms."
161
+ x.res.complete
162
+ io.close
163
+ x.jobs.each{|job|
164
+ job[0].call(*(job.slice(1, job.length)))
165
+ }
166
+ ::Thread.current[:status] = "idle"
167
+ ::Thread.current[:last_used] = Time.new.to_i
168
+ x # Return x for the console interfaces
169
+ end
170
+
171
+ def fatal_error(x, e)
172
+ x.res.status = 503
173
+ puts "FATAL ERROR: #{e}\n#{e.backtrace}"
174
+ report = "APPLICATION ERROR\n=================\n\nUSR:\n\n#{x.usr.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\nERROR:\n\n#{e}\n#{e.backtrace.join("\n")}\n\nENV:\n\n#{x.req.env.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\nGET:\n\n#{x.req.get.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\nPOST:\n\n#{x.req.post.map{|n,v| "#{n}: #{v}"}.join("\n")}\n\n"
175
+ if Waxx['debug']['on_screen']
176
+ x << "<pre>#{report.h}</pre>"
177
+ else
178
+ App::Html.page(x,
179
+ title: "System Error",
180
+ content: "<h4><span class='glyphicon glyphicon-thumbs-down'></span> Sorry! Something went wrong on our end.</h4>
181
+ <h4><span class='glyphicon glyphicon-thumbs-up'></span> The tech support team has been notified.</h4>
182
+ <p>We will contact you if we need addition information. </p>
183
+ <p>Sorry for the inconvenience.</p>"
184
+ )
185
+ end
186
+ if Waxx['debug']['send_email'] and Waxx['debug']['email']
187
+ begin
188
+ to_email = Waxx['debug']['email']
189
+ from_email = Waxx['site']['support_email']
190
+ subject = "[Bug] #{Waxx['site']['name']} #{x.meth}:#{x.req.uri}"
191
+ # Send email via DB
192
+ App::Email.post(x, d:{
193
+ to_email: to_email,
194
+ from_email: from_email,
195
+ subject: subject,
196
+ body_text: report
197
+ })
198
+ rescue => e2
199
+ begin
200
+ # Send email directly
201
+ Mail.deliver do
202
+ from from_email
203
+ to to_email
204
+ subject subject
205
+ body report
206
+ end
207
+ rescue => e3
208
+ puts "FATAL ERROR: Could not send bug report email: #{e2}\n#{e2.backtrace} AND #{e3}\n#{e3.backtrace}"
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ # Require first level apps in the Waxx::Root/app directory
215
+ def require_apps
216
+ Dir["#{Waxx["opts"][:base]}/app/*"].each{|f|
217
+ next if f =~ /\/app\.rb$/ # Don't reinclude app.rb
218
+ require f if f =~ /\.rb$/ # Load files in the app directory
219
+ if File.directory? f # Load top-level apps
220
+ name = f.split("/").last
221
+ require "#{f}/#{name}" if File.exist? "#{f}/#{name}.rb"
222
+ end
223
+ }
224
+ end
225
+
226
+ def reload
227
+ reload_code
228
+ end
229
+
230
+ def reload_code
231
+ require_apps
232
+ $LOADED_FEATURES.each{|f|
233
+ if f =~ /^\//
234
+ if not f =~ /^\/usr\/local/
235
+ if File.ctime(f) > @@last_load_time
236
+ load(f)
237
+ end
238
+ end
239
+ end
240
+ }
241
+ @@last_load_time = Time.new
242
+ end
243
+
244
+ def create_thread(id)
245
+ Thread.new do
246
+ Thread.current[:name]="waxx-#{Time.new.to_i}-#{id}"
247
+ Thread.current[:status]="idle"
248
+ Thread.current[:db] = Waxx['databases'].nil? ? {} : Waxx::Database.connections(Waxx['databases'])
249
+ Thread.current[:last_used] = Time.new.to_i
250
+ Waxx.debug "Create thread #{Thread.current[:name]}"
251
+ loop do
252
+ Waxx::Server.process_request(@@queue.pop, Thread.current[:db])
253
+ end
254
+ end
255
+ end
256
+
257
+ def setup_threads
258
+ Waxx.debug "setup_threads"
259
+ Thread.current[:name]="main"
260
+ @@queue = Queue.new
261
+ thread_count = Waxx['server']['min_threads'] || Waxx['server']['threads'] || 4
262
+ 1.upto(thread_count).each do |i|
263
+ create_thread(i)
264
+ end
265
+ thread_count
266
+ end
267
+
268
+ def restart
269
+ stop
270
+ sleep(1)
271
+ start
272
+ end
273
+
274
+ def start(options={})
275
+ @@last_load_time = Time.new
276
+ Waxx.debug "start #{$$}"
277
+ thread_count = setup_threads
278
+ server = TCPServer.new(Waxx['server']['host'], Waxx['server']['port'])
279
+ puts "Listening on #{server.addr} with #{thread_count} threads (max_threads: #{Waxx['server']['max_threads'] || Waxx['server']['threads']})"
280
+ while s = server.accept
281
+ Waxx.debug "server.accept", 7
282
+ reload_code if Waxx['debug']['auto_reload_code']
283
+ @@queue << s
284
+ Waxx.debug "q.size: #{@@queue.size}", 7
285
+ Waxx.debug "q.waiting: #{@@queue.num_waiting}", 7
286
+ # Check threads
287
+ Waxx::Supervisor.check
288
+ end
289
+ Waxx.debug "end start", 9
290
+ end
291
+
292
+ def stop(opts={})
293
+ Waxx.debug "stop #{$$}"
294
+ puts "Stopping #{Thread.list.size - 1} worker threads..."
295
+ Thread.list.each{|t|
296
+ next if t[:name] == "main"
297
+ puts "Killing #{t[:name]} with status #{t[:status]}"
298
+ t[:db].close
299
+ t.exit
300
+ }
301
+ puts "Done"
302
+ end
303
+ end
304
+
@@ -0,0 +1,237 @@
1
+ # Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
2
+ # Released under the Apache Version 2 License. See LICENSE.txt.
3
+
4
+ ##
5
+ # The SQLite Object methods
6
+ module Waxx::Sqlite3
7
+ extend Waxx::Object
8
+ extend self
9
+
10
+ attr :app
11
+ attr :db
12
+ attr :table
13
+ attr :columns
14
+ attr :pkey
15
+ attr :joins
16
+ attr :orders
17
+
18
+ ##
19
+ # Connect to an sqlite database
20
+ #
21
+ # Set in config.yaml:
22
+ # databases:
23
+ # blog: sqlite:///path/to/database.db
24
+ # Or call this with the path to the db file
25
+ def connect(str)
26
+ conn = SQLite3::Database.new( str )
27
+ conn.results_as_hash = true
28
+ conn
29
+ end
30
+
31
+ def init(app:nil, db:"app", table:nil, pk:"id", cols:nil)
32
+ @app ||= (app || App.table_from_class(name)).to_sym
33
+ @db ||= db.to_sym
34
+ @table ||= (table || App.table_from_class(name)).to_sym
35
+ @pkey ||= pk.to_sym
36
+ @columns ||= {}
37
+ @joins ||= {}
38
+ @orders ||= {}
39
+ has(cols) if cols
40
+ end
41
+
42
+ def has(opts=nil)
43
+ init if @table.nil?
44
+ return @columns if opts.nil?
45
+ @columns = opts
46
+ @columns.each{|n,v|
47
+ v[:table] = @table
48
+ v[:column] = n
49
+ v[:views] = []
50
+ v[:label] ||= Waxx::Util.label(n)
51
+ @orders[n] = v[:order] || n
52
+ @orders["_#{n}".to_sym] = v[:_order] || "#{n} DESC"
53
+ @pkey = n if v[:pkey]
54
+ if v[:is]
55
+ r, tc = v[:is].split(":")
56
+ t, c = tc.split(".")
57
+ @joins[r] = {join: "INNER", table: t, col: c}
58
+ end
59
+ if v[:has]
60
+ r, tc = v[:has].split(":")
61
+ t, c = tc.split(".")
62
+ @joins[r] = {join: "LEFT", table: t, col: c}
63
+ end
64
+ }
65
+ end
66
+
67
+ def [](n)
68
+ @columns[n.to_sym]
69
+ end
70
+
71
+ def /(n)
72
+ @columns[n.to_sym]
73
+ end
74
+
75
+ def get_cols(*args)
76
+ re = {}
77
+ args.flatten.map{|a| re[a] = @columns[a.to_sym]}
78
+ re
79
+ end
80
+
81
+ def runs(opts=nil)
82
+ init if @app.nil?
83
+ return App[@app] if opts.nil?
84
+ App[@app] = opts
85
+ end
86
+
87
+ def run(x, act, meth, *args)
88
+ App[@app][act.to_sym][meth.to_sym][x, *args]
89
+ end
90
+
91
+ def parse_select(select, view)
92
+ raise "Can not define both select and view in Waxx::Object.parse_select (#{name})." if select and view
93
+ return select || "*" if view.nil?
94
+ view.columns.map{|n,c|
95
+ raise "Column #{n} not defined in #{view}" if c.nil?
96
+ if c[:sql_select]
97
+ "#{c[:sql_select]} AS #{n}"
98
+ elsif n != c[:column]
99
+ "#{c[:table]}.#{c[:column]} AS #{n}"
100
+ else
101
+ "#{c[:table]}.#{c[:column]}"
102
+ end
103
+ }.join(", ")
104
+ end
105
+
106
+ def get(x, select:nil, id:nil, joins:nil, where:nil, having:nil, order:nil, limit:nil, offset:nil, view:nil, &blk)
107
+ Waxx.debug "object.get"
108
+ select = parse_select(select, view)
109
+ where = ["#{@table}.#{@pkey} = ?",id] if id and where.nil?
110
+ # Block SQL injection in order clause. All order options must be defined in @orders.
111
+ if order
112
+ if not @orders/order
113
+ Waxx.debug("ERROR: Object.get order (#{order}) not found in @orders [#{@orders.keys.join(", ")}]. Sorting by #{@pkey} instead.")
114
+ order = @pkey
115
+ else
116
+ order = @orders/order
117
+ end
118
+ end
119
+ if joins.nil? and view
120
+ joins = view.joins_to_sql
121
+ end
122
+ q = {select:select, joins:joins, where:where, having:having, order:order, limit:limit, offset:offset}
123
+ yield q if block_given?
124
+ Waxx.debug "object.get.select: #{q[:select]}"
125
+ return [] if q[:select].empty?
126
+ sql = []
127
+ sql << "SELECT #{q[:select] || "*"}"
128
+ sql << "FROM #{@table} #{q[:joins]}"
129
+ sql << "WHERE #{q[:where][0]}" if q[:where]
130
+ sql << "HAVING #{q[:having[0]]}" if q[:having]
131
+ sql << "ORDER BY #{q[:order]}" if q[:order]
132
+ sql << "LIMIT #{q[:limit].to_i}" if q[:limit]
133
+ sql << "OFFSET #{q[:offset].to_i}" if q[:offset]
134
+ vals = []
135
+ vals << q[:where][1] if q[:where] and q[:where][1]
136
+ vals << q[:having][1] if q[:having] and q[:having][1]
137
+ #[sql.join(" "), vals.flatten]
138
+ Waxx.debug sql
139
+ Waxx.debug vals.join(", ")
140
+ x.db[@db].execute(sql.join(" "), vals.flatten)
141
+ end
142
+
143
+ def get_by_id(x, id, select=nil, view:nil)
144
+ get(x, id: id, select: select, view: view).first
145
+ end
146
+ alias by_id get_by_id
147
+
148
+ def post(x, data, cols:nil, returning:nil, view:nil, &blk)
149
+ if view
150
+ cols = view.columns.select{|n,c| c[:table] == @table}
151
+ else
152
+ cols ||= @columns
153
+ end
154
+ data = blk.call if block_given?
155
+ sql = "INSERT INTO #{@table} ("
156
+ names = []
157
+ vars = []
158
+ vals = []
159
+ ret = []
160
+ i = 1
161
+ cols.each{|n,v|
162
+ if data/n
163
+ names << n.to_s
164
+ vars << "?"
165
+ vals << cast(v, data/n)
166
+ i += 1
167
+ end
168
+ ret << n.to_s
169
+ }
170
+ sql << names.join(",")
171
+ sql << ") VALUES (#{vars.join(",")})"
172
+ Waxx.debug(sql, 4)
173
+ Waxx.debug(vals, 4)
174
+ x.db[@db].execute(sql, vals)
175
+ new_id = x.db[@db].last_insert_row_id
176
+ x.db[@db].execute("SELECT #{returning || ret.join(",")} FROM #{@table} WHERE #{@pkey} = ?", [new_id]).first
177
+ end
178
+
179
+ def put(x, id, data, cols:nil, returning:nil, view:nil, where:nil, &blk)
180
+ if view
181
+ cols = view.columns.select{|n,c| c[:table] == @table}
182
+ else
183
+ cols ||= @columns
184
+ end
185
+ data = blk.call if block_given?
186
+ sql = "UPDATE #{@table} SET "
187
+ set = []
188
+ vals = []
189
+ ret = []
190
+ i = 1
191
+ Waxx.debug "data: #{data}"
192
+ cols.each{|n,v|
193
+ Waxx.debug "col: #{n}: #{v.inspect}"
194
+ if data.has_key? n.to_s or data.has_key? n.to_sym
195
+ set << "#{n} = ?"
196
+ vals << cast(v, data/n)
197
+ ret << n.to_s
198
+ i += 1
199
+ end
200
+ }
201
+ sql << set.join(",")
202
+ sql << " WHERE #{@pkey} = ? #{where} RETURNING #{returning || ret.join(",")}"
203
+ vals << id
204
+ Waxx.debug(sql)
205
+ Waxx.debug(vals)
206
+ x.db[@db].execute(sql, vals).first
207
+ end
208
+ alias patch put
209
+
210
+ def put_post(x, id, data, cols:nil, returning:nil, view: nil)
211
+ q = nil
212
+ q = get_by_id(x, id, @pkey) if id.to_i > 0
213
+ return post(x, data, cols: cols, returning: returning, view: view) if q.nil?
214
+ put(x, id, data, cols: cols, returning: returning, view: view)
215
+ end
216
+
217
+ def cast(col, val)
218
+ case col[:type].to_sym
219
+ when :int
220
+ val.to_s.empty? ? nil : val.to_i
221
+ when :float, :numeric
222
+ val.to_s.empty? ? nil : val.to_f
223
+ else
224
+ val
225
+ end
226
+ end
227
+
228
+ def delete(x, id)
229
+ x.db[@db].execute("DELETE FROM #{@table} WHERE #{@pkey} = ?", [id])
230
+ end
231
+
232
+ def order(req_order, default_order='')
233
+ return default_order if req_order.nil?
234
+ return orders[req_order.to_sym] if orders.has_key? req_order.to_sym
235
+ @pkey
236
+ end
237
+ end
@@ -0,0 +1,47 @@
1
+ # Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
2
+ # Released under the Apache Version 2 License. See LICENSE.txt.
3
+
4
+ # Supervises the thread pool
5
+ module Waxx::Supervisor
6
+ extend self
7
+
8
+ ##
9
+ # Check the thread pool and add or remove threads
10
+ def check
11
+ start_threads = Waxx['server']['min_threads'] || Waxx['server']['threads']
12
+ max_threads = Waxx['server']['max_threads'] || Waxx['server']['threads']
13
+ idle_thread_timeout = Waxx['server']['idle_thread_timeout'] || 600
14
+ ts = Thread.list.select{|t| t[:name] =~ /waxx/}
15
+ Waxx.debug "Supervisor.check_threads: #{ts.size}", 5
16
+ # Ensure the minimum threads
17
+ if ts.size < start_threads
18
+ 0.upto(start_threads - ts.size).each do |i|
19
+ Waxx::Server.create_thread(i)
20
+ end
21
+ end
22
+ # If no idle threads add more (up-to max)
23
+ if ts.size < max_threads
24
+ idle_count = 0
25
+ ts.each{|t|
26
+ idle_count += 1 if t[:status] == 'idle'
27
+ }
28
+ if idle_count == 0
29
+ Waxx::Server.create_thread(1)
30
+ end
31
+ end
32
+ # Terminate old threads
33
+ ts.each{|t|
34
+ if ts.size > start_threads
35
+ if Time.new.to_i - t[:last_used].to_i > idle_thread_timeout and t[:status] == 'idle' and Thread.current != t
36
+ Waxx.debug "Terminate expired thread #{t[:name]}", 3
37
+ Waxx['databases'].each{|n,v|
38
+ t[:db][n.to_sym].close rescue "already closed"
39
+ }
40
+ t.exit
41
+ ts.delete t
42
+ end
43
+ end
44
+ }
45
+ end
46
+
47
+ end