qm 1.1.10 → 1.1.11

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -4
  3. data/lib/api-server.rb +198 -0
  4. data/lib/qm.rb +3 -2
  5. metadata +63 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92fbd2db28892ca2fd61d59edaf0ce904d36b8a0
4
- data.tar.gz: 0791eaaeefcc38be2c88128b054b66a0ca7b33c3
3
+ metadata.gz: 4e4b2cd330c69ee8a8d56b14249fb06e9d1b3d2a
4
+ data.tar.gz: 00c4657ea4b0dfbc005c6f01203f68392b111b9c
5
5
  SHA512:
6
- metadata.gz: 1cf1815745678de00a61b9af3b05648f9d0d287ee31f3518cebbc0d00acdbe1add773d05c82ce524d4bf7983d2b98dff36a096bba280767f4b9f226db9584f87
7
- data.tar.gz: 5cf44879eadf705525a8c28e343b0ea4a2a5ee417d4626bbea6cbd05eef3db604e2732950b7e5018e7ab3055ecf0815ed9b4a89bcb58ae1c42fc378a8cb2f01c
6
+ metadata.gz: 715e30c6223fd2daf8d63f23b973c1f5741692266b0bd17e51fe7e644a9e026d48ab47f7c21690b94a9b2e586e1930c3246d0af72ba9cf464c26e6fb116ffcbf
7
+ data.tar.gz: ccc3852764ffdfdd44b7945470fe4237c90127f4d4052eb35a361d30718a3274f7a6a89c2340e1c4f3ba39bf25a9fc32d6e7b171eaf17af695e0ff856d02c388
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # qm [![Gem Version](https://badge.fury.io/rb/qm.png)](http://badge.fury.io/rb/qm) [![Dependency Status](https://gemnasium.com/qmachine/qm-ruby.png)](https://gemnasium.com/qmachine/qm-ruby)
1
+ # qm
2
2
 
