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.
- checksums.yaml +4 -4
- data/README.md +8 -4
- data/lib/api-server.rb +198 -0
- data/lib/qm.rb +3 -2
- metadata +63 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e4b2cd330c69ee8a8d56b14249fb06e9d1b3d2a
|
4
|
+
data.tar.gz: 00c4657ea4b0dfbc005c6f01203f68392b111b9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 715e30c6223fd2daf8d63f23b973c1f5741692266b0bd17e51fe7e644a9e026d48ab47f7c21690b94a9b2e586e1930c3246d0af72ba9cf464c26e6fb116ffcbf
|
7
|
+
data.tar.gz: ccc3852764ffdfdd44b7945470fe4237c90127f4d4052eb35a361d30718a3274f7a6a89c2340e1c4f3ba39bf25a9fc32d6e7b171eaf17af695e0ff856d02c388
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# qm
|
1
|
+
# qm
|
2
2
|
|
3
|
-
The `qm` gem for [Ruby](http://www.ruby-lang.org/)
|
4
|
-
server and
|
5
|
-
|
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
|
+
[](http://badge.fury.io/rb/qm) [](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
|
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
|
-
|
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.
|
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-
|
12
|
-
dependencies:
|
13
|
-
|
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:
|