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