3
- The `qm` gem for [Ruby](http://www.ruby-lang.org/) will implement both the API
4
- server and the web server components of [QMachine](https://www.qmachine.org)
5
- (QM), but currently it is *very* incomplete.
3
+ The `qm` gem for [Ruby](http://www.ruby-lang.org/) implements both the API
4
+ server and web server components of [QMachine](https://www.qmachine.org) (QM),
5
+ but currently it is incomplete, especially with regard to configuration.
6
6
 
7
7
  Install
8
8
  -------
@@ -11,4 +11,8 @@ To install the latest release, run
11
11
 
12
12
  gem install qm
13
13
 
14
+ ===
15
+
16
+ [![Gem Version](https://badge.fury.io/rb/qm.png)](http://badge.fury.io/rb/qm) [![Dependency Status](https://gemnasium.com/qmachine/qm-ruby.png)](https://gemnasium.com/qmachine/qm-ruby)
17
+
14
18
  <!-- vim:set syntax=markdown: -->
data/lib/api-server.rb ADDED
@@ -0,0 +1,198 @@
1
+ #- Ruby source code / Rack configuration file
2
+
3
+ #- api-server.rb ~~
4
+ #
5
+ # This file is a hacked-up version of the "teaching version" of QMachine,
6
+ # and the rest of this introduction reflects that.
7
+ #
8
+ # This is a self-contained Rack app that uses Sinatra's domain-specific
9
+ # language (DSL) in tandem with SQLite to implement a teaching version of
10
+ # QMachine. The idea here is to pack most of the functionality of the
11
+ # original Node.js codebase into a single file that reads like pseudo-code.
12
+ #
13
+ # Of course, there are some caveats. This version succeeds in abbreviating
14
+ # the original codebase, but it doesn't support all of the original options
15
+ # yet. The code can also be hard to modify if you're unfamiliar with Sinatra,
16
+ # because Ruby's scoping rules are very different from JavaScript's, and
17
+ # Sinatra's DSL makes things even "worse", to be honest. My advice here is,
18
+ # don't think too hard about it. Just enjoy it.
19
+ #
20
+ # I do plan to merge this program with the Ruby gem in the future, which is
21
+ # why the database schema matches the Node.js implementation's (which is not
22
+ # as straight-forward as it could be). For now, it serves its purpose, and it
23
+ # does so with just 95 lines of source code ;-)
24
+ #
25
+ # NOTE: Using a "%" character incorrectly in a URL will cause you great
26
+ # anguish, and there isn't a good way for me to handle this problem "softly"
27
+ # because it is the expected behavior (http://git.io/bmKr2w). Thus, you will
28
+ # tend to see "Bad Request" on your screen if you insist on using "%" as part
29
+ # of a 'box', 'key', or 'status' value.
30
+ #
31
+ # ~~ (c) SRW, 24 Apr 2013
32
+ # ~~ last updated 13 Jul 2014
33
+
34
+ require 'json'
35
+ require 'sinatra'
36
+ #require 'sinatra/cross_origin'
37
+ require 'sqlite3'
38
+
39
+ class QMachineService < Sinatra::Base
40
+
41
+ configure do
42
+
43
+ # QMachine options
44
+
45
+ set avar_ttl: 86400, # seconds
46
+ enable_api_server: true,
47
+ enable_CORS: true,
48
+ enable_web_server: true,
49
+ hostname: '0.0.0.0',
50
+ persistent_storage: 'qm.db',
51
+ port: ENV['PORT'] || 8177,
52
+ public_folder: 'public'
53
+
54
+ # Sinatra mappings and options needed by QMachine -- leave these alone ;-)
55
+
56
+ mime_type webapp: 'application/x-web-app-manifest+json'
57
+ set bind: :hostname, run: false, static: :enable_web_server
58
+
59
+ # See also: http://www.sinatrarb.com/configuration.html
60
+
61
+ end
62
+
63
+ error do
64
+ # This "route" handles errors that occur as part of the server-side code.
65
+ hang_up
66
+ end
67
+
68
+ helpers do
69
+ # This block defines subfunctions for use inside the route definitions.
70
+
71
+ def sqlite(query)
72
+ # This helper method helps DRY out the code for database queries, and
73
+ # it does so in an incredibly robust and inefficient way -- by
74
+ # creating the table and evicting expired rows before every single
75
+ # query. A caveat, of course, is that the special ":memory:" database
76
+ # doesn't work correctly, but ":memory:" isn't *persistent* storage
77
+ # anyway. Also, I have omitted indexing on `box_status` for obvious
78
+ # reasons :-P
79
+ begin
80
+ db = SQLite3::Database.open(settings.persistent_storage)
81
+ db.execute_batch <<-sql
82
+ CREATE TABLE IF NOT EXISTS avars (
83
+ body TEXT NOT NULL,
84
+ box_key TEXT NOT NULL PRIMARY KEY,
85
+ box_status TEXT,
86
+ exp_date INTEGER NOT NULL,
87
+ key TEXT
88
+ );
89
+ DELETE FROM avars WHERE (exp_date < #{now_plus(0)})
90
+ sql
91
+ # We have to execute the query code `query` separately because
92
+ # the `db.execute_batch` function always returns `nil`, which
93
+ # prevents us from being able to retrieve the results of the
94
+ # query.
95
+ x = db.execute(query)
96
+ rescue SQLite3::Exception => err
97
+ puts "Exception occurred: #{err}"
98
+ ensure
99
+ db.close if db
100
+ end
101
+ return x
102
+ end
103
+
104
+ def hang_up
105
+ # This helper method "hangs up" on a request by sending a nondescript
106
+ # 444 response back to the client, a convention taken from nginx.
107
+ halt [444, {'Content-Type' => 'text/plain'}, ['']]
108
+ end
109
+
110
+ def now_plus(dt)
111
+ # This helper method computes a date (in milliseconds) that is
112
+ # specified by an offset `dt` (in seconds).
113
+ return (1000 * (Time.now.to_f + dt)).to_i
114
+ end
115
+
116
+ end
117
+
118
+ not_found do
119
+ # This "route" handles requests that didn't match.
120
+ hang_up
121
+ end
122
+
123
+ if settings.enable_api_server? then
124
+
125
+ # Here, we set up "routes" to handle incoming GET and POST requests.
126
+
127
+ before '/*/*' do |version, box|
128
+ # When any request matches the pattern given, this block will execute
129
+ # before the route that corresponds to its HTTP method. The code here
130
+ # will validate the request's parameters and store them as instance
131
+ # variables that will be available to the corresponding route's code.
132
+ @box, @key, @status = box, params[:key], params[:status]
133
+ hang_up unless ((version == 'box') or (version == 'v1')) and
134
+ (@box.match(/^[\w\-]+$/)) and
135
+ ((@key.is_a?(String) and @key.match(/^[A-Za-z0-9]+$/)) or
136
+ (@status.is_a?(String) and @status.match(/^[A-Za-z0-9]+$/)))
137
+ #cross_origin if settings.enable_CORS?
138
+ end
139
+
140
+ get '/:version/:box' do
141
+ # This route responds to API calls that "read" from persistent
142
+ # storage, such as when checking for new tasks to run or downloading
143
+ # results.
144
+ hang_up unless (@key.is_a?(String) ^ @status.is_a?(String))
145
+ bk, bs = "#{@box}&#{@key}", "#{@box}&#{@status}"
146
+ if @key.is_a?(String) then
147
+ # This arm runs when a client requests the value of a specific
148
+ # avar.
149
+ x = sqlite("SELECT body FROM avars WHERE box_key = '#{bk}'")
150
+ y = (x.length == 0) ? '{}' : x[0][0]
151
+ else
152
+ # This arm runs when a client requests a task queue.
153
+ x = sqlite("SELECT key FROM avars WHERE box_status = '#{bs}'")
154
+ y = (x.length == 0) ? '[]' : (x.map {|row| row[0]}).to_json
155
+ end
156
+ return [200, {'Content-Type' => 'application/json'}, [y]]
157
+ end
158
+
159
+ post '/:version/:box' do
160
+ # This route responds to API calls that "write" to persistent
161
+ # storage, such as when uploading results or submitting new tasks.
162
+ hang_up unless @key.is_a?(String) and not @status.is_a?(String)
163
+ body, ed = request.body.read, now_plus(settings.avar_ttl)
164
+ x = JSON.parse(body)
165
+ hang_up unless (@box == x['box']) and (@key == x['key'])
166
+ bk, bs = "#{@box}&#{@key}", "#{@box}&#{x['status']}"
167
+ if x['status'].is_a?(String) then
168
+ # This arm runs only when a client writes an avar which
169
+ # represents a task description.
170
+ hang_up unless x['status'].match(/^[A-Za-z0-9]+$/)
171
+ sqlite("INSERT OR REPLACE INTO avars
172
+ (body, box_key, box_status, exp_date, key)
173
+ VALUES ('#{body}', '#{bk}', '#{bs}', #{ed}, '#{@key}')")
174
+ else
175
+ # This arm runs when a client is writing a "regular avar".
176
+ sqlite("INSERT OR REPLACE INTO avars (body, box_key, exp_date)
177
+ VALUES ('#{body}', '#{bk}', #{ed})")
178
+ end
179
+ return [201, {'Content-Type' => 'text/plain'}, ['']]
180
+ end
181
+
182
+ end
183
+
184
+ if settings.enable_web_server? then
185
+
186
+ get '/' do
187
+ # This route enables a static index page to be served from the public
188
+ # folder, if and only if QM's web server has been enabled.
189
+ send_file(File.join(settings.public_folder, 'index.html'))
190
+ end
191
+
192
+ end
193
+
194
+ end
195
+
196
+ QMachineService.run!
197
+
198
+ #- vim:set syntax=ruby:
data/lib/qm.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  #- qm.rb ~~
4
4
  # ~~ (c) SRW, 12 Apr 2013
5
- # ~~ last updated 17 Apr 2013
5
+ # ~~ last updated 13 Jul 2014
6
6
 
7
7
  module QM
8
8
 
@@ -27,7 +27,8 @@ module QM
27
27
 
28
28
  def self::launch_service(*obj)
29
29
  # This function needs documentation.
30
- puts '(placeholder: `launch_service`)';
30
+ require 'api-server.rb'
31
+ #puts '(placeholder: `launch_service`)';
31
32
  return;
32
33
  end
33
34
 
metadata CHANGED
@@ -1,16 +1,72 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.10
4
+ version: 1.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Wilkinson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-01 00:00:00.000000000 Z
12
- dependencies: []
13
- description: This is a *very incomplete* port of QMachine.
11
+ date: 2014-07-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.9
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.9
55
+ - !ruby/object:Gem::Dependency
56
+ name: thin
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.6.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.6.2
69
+ description: This is an incomplete port of the QMachine web service.
14
70
  email: sean@mathbiol.org
15
71
  executables: []
16
72
  extensions: []
@@ -20,11 +76,13 @@ extra_rdoc_files:
20
76
  files:
21
77
  - LICENSE
22
78
  - README.md
79
+ - lib/api-server.rb
23
80
  - lib/qm.rb
24
81
  homepage: https://www.qmachine.org
25
82
  licenses:
26
83
  - Apache 2.0
27
- metadata: {}
84
+ metadata:
85
+ issue_tracker: https://github.com/qmachine/qm-ruby/issues
28
86
  post_install_message:
29
87
  rdoc_options: []
30
88
  require_paths: