rvt 0.9.9
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +25 -0
- data/app/assets/javascripts/rvt/application.js +1 -0
- data/app/assets/javascripts/rvt/console_sessions.js +182 -0
- data/app/assets/stylesheets/rvt/application.css +13 -0
- data/app/assets/stylesheets/rvt/console_sessions.css.erb +6 -0
- data/app/controllers/rvt/application_controller.rb +13 -0
- data/app/controllers/rvt/console_sessions_controller.rb +47 -0
- data/app/models/rvt/console_session.rb +109 -0
- data/app/views/layouts/rvt/application.html.erb +14 -0
- data/app/views/rvt/console_sessions/index.html.erb +17 -0
- data/config/routes.rb +11 -0
- data/lib/assets/javascripts/rvt.js +41 -0
- data/lib/rvt.rb +21 -0
- data/lib/rvt/colors.rb +87 -0
- data/lib/rvt/colors/light.rb +24 -0
- data/lib/rvt/colors/monokai.rb +24 -0
- data/lib/rvt/colors/solarized.rb +47 -0
- data/lib/rvt/colors/tango.rb +24 -0
- data/lib/rvt/colors/xterm.rb +24 -0
- data/lib/rvt/engine.rb +85 -0
- data/lib/rvt/slave.rb +147 -0
- data/lib/rvt/version.rb +3 -0
- data/test/controllers/rvt/console_sessions_controller_test.rb +100 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/controller_helper_test/index.html.erb +1 -0
- data/test/dummy/app/views/exception_test/xhr.html.erb +1 -0
- data/test/dummy/app/views/helper_test/index.html.erb +220 -0
- data/test/dummy/app/views/layouts/application.html.erb +16 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +44 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +34 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +16 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +247222 -0
- data/test/dummy/log/test.log +963 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/0509a6e0e75d9ac5a88bba7291b04686 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2bd9d10dae311aa2dfb6dea9c1e0ad50 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2f41a6a41d0a16db31cabd2b8689ca00 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/4de66e84a0d6f009fac50cd608fb8581 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/56e8026311075410507152df2aea4307 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/9542de15712d45f70221931bf78c00e5 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/98cea396a602c53d876e79bf4b89de3b +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/9df3968b0f171feec749766fffa50b2e +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/aa5fc4cb46c5294192c9d3af8824f88b +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/ac7197dc7dd9ab362d915d19e37a8e01 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/b238befd6eff46b26d56322c1450ea45 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/c69b8b79c21fd48ad84c3fad87945a5c +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/da4318a4d8364d0616edd706508371bc +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/dd71b14df42fd6dc95355cded9e4d0f9 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/eb06cae1627276e46965809e766aff13 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/edffb5017d27ddb65965203636286405 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f0ced5f3d4a75fce1c596d6c3f97a42d +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/fb9e611526e612ba797f0c11b987826b +0 -0
- data/test/models/console_session_test.rb +58 -0
- data/test/rvt/colors_test.rb +58 -0
- data/test/rvt/engine_test.rb +145 -0
- data/test/rvt/slave_test.rb +72 -0
- data/test/test_helper.rb +27 -0
- data/vendor/assets/javascripts/term.js +5771 -0
- metadata +284 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 40c0eb5241121c94ef06e819351e3622dec19f87
|
|
4
|
+
data.tar.gz: 33f5a3b57e0f16bddda6cf8568d0a126403fd9e9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 473f3b32b24b91a76f45a46b19f557b0f96a3bb8f5ec3040bf2d1a09f265d662d3952875a8aec3cf45e045bb3683c7e6c8d9229382cd063399627c0ab5033344
|
|
7
|
+
data.tar.gz: 8decfdb59c9293d84bac29d98fe546d3da4ef8e944d76c688e158e3284db8a6289933113c3aaed683f02c94c3d7e1451b720e9888e30c1a6711ecde5e7b9f85d
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2015 Genadi Samokovarov, Guillermo Iguaran
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'socket'
|
|
8
|
+
require 'active_support/core_ext/string/strip'
|
|
9
|
+
require 'rake/testtask'
|
|
10
|
+
|
|
11
|
+
EXPANDED_CWD = File.expand_path(File.dirname(__FILE__))
|
|
12
|
+
|
|
13
|
+
Rake::TestTask.new(:test) do |t|
|
|
14
|
+
t.libs << 'lib'
|
|
15
|
+
t.libs << 'test'
|
|
16
|
+
t.pattern = 'test/**/*_test.rb'
|
|
17
|
+
t.verbose = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Just ignore this if rake is not runned from the current directory, as is the
|
|
21
|
+
# case with docker's container. BUNDLE_GEMFILE won't do for our case, since the
|
|
22
|
+
# Gemfile references gemspec.
|
|
23
|
+
Bundler::GemHelper.install_tasks if defined? Bundler::GemHelper
|
|
24
|
+
|
|
25
|
+
task default: :test
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//= require_tree .
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//= require rvt
|
|
2
|
+
|
|
3
|
+
var AJAXTransport = (function(RVT) {
|
|
4
|
+
|
|
5
|
+
var inherits = RVT.inherits;
|
|
6
|
+
var EventEmitter = RVT.EventEmitter;
|
|
7
|
+
|
|
8
|
+
var FORM_MIME_TYPE = 'application/x-www-form-urlencoded; charset=utf-8';
|
|
9
|
+
|
|
10
|
+
var AJAXTransport = function(options) {
|
|
11
|
+
EventEmitter.call(this);
|
|
12
|
+
options || (options = {});
|
|
13
|
+
|
|
14
|
+
this.url = (typeof options.url === 'string') ? {
|
|
15
|
+
input: options.url,
|
|
16
|
+
pendingOutput: options.url,
|
|
17
|
+
configuration: options.url
|
|
18
|
+
} : options.url;
|
|
19
|
+
|
|
20
|
+
this.uid = options.uid;
|
|
21
|
+
this.pendingInput = '';
|
|
22
|
+
|
|
23
|
+
this.initializeEventHandlers();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
inherits(AJAXTransport, EventEmitter);
|
|
27
|
+
|
|
28
|
+
// Initializes the default event handlers.
|
|
29
|
+
AJAXTransport.prototype.initializeEventHandlers = function() {
|
|
30
|
+
this.on('input', this.sendInput);
|
|
31
|
+
this.on('configuration', this.sendConfiguration);
|
|
32
|
+
this.once('initialization', function(cols, rows) {
|
|
33
|
+
this.emit('configuration', cols, rows);
|
|
34
|
+
this.pollForPendingOutput();
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Shorthand for creating XHR requests.
|
|
39
|
+
AJAXTransport.prototype.createRequest = function(method, url, options) {
|
|
40
|
+
options || (options = {});
|
|
41
|
+
|
|
42
|
+
var params = '';
|
|
43
|
+
|
|
44
|
+
if (typeof options.form === 'object') {
|
|
45
|
+
var content = [], form = options.form;
|
|
46
|
+
|
|
47
|
+
for (var key in form) {
|
|
48
|
+
var value = form[key];
|
|
49
|
+
content.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
params = content.join('&');
|
|
53
|
+
if (method === 'GET' ? '?' : '') params = '?' + params;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var request = new XMLHttpRequest;
|
|
57
|
+
request.open(method, method === 'GET' ? url + params : url);
|
|
58
|
+
|
|
59
|
+
if (params && method != 'GET') {
|
|
60
|
+
request.setRequestHeader('Content-Type', FORM_MIME_TYPE);
|
|
61
|
+
request.data = params;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return request;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
AJAXTransport.prototype.pollForPendingOutput = function() {
|
|
68
|
+
var request = this.createRequest('GET', this.url.pendingOutput, {
|
|
69
|
+
form: { uid: this.uid }
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
var self = this;
|
|
73
|
+
request.onreadystatechange = function() {
|
|
74
|
+
if (request.readyState === XMLHttpRequest.DONE) {
|
|
75
|
+
if (request.status === 200) {
|
|
76
|
+
self.emit('pendingOutput', request.responseText);
|
|
77
|
+
self.pollForPendingOutput();
|
|
78
|
+
} else {
|
|
79
|
+
self.emit('disconnect', request);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
request.send(null);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Send the input to the server.
|
|
88
|
+
//
|
|
89
|
+
// Each key press is encoded to an intermediate format, before it is sent to
|
|
90
|
+
// the server.
|
|
91
|
+
//
|
|
92
|
+
// RVT#keysPressed is an alias for RVT#sendInput.
|
|
93
|
+
AJAXTransport.prototype.sendInput = function(input) {
|
|
94
|
+
input || (input = '');
|
|
95
|
+
|
|
96
|
+
if (this.disconnected) return;
|
|
97
|
+
if (this.sendingInput) return this.pendingInput += input;
|
|
98
|
+
|
|
99
|
+
// Indicate that we are starting to send input.
|
|
100
|
+
this.sendingInput = true;
|
|
101
|
+
|
|
102
|
+
var request = this.createRequest('PUT', this.url.input, {
|
|
103
|
+
form: { input: this.pendingInput + input, uid: this.uid }
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Clear the pending input.
|
|
107
|
+
this.pendingInput = '';
|
|
108
|
+
|
|
109
|
+
var self = this;
|
|
110
|
+
request.onreadystatechange = function() {
|
|
111
|
+
if (request.readyState === XMLHttpRequest.DONE) {
|
|
112
|
+
self.sendingInput = false;
|
|
113
|
+
if (self.pendingInput) self.sendInput();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
request.send(request.data);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Send the terminal configuration to the server.
|
|
121
|
+
//
|
|
122
|
+
// Right now by configuration, we understand the terminal widht and terminal
|
|
123
|
+
// height.
|
|
124
|
+
//
|
|
125
|
+
// RVT#resized is an alias for RVT#sendconfiguration.
|
|
126
|
+
AJAXTransport.prototype.sendConfiguration = function(cols, rows) {
|
|
127
|
+
if (this.disconnected) return;
|
|
128
|
+
|
|
129
|
+
var request = this.createRequest('PUT', this.url.configuration, {
|
|
130
|
+
form: { width: cols, height: rows, uid: this.uid }
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Just send the configuration and don't care about any output.
|
|
134
|
+
request.send(request.data);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return AJAXTransport;
|
|
138
|
+
|
|
139
|
+
}).call(this, RVT);
|
|
140
|
+
|
|
141
|
+
window.addEventListener('load', function() {
|
|
142
|
+
var geometry = calculateFitScreenGeometry();
|
|
143
|
+
config.terminal.cols = geometry[0];
|
|
144
|
+
config.terminal.rows = geometry[1];
|
|
145
|
+
|
|
146
|
+
var terminal = window.terminal = new RVT.Terminal(config.terminal);
|
|
147
|
+
|
|
148
|
+
terminal.on('data', function(data) {
|
|
149
|
+
transport.emit('input', data);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
var transport = new AJAXTransport(config.transport);
|
|
153
|
+
|
|
154
|
+
transport.on('pendingOutput', function(response) {
|
|
155
|
+
var json = JSON.parse(response);
|
|
156
|
+
if (json.output) terminal.write(json.output);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
transport.on('disconnect', function() {
|
|
160
|
+
terminal.destroy();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
transport.emit('initialization', terminal.cols, terminal.rows);
|
|
164
|
+
|
|
165
|
+
// Utilities
|
|
166
|
+
// ---------
|
|
167
|
+
|
|
168
|
+
function calculateFitScreenGeometry() {
|
|
169
|
+
// Currently, resizing term.js is broken. E.g. opening vi causes it to go
|
|
170
|
+
// back to 80x24 and fail with off-by-one error. Other stuff, like chip8
|
|
171
|
+
// are rendered incorrectly and so on.
|
|
172
|
+
//
|
|
173
|
+
// To work around it, create a temporary terminal, just so we can get the
|
|
174
|
+
// best dimensions for the screen.
|
|
175
|
+
var temporary = new RVT.Terminal;
|
|
176
|
+
try {
|
|
177
|
+
return temporary.fitScreen();
|
|
178
|
+
} finally {
|
|
179
|
+
temporary.destroy();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
|
10
|
+
*
|
|
11
|
+
*= require_self
|
|
12
|
+
*= require_tree .
|
|
13
|
+
*/
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<% RVT.config.style.instance_eval do %>
|
|
2
|
+
body { color: <%= colors.foreground %>; background: <%= colors.background %>; margin: 0; padding: 0; }
|
|
3
|
+
|
|
4
|
+
.terminal { float: left; overflow: hidden; font: <%= font %>; }
|
|
5
|
+
.terminal-cursor { color: <%= colors.background %>; background: <%= colors.foreground %>; }
|
|
6
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module RVT
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
before_action :prevent_unauthorized_requests!
|
|
4
|
+
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def prevent_unauthorized_requests!
|
|
8
|
+
unless request.remote_ip.in?(RVT.config.whitelisted_ips)
|
|
9
|
+
render nothing: true, status: :unauthorized
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require_dependency 'rvt/application_controller'
|
|
2
|
+
|
|
3
|
+
module RVT
|
|
4
|
+
class ConsoleSessionsController < ApplicationController
|
|
5
|
+
rescue_from ConsoleSession::Unavailable do |exception|
|
|
6
|
+
render json: exception, status: :gone
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
rescue_from ConsoleSession::Invalid do |exception|
|
|
10
|
+
render json: exception, status: :unprocessable_entity
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
rescue_from ConsoleSession::Unauthorized do |exception|
|
|
14
|
+
render json: exception, status: :unauthorized
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def index
|
|
18
|
+
@console_session = ConsoleSession.create
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def input
|
|
22
|
+
@console_session = ConsoleSession.find_by_pid_and_uid(params[:id], params[:uid])
|
|
23
|
+
@console_session.send_input(console_session_params[:input])
|
|
24
|
+
|
|
25
|
+
render nothing: true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def configuration
|
|
29
|
+
@console_session = ConsoleSession.find_by_pid_and_uid(params[:id], params[:uid])
|
|
30
|
+
@console_session.configure(console_session_params)
|
|
31
|
+
|
|
32
|
+
render nothing: true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def pending_output
|
|
36
|
+
@console_session = ConsoleSession.find_by_pid_and_uid(params[:id], params[:uid])
|
|
37
|
+
|
|
38
|
+
render json: { output: @console_session.pending_output }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def console_session_params
|
|
44
|
+
params.permit(:id, :uid, :input, :width, :height)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module RVT
|
|
2
|
+
# Manage and persist (in memory) RVT::Slave instances.
|
|
3
|
+
class ConsoleSession
|
|
4
|
+
include ActiveModel::Model
|
|
5
|
+
|
|
6
|
+
# In-memory storage for the console sessions. Session preservation is
|
|
7
|
+
# troubled on servers with multiple workers and threads.
|
|
8
|
+
INMEMORY_STORAGE = {}
|
|
9
|
+
|
|
10
|
+
# Base error class for ConsoleSession specific exceptions.
|
|
11
|
+
#
|
|
12
|
+
# Provides #to_json implementation, so all subclasses are JSON
|
|
13
|
+
# serializable.
|
|
14
|
+
class Error < StandardError
|
|
15
|
+
def as_json(*)
|
|
16
|
+
{ error: to_s }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Raised when trying to find a session that is no longer in the in-memory
|
|
21
|
+
# session storage or when the slave process exited.
|
|
22
|
+
Unavailable = Class.new(Error)
|
|
23
|
+
|
|
24
|
+
# Raised when an operation transition to an invalid state.
|
|
25
|
+
Invalid = Class.new(Error)
|
|
26
|
+
|
|
27
|
+
# Raised when a request doesn't know the slave process uid.
|
|
28
|
+
Unauthorized = Class.new(Error)
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# Finds a session by its pid.
|
|
32
|
+
#
|
|
33
|
+
# Raises RVT::ConsoleSession::Expired if there is no such session.
|
|
34
|
+
def find(pid)
|
|
35
|
+
INMEMORY_STORAGE[pid.to_i] or raise Unavailable, 'Session unavailable'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Finds a session by its pid.
|
|
39
|
+
#
|
|
40
|
+
# Raises RVT::ConsoleSession::Expired if there is no such session.
|
|
41
|
+
# Raises RVT::ConsoleSession::Unauthorized if uid doesn't match.
|
|
42
|
+
def find_by_pid_and_uid(pid, uid)
|
|
43
|
+
find(pid).tap do |console_session|
|
|
44
|
+
raise Unauthorized if console_session.uid != uid
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Creates an already persisted consolse session.
|
|
49
|
+
#
|
|
50
|
+
# Use this method if you need to persist a session, without providing it
|
|
51
|
+
# any input.
|
|
52
|
+
def create
|
|
53
|
+
new.persist
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize
|
|
58
|
+
@slave = Slave.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Explicitly persist the model in the in-memory storage.
|
|
62
|
+
def persist
|
|
63
|
+
INMEMORY_STORAGE[pid] = self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns true if the current session is persisted in the in-memory storage.
|
|
67
|
+
def persisted?
|
|
68
|
+
self == INMEMORY_STORAGE[pid]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns an Enumerable of all key attributes if any is set, regardless if
|
|
72
|
+
# the object is persisted or not.
|
|
73
|
+
def to_key
|
|
74
|
+
[pid] if persisted?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def delegate_and_call_slave_method(name, *args, &block)
|
|
80
|
+
# Cache the delegated method, so we don't have to hit #method_missing
|
|
81
|
+
# on every call.
|
|
82
|
+
define_singleton_method(name) do |*inner_args, &inner_block|
|
|
83
|
+
begin
|
|
84
|
+
@slave.public_send(name, *inner_args, &inner_block)
|
|
85
|
+
rescue ArgumentError => exc
|
|
86
|
+
raise Invalid, exc
|
|
87
|
+
rescue Slave::Closed => exc
|
|
88
|
+
raise Unavailable, exc
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Now call the method, since that's our most common use case. Delegate
|
|
93
|
+
# the method and than call it.
|
|
94
|
+
public_send(name, *args, &block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def method_missing(name, *args, &block)
|
|
98
|
+
if @slave.respond_to?(name)
|
|
99
|
+
delegate_and_call_slave_method(name, *args, &block)
|
|
100
|
+
else
|
|
101
|
+
super
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def respond_to_missing?(name, include_all = false)
|
|
106
|
+
@slave.respond_to?(name) or super
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|