rhosync 2.0.0.beta1
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.
- data/CHANGELOG +5 -0
- data/LICENSE +674 -0
- data/README.md +26 -0
- data/Rakefile +109 -0
- data/bench/bench +6 -0
- data/bench/benchapp/Rakefile +14 -0
- data/bench/benchapp/application.rb +13 -0
- data/bench/benchapp/config.ru +32 -0
- data/bench/benchapp/settings/license.key +1 -0
- data/bench/benchapp/settings/settings.yml +18 -0
- data/bench/benchapp/sources/mock_adapter.rb +55 -0
- data/bench/benchapp/sources/queue_mock_adapter.rb +2 -0
- data/bench/benchapp/vendor/rhosync/lib/rhosync.rb +7 -0
- data/bench/lib/bench/cli.rb +16 -0
- data/bench/lib/bench/logging.rb +18 -0
- data/bench/lib/bench/mock_client.rb +41 -0
- data/bench/lib/bench/result.rb +50 -0
- data/bench/lib/bench/runner.rb +44 -0
- data/bench/lib/bench/session.rb +65 -0
- data/bench/lib/bench/statistics.rb +56 -0
- data/bench/lib/bench/test_data.rb +55 -0
- data/bench/lib/bench/timer.rb +10 -0
- data/bench/lib/bench/utils.rb +49 -0
- data/bench/lib/bench.rb +128 -0
- data/bench/lib/testdata/100-data.txt +148 -0
- data/bench/lib/testdata/5-data.txt +11 -0
- data/bench/scripts/cud_script.rb +77 -0
- data/bench/scripts/helpers.rb +101 -0
- data/bench/scripts/query_md_script.rb +46 -0
- data/bench/scripts/query_script.rb +46 -0
- data/bench/spec/bench_spec_helper.rb +65 -0
- data/bench/spec/logging_spec.rb +19 -0
- data/bench/spec/mock_adapter_spec.rb +61 -0
- data/bench/spec/mock_client_spec.rb +64 -0
- data/bench/spec/result_spec.rb +59 -0
- data/bench/spec/utils_spec.rb +35 -0
- data/bin/rhosync +34 -0
- data/doc/protocol.html +1901 -0
- data/doc/public/css/print.css +29 -0
- data/doc/public/css/screen.css +257 -0
- data/doc/public/css/style.css +20 -0
- data/examples/simple/application.rb +13 -0
- data/examples/simple/sources/sample_adapter.rb +5 -0
- data/examples/simple/sources/simple_adapter.rb +5 -0
- data/examples/simple/vendor/rhosync/lib/rhosync.rb +7 -0
- data/generators/rhosync.rb +98 -0
- data/generators/templates/application/Rakefile +19 -0
- data/generators/templates/application/application.rb +27 -0
- data/generators/templates/application/config.ru +33 -0
- data/generators/templates/application/settings/license.key +1 -0
- data/generators/templates/application/settings/settings.yml +14 -0
- data/generators/templates/source/source_adapter.rb +49 -0
- data/lib/rhosync/api/create_client.rb +3 -0
- data/lib/rhosync/api/create_user.rb +7 -0
- data/lib/rhosync/api/delete_client.rb +5 -0
- data/lib/rhosync/api/delete_user.rb +5 -0
- data/lib/rhosync/api/get_api_token.rb +7 -0
- data/lib/rhosync/api/get_client_params.rb +3 -0
- data/lib/rhosync/api/get_db_doc.rb +7 -0
- data/lib/rhosync/api/get_license_info.rb +7 -0
- data/lib/rhosync/api/get_source_params.rb +3 -0
- data/lib/rhosync/api/list_client_docs.rb +12 -0
- data/lib/rhosync/api/list_clients.rb +3 -0
- data/lib/rhosync/api/list_source_docs.rb +10 -0
- data/lib/rhosync/api/list_sources.rb +15 -0
- data/lib/rhosync/api/list_users.rb +3 -0
- data/lib/rhosync/api/ping.rb +7 -0
- data/lib/rhosync/api/push_deletes.rb +6 -0
- data/lib/rhosync/api/push_objects.rb +6 -0
- data/lib/rhosync/api/reset.rb +10 -0
- data/lib/rhosync/api/set_db_doc.rb +8 -0
- data/lib/rhosync/api/set_refresh_time.rb +8 -0
- data/lib/rhosync/api/update_user.rb +4 -0
- data/lib/rhosync/api/upload_file.rb +4 -0
- data/lib/rhosync/api_token.rb +19 -0
- data/lib/rhosync/app.rb +69 -0
- data/lib/rhosync/bulk_data/bulk_data.rb +75 -0
- data/lib/rhosync/bulk_data/syncdb.index.schema +3 -0
- data/lib/rhosync/bulk_data/syncdb.schema +37 -0
- data/lib/rhosync/bulk_data.rb +2 -0
- data/lib/rhosync/client.rb +74 -0
- data/lib/rhosync/client_sync.rb +296 -0
- data/lib/rhosync/console/app/helpers/auth_helper.rb +18 -0
- data/lib/rhosync/console/app/helpers/extensions.rb +19 -0
- data/lib/rhosync/console/app/helpers/helpers.rb +52 -0
- data/lib/rhosync/console/app/public/main.css +7 -0
- data/lib/rhosync/console/app/public/text.txt +0 -0
- data/lib/rhosync/console/app/routes/auth.rb +29 -0
- data/lib/rhosync/console/app/routes/client.rb +32 -0
- data/lib/rhosync/console/app/routes/docs.rb +84 -0
- data/lib/rhosync/console/app/routes/home.rb +22 -0
- data/lib/rhosync/console/app/routes/user.rb +63 -0
- data/lib/rhosync/console/app/views/client.erb +30 -0
- data/lib/rhosync/console/app/views/doc.erb +56 -0
- data/lib/rhosync/console/app/views/docs.erb +29 -0
- data/lib/rhosync/console/app/views/index.erb +50 -0
- data/lib/rhosync/console/app/views/layout.erb +12 -0
- data/lib/rhosync/console/app/views/newuser.erb +17 -0
- data/lib/rhosync/console/app/views/ping.erb +28 -0
- data/lib/rhosync/console/app/views/result.erb +11 -0
- data/lib/rhosync/console/app/views/user.erb +32 -0
- data/lib/rhosync/console/app/views/users.erb +14 -0
- data/lib/rhosync/console/rhosync_api.rb +102 -0
- data/lib/rhosync/console/server.rb +27 -0
- data/lib/rhosync/credential.rb +9 -0
- data/lib/rhosync/document.rb +43 -0
- data/lib/rhosync/indifferent_access.rb +132 -0
- data/lib/rhosync/jobs/bulk_data_job.rb +104 -0
- data/lib/rhosync/jobs/ping_job.rb +19 -0
- data/lib/rhosync/jobs/source_job.rb +16 -0
- data/lib/rhosync/license.rb +79 -0
- data/lib/rhosync/lock_ops.rb +11 -0
- data/lib/rhosync/model.rb +410 -0
- data/lib/rhosync/ping/blackberry.rb +55 -0
- data/lib/rhosync/ping/iphone.rb +44 -0
- data/lib/rhosync/ping.rb +2 -0
- data/lib/rhosync/read_state.rb +27 -0
- data/lib/rhosync/server/views/index.erb +12 -0
- data/lib/rhosync/server.rb +242 -0
- data/lib/rhosync/source.rb +112 -0
- data/lib/rhosync/source_adapter.rb +95 -0
- data/lib/rhosync/source_sync.rb +245 -0
- data/lib/rhosync/store.rb +199 -0
- data/lib/rhosync/tasks.rb +151 -0
- data/lib/rhosync/user.rb +83 -0
- data/lib/rhosync/version.rb +3 -0
- data/lib/rhosync.rb +251 -0
- data/spec/api/api_helper.rb +44 -0
- data/spec/api/create_client_spec.rb +13 -0
- data/spec/api/create_user_spec.rb +16 -0
- data/spec/api/delete_client_spec.rb +13 -0
- data/spec/api/delete_user_spec.rb +18 -0
- data/spec/api/get_api_token_spec.rb +25 -0
- data/spec/api/get_client_params_spec.rb +18 -0
- data/spec/api/get_db_doc_spec.rb +21 -0
- data/spec/api/get_license_info_spec.rb +16 -0
- data/spec/api/get_source_params_spec.rb +26 -0
- data/spec/api/list_client_docs_spec.rb +33 -0
- data/spec/api/list_clients_spec.rb +23 -0
- data/spec/api/list_source_docs_spec.rb +26 -0
- data/spec/api/list_sources_spec.rb +27 -0
- data/spec/api/list_users_spec.rb +21 -0
- data/spec/api/ping_spec.rb +24 -0
- data/spec/api/push_deletes_spec.rb +16 -0
- data/spec/api/push_objects_spec.rb +27 -0
- data/spec/api/reset_spec.rb +22 -0
- data/spec/api/set_db_doc_spec.rb +20 -0
- data/spec/api/set_refresh_time_spec.rb +43 -0
- data/spec/api/update_user_spec.rb +31 -0
- data/spec/api/upload_file_spec.rb +26 -0
- data/spec/api_token_spec.rb +13 -0
- data/spec/app_spec.rb +20 -0
- data/spec/apps/rhotestapp/Rakefile +1 -0
- data/spec/apps/rhotestapp/application.rb +16 -0
- data/spec/apps/rhotestapp/config.ru +1 -0
- data/spec/apps/rhotestapp/settings/apple_fake_cert.pem +1 -0
- data/spec/apps/rhotestapp/settings/license.key +1 -0
- data/spec/apps/rhotestapp/settings/settings.yml +23 -0
- data/spec/apps/rhotestapp/sources/base_adapter.rb +9 -0
- data/spec/apps/rhotestapp/sources/sample_adapter.rb +66 -0
- data/spec/apps/rhotestapp/sources/simple_adapter.rb +39 -0
- data/spec/apps/rhotestapp/sources/sub_adapter.rb +7 -0
- data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem/mygem.rb +8 -0
- data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem.rb +1 -0
- data/spec/bulk_data/bulk_data_spec.rb +79 -0
- data/spec/client_spec.rb +58 -0
- data/spec/client_sync_spec.rb +377 -0
- data/spec/doc/base.html +72 -0
- data/spec/doc/doc_spec.rb +303 -0
- data/spec/doc/footer.html +4 -0
- data/spec/doc/header.html +30 -0
- data/spec/document_spec.rb +27 -0
- data/spec/generator/generator_spec.rb +53 -0
- data/spec/generator/generator_spec_helper.rb +8 -0
- data/spec/jobs/bulk_data_job_spec.rb +76 -0
- data/spec/jobs/ping_job_spec.rb +26 -0
- data/spec/jobs/source_job_spec.rb +25 -0
- data/spec/license_spec.rb +48 -0
- data/spec/model_spec.rb +269 -0
- data/spec/perf/bulk_data_perf_spec.rb +33 -0
- data/spec/perf/perf_spec_helper.rb +51 -0
- data/spec/perf/store_perf_spec.rb +28 -0
- data/spec/ping/blackberry_spec.rb +62 -0
- data/spec/ping/iphone_spec.rb +50 -0
- data/spec/read_state_spec.rb +25 -0
- data/spec/rhosync_spec.rb +43 -0
- data/spec/server/server_spec.rb +341 -0
- data/spec/source_adapter_spec.rb +114 -0
- data/spec/source_spec.rb +77 -0
- data/spec/source_sync_spec.rb +248 -0
- data/spec/spec_helper.rb +240 -0
- data/spec/store_spec.rb +149 -0
- data/spec/sync_states_spec.rb +101 -0
- data/spec/testdata/1000-data.txt +1414 -0
- data/spec/testdata/compressed/compress-data.txt +1 -0
- data/spec/testdata/upload1.txt +1 -0
- data/spec/testdata/upload2.txt +1 -0
- data/spec/user_spec.rb +79 -0
- data/tasks/redis.rake +134 -0
- metadata +545 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..')
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'rhosync'
|
|
7
|
+
|
|
8
|
+
module Rhosync
|
|
9
|
+
|
|
10
|
+
class ApiException < Exception
|
|
11
|
+
attr_accessor :error_code
|
|
12
|
+
def initialize(error_code,message)
|
|
13
|
+
super(message)
|
|
14
|
+
@error_code = error_code
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Server < Sinatra::Base
|
|
19
|
+
libdir = File.dirname(File.expand_path(__FILE__))
|
|
20
|
+
set :views, "#{libdir}/server/views"
|
|
21
|
+
set :public, "#{libdir}/server/public"
|
|
22
|
+
set :static, true
|
|
23
|
+
|
|
24
|
+
set :secret, '<changeme>' unless defined? Server.secret
|
|
25
|
+
|
|
26
|
+
use Rack::Session::Cookie, :key => 'rhosync_session',
|
|
27
|
+
:expire_after => 31536000,
|
|
28
|
+
:secret => Server.secret
|
|
29
|
+
|
|
30
|
+
# Setup route and mimetype for bulk data downloads
|
|
31
|
+
# TODO: Figure out why "mime :data, 'application/octet-stream'" doesn't work
|
|
32
|
+
Rack::Mime::MIME_TYPES['.data'] = 'application/octet-stream'
|
|
33
|
+
|
|
34
|
+
include Rhosync
|
|
35
|
+
|
|
36
|
+
helpers do
|
|
37
|
+
def request_action
|
|
38
|
+
request.env['PATH_INFO'].split('/').last
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def check_api_token
|
|
42
|
+
request_action == 'get_api_token' or
|
|
43
|
+
(params[:api_token] and ApiToken.is_exist?(params[:api_token]))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def do_login
|
|
47
|
+
login ? status(200) : status(401)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def login_required
|
|
51
|
+
current_user.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def login
|
|
55
|
+
if current_app and current_app.can_authenticate?
|
|
56
|
+
user = current_app.authenticate(params[:login], params[:password], session)
|
|
57
|
+
else
|
|
58
|
+
user = User.authenticate(params[:login], params[:password])
|
|
59
|
+
end
|
|
60
|
+
if user
|
|
61
|
+
session[:login] = user.login
|
|
62
|
+
session[:app_name] = APP_NAME
|
|
63
|
+
true
|
|
64
|
+
else
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def logout
|
|
70
|
+
session[:login] = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def current_user
|
|
74
|
+
if @user.nil? and User.is_exist?(session[:login])
|
|
75
|
+
@user = User.load(session[:login])
|
|
76
|
+
end
|
|
77
|
+
if @user and (@user.admin == 1 || session[:app_name] == APP_NAME)
|
|
78
|
+
@user
|
|
79
|
+
else
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def api_user
|
|
85
|
+
request_action == 'get_api_token' ? current_user : ApiToken.load(params[:api_token]).user
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def current_app
|
|
89
|
+
App.load(APP_NAME)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def current_source
|
|
93
|
+
return @source if @source
|
|
94
|
+
user = current_user
|
|
95
|
+
if params[:source_name] and user
|
|
96
|
+
@source = Source.load(params[:source_name],
|
|
97
|
+
{:user_id => user.login,:app_id => APP_NAME})
|
|
98
|
+
else
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def current_client
|
|
104
|
+
if @client.nil? and params[:client_id]
|
|
105
|
+
@client = Client.load(params[:client_id].to_s,
|
|
106
|
+
params[:source_name] ? {:source_name => current_source.name} : {:source_name => '*'})
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def source_config
|
|
111
|
+
{ "sources" => Rhosync.get_config(Rhosync.base_directory)[:sources] }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def catch_all
|
|
115
|
+
begin
|
|
116
|
+
yield
|
|
117
|
+
rescue Exception => e
|
|
118
|
+
#log e.message + e.backtrace.join("\n")
|
|
119
|
+
throw :halt, [500, e.message]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def initialize
|
|
125
|
+
# Whine about default session secret
|
|
126
|
+
check_default_secret!(Server.secret)
|
|
127
|
+
super
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
Rhosync.log "Rhosync Server v#{Rhosync::VERSION} started..."
|
|
131
|
+
|
|
132
|
+
before do
|
|
133
|
+
if params["cud"]
|
|
134
|
+
cud = JSON.parse(params["cud"])
|
|
135
|
+
params.delete("cud")
|
|
136
|
+
params.merge!(cud)
|
|
137
|
+
end
|
|
138
|
+
if request.env['CONTENT_TYPE'] == 'application/json'
|
|
139
|
+
params.merge!(JSON.parse(request.body.read))
|
|
140
|
+
request.body.rewind
|
|
141
|
+
end
|
|
142
|
+
if params[:version] and params[:version].to_i < 3
|
|
143
|
+
throw :halt, [404, "Server supports version 3 or higher of the protocol."]
|
|
144
|
+
end
|
|
145
|
+
#log "request params: #{params.inspect}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
%w[get post].each do |verb|
|
|
149
|
+
send(verb, "/application*") do
|
|
150
|
+
unless request_action == 'clientlogin'
|
|
151
|
+
throw :halt, [401, "Not authenticated"] if login_required
|
|
152
|
+
end
|
|
153
|
+
pass
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
get '/' do
|
|
158
|
+
erb :index
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Collection routes
|
|
162
|
+
post '/login' do
|
|
163
|
+
logout
|
|
164
|
+
do_login
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
post '/application/clientlogin' do
|
|
168
|
+
logout
|
|
169
|
+
do_login
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
get '/application/clientcreate' do
|
|
173
|
+
content_type :json
|
|
174
|
+
client = Client.create(:user_id => current_user.id,:app_id => current_app.id)
|
|
175
|
+
client.update_fields(params)
|
|
176
|
+
{ "client" => { "client_id" => client.id.to_s } }.merge!(source_config).to_json
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
post '/application/clientregister' do
|
|
180
|
+
current_client.update_fields(params)
|
|
181
|
+
source_config.to_json
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
get '/application/clientreset' do
|
|
185
|
+
ClientSync.reset(current_client)
|
|
186
|
+
source_config.to_json
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Member routes
|
|
190
|
+
get '/application' do
|
|
191
|
+
catch_all do
|
|
192
|
+
content_type :json
|
|
193
|
+
cs = ClientSync.new(current_source,current_client,params[:p_size])
|
|
194
|
+
res = cs.send_cud(params[:token],params[:query]).to_json
|
|
195
|
+
res
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
post '/application' do
|
|
200
|
+
catch_all do
|
|
201
|
+
cs = ClientSync.new(current_source,current_client,params[:p_size])
|
|
202
|
+
cs.receive_cud(params)
|
|
203
|
+
status 200
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
get '/application/bulk_data' do
|
|
208
|
+
catch_all do
|
|
209
|
+
content_type :json
|
|
210
|
+
data = ClientSync.bulk_data(params[:partition].to_sym,current_client)
|
|
211
|
+
data.to_json
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
get '/application/search' do
|
|
216
|
+
catch_all do
|
|
217
|
+
content_type :json
|
|
218
|
+
ClientSync.search_all(current_client,params).to_json
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def self.api(name)
|
|
223
|
+
post "/api/#{name}" do
|
|
224
|
+
if check_api_token
|
|
225
|
+
begin
|
|
226
|
+
yield params,api_user
|
|
227
|
+
rescue ApiException => ae
|
|
228
|
+
throw :halt, [ae.error_code, ae.message]
|
|
229
|
+
rescue Exception => e
|
|
230
|
+
# log e.message + "\n" + e.backtrace.join("\n")
|
|
231
|
+
throw :halt, [500, e.message]
|
|
232
|
+
end
|
|
233
|
+
else
|
|
234
|
+
throw :halt, [422, "No API token provided"]
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
include Rhosync
|
|
242
|
+
Dir[File.join(File.dirname(__FILE__),'api','**','*.rb')].each { |api| load api }
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Rhosync
|
|
2
|
+
class Source < Model
|
|
3
|
+
field :source_id,:integer
|
|
4
|
+
field :name,:string
|
|
5
|
+
field :url,:string
|
|
6
|
+
field :login,:string
|
|
7
|
+
field :password,:string
|
|
8
|
+
field :priority,:integer
|
|
9
|
+
field :callback_url,:string
|
|
10
|
+
field :poll_interval,:integer
|
|
11
|
+
field :partition_type,:string
|
|
12
|
+
field :sync_type,:string
|
|
13
|
+
field :queue,:string
|
|
14
|
+
field :query_queue,:string
|
|
15
|
+
field :cud_queue,:string
|
|
16
|
+
attr_accessor :app_id, :user_id
|
|
17
|
+
validates_presence_of :name #, :source_id
|
|
18
|
+
|
|
19
|
+
include Document
|
|
20
|
+
include LockOps
|
|
21
|
+
|
|
22
|
+
def self.set_defaults(fields)
|
|
23
|
+
fields[:url] ||= ''
|
|
24
|
+
fields[:login] ||= ''
|
|
25
|
+
fields[:password] ||= ''
|
|
26
|
+
fields[:priority] ||= 3
|
|
27
|
+
fields[:partition_type] ||= :user
|
|
28
|
+
fields[:poll_interval] ||= 300
|
|
29
|
+
fields[:sync_type] ||= :incremental
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.create(fields,params)
|
|
33
|
+
fields = fields.with_indifferent_access # so we can access hash keys as symbols
|
|
34
|
+
# validate_attributes(params)
|
|
35
|
+
fields[:id] = fields[:name]
|
|
36
|
+
set_defaults(fields)
|
|
37
|
+
super(fields,params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.load(id,params)
|
|
41
|
+
validate_attributes(params)
|
|
42
|
+
super(id,params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def update(fields)
|
|
46
|
+
self.class.set_defaults(fields)
|
|
47
|
+
super(fields)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def clone(src_doctype,dst_doctype)
|
|
51
|
+
Store.clone(docname(src_doctype),docname(dst_doctype))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Return the user associated with a source
|
|
55
|
+
def user
|
|
56
|
+
@user ||= User.load(self.user_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Return the app the source belongs to
|
|
60
|
+
def app
|
|
61
|
+
@app ||= App.load(self.app_id)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def read_state
|
|
65
|
+
id = {:app_id => self.app_id,:user_id => user_by_partition,
|
|
66
|
+
:source_name => self.name}
|
|
67
|
+
@read_state ||= ReadState.load(id)
|
|
68
|
+
@read_state ||= ReadState.create(id)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def doc_suffix(doctype)
|
|
72
|
+
"#{user_by_partition}:#{self.name}:#{doctype.to_s}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def delete
|
|
76
|
+
flash_data('*')
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def partition
|
|
81
|
+
self.partition_type.to_sym
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def partition=(value)
|
|
85
|
+
self.partition_type = value
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def user_by_partition
|
|
89
|
+
self.partition.to_sym == :user ? self.user_id : '__shared__'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def check_refresh_time
|
|
93
|
+
self.poll_interval == 0 or
|
|
94
|
+
(self.poll_interval != -1 and self.read_state.refresh_time <= Time.now.to_i)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def if_need_refresh(client_id=nil,params=nil)
|
|
98
|
+
need_refresh = lock(:md) do |s|
|
|
99
|
+
check = check_refresh_time
|
|
100
|
+
s.read_state.refresh_time = Time.now.to_i + s.poll_interval if check
|
|
101
|
+
check
|
|
102
|
+
end
|
|
103
|
+
yield client_id,params if need_refresh
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
def self.validate_attributes(params)
|
|
108
|
+
raise ArgumentError.new('Missing required attribute user_id') unless params[:user_id]
|
|
109
|
+
raise ArgumentError.new('Missing required attribute app_id') unless params[:app_id]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Rhosync
|
|
2
|
+
class SourceAdapterException < RuntimeError; end
|
|
3
|
+
|
|
4
|
+
# raise this to cause client to be logged out during a sync
|
|
5
|
+
class SourceAdapterLoginException < SourceAdapterException; end
|
|
6
|
+
|
|
7
|
+
class SourceAdapterLogoffException < SourceAdapterException; end
|
|
8
|
+
|
|
9
|
+
# raise these to trigger rhosync sending an error to the client
|
|
10
|
+
class SourceAdapterServerTimeoutException < SourceAdapterException; end
|
|
11
|
+
class SourceAdapterServerErrorException < SourceAdapterException; end
|
|
12
|
+
|
|
13
|
+
class SourceAdapter
|
|
14
|
+
attr_accessor :session
|
|
15
|
+
|
|
16
|
+
def initialize(source,credential=nil)
|
|
17
|
+
@source = source
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns an instance of a SourceAdapter by source name
|
|
21
|
+
def self.create(source,credential=nil)
|
|
22
|
+
adapter=nil
|
|
23
|
+
if source
|
|
24
|
+
begin
|
|
25
|
+
source.name.strip! if source.name
|
|
26
|
+
require under_score(source.name)
|
|
27
|
+
adapter=(Object.const_get(source.name)).new(source,credential)
|
|
28
|
+
rescue Exception=>e
|
|
29
|
+
log "Failure to create adapter from class #{source.name}: #{e.inspect.to_s}"
|
|
30
|
+
raise e
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
adapter
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def login; end
|
|
37
|
+
|
|
38
|
+
def query(params=nil); end
|
|
39
|
+
|
|
40
|
+
def search(params=nil); end
|
|
41
|
+
|
|
42
|
+
def sync
|
|
43
|
+
return if _result_nil?
|
|
44
|
+
if @result.empty?
|
|
45
|
+
@source.lock(:md) do |s|
|
|
46
|
+
s.flash_data(:md)
|
|
47
|
+
s.put_value(:md_size,0)
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
tmp_docname = @source.docname(:md) + get_random_uuid
|
|
51
|
+
Store.put_data(tmp_docname,@result)
|
|
52
|
+
@source.lock(:md) do |s|
|
|
53
|
+
Store.rename(tmp_docname,s.docname(:md))
|
|
54
|
+
s.put_value(:md_size,@result.size)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create(name_value_list); end
|
|
60
|
+
|
|
61
|
+
def update(name_value_list); end
|
|
62
|
+
|
|
63
|
+
def delete(name_value_list); end
|
|
64
|
+
|
|
65
|
+
def ask(params=nil); end
|
|
66
|
+
|
|
67
|
+
def logoff; end
|
|
68
|
+
|
|
69
|
+
def save(docname)
|
|
70
|
+
return if _result_nil?
|
|
71
|
+
if @result.empty?
|
|
72
|
+
Store.flash_data(docname)
|
|
73
|
+
else
|
|
74
|
+
Store.put_data(docname,@result)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# only implement this if you want RhoSync to install a callback into your backend
|
|
79
|
+
# def set_callback(notify_url)
|
|
80
|
+
# end
|
|
81
|
+
|
|
82
|
+
MSG_NIL_RESULT_ATTRIB = "You might have expected a synchronization but the @result attribute was 'nil'"
|
|
83
|
+
|
|
84
|
+
protected
|
|
85
|
+
def current_user
|
|
86
|
+
@source.user
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
def _result_nil? #:nodoc:
|
|
91
|
+
log MSG_NIL_RESULT_ATTRIB if @result.nil?
|
|
92
|
+
@result.nil?
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
module Rhosync
|
|
2
|
+
class SourceSync
|
|
3
|
+
attr_reader :adapter
|
|
4
|
+
|
|
5
|
+
def initialize(source)
|
|
6
|
+
@source = source
|
|
7
|
+
raise InvalidArgumentError.new('Invalid source') if @source.nil?
|
|
8
|
+
raise InvalidArgumentError.new('Invalid app for source') unless @source.app
|
|
9
|
+
@adapter = SourceAdapter.create(@source)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# CUD Operations
|
|
13
|
+
def create(client_id)
|
|
14
|
+
_process_cud('create',client_id)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update(client_id)
|
|
18
|
+
_process_cud('update',client_id)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete(client_id)
|
|
22
|
+
_process_cud('delete',client_id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Read Operation; params are query arguments
|
|
26
|
+
def read(client_id=nil,params=nil)
|
|
27
|
+
_read('query',client_id,params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def search(client_id=nil,params=nil)
|
|
31
|
+
return if _auth_op('login',client_id) == false
|
|
32
|
+
res = _read('search',client_id,params)
|
|
33
|
+
_auth_op('logoff',client_id)
|
|
34
|
+
res
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def process_cud(client_id)
|
|
38
|
+
if @source.cud_queue or @source.queue
|
|
39
|
+
async(:cud,@source.cud_queue || @source.queue,client_id)
|
|
40
|
+
else
|
|
41
|
+
do_cud(client_id)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def do_cud(client_id)
|
|
46
|
+
return if _auth_op('login') == false
|
|
47
|
+
self.create(client_id)
|
|
48
|
+
self.update(client_id)
|
|
49
|
+
self.delete(client_id)
|
|
50
|
+
_auth_op('logoff')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def process_query(params=nil)
|
|
54
|
+
if @source.query_queue or @source.queue
|
|
55
|
+
async(:query,@source.query_queue || @source.queue,nil,params)
|
|
56
|
+
else
|
|
57
|
+
do_query(params)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def do_query(params=nil)
|
|
62
|
+
@source.if_need_refresh do
|
|
63
|
+
return if _auth_op('login') == false
|
|
64
|
+
self.read(nil,params)
|
|
65
|
+
_auth_op('logoff')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Enqueue a job for the source based on job type
|
|
70
|
+
def async(job_type,queue_name,client_id=nil,params=nil)
|
|
71
|
+
SourceJob.queue = queue_name
|
|
72
|
+
Resque.enqueue(SourceJob,job_type,@source.id,
|
|
73
|
+
@source.app_id,@source.user_id,client_id,params)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def push_objects(objects)
|
|
77
|
+
@source.lock(:md) do |s|
|
|
78
|
+
doc = @source.get_data(:md)
|
|
79
|
+
objects.each do |id,obj|
|
|
80
|
+
doc[id] ||= {}
|
|
81
|
+
doc[id].merge!(obj)
|
|
82
|
+
end
|
|
83
|
+
@source.put_data(:md,doc)
|
|
84
|
+
@source.update_count(:md_size,doc.size)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def push_deletes(objects)
|
|
89
|
+
@source.lock(:md) do |s|
|
|
90
|
+
doc = @source.get_data(:md)
|
|
91
|
+
objects.each do |id|
|
|
92
|
+
doc.delete(id)
|
|
93
|
+
end
|
|
94
|
+
@source.put_data(:md,doc)
|
|
95
|
+
@source.update_count(:md_size,doc.size)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
def _auth_op(operation,client_id=-1)
|
|
101
|
+
edockey = client_id == -1 ? @source.docname(:errors) :
|
|
102
|
+
Client.load(client_id,{:source_name => @source.name}).docname(:search_errors)
|
|
103
|
+
begin
|
|
104
|
+
Store.flash_data(edockey) if operation == 'login'
|
|
105
|
+
@adapter.send operation
|
|
106
|
+
rescue Exception => e
|
|
107
|
+
log "SourceAdapter raised #{operation} exception: #{e}"
|
|
108
|
+
Store.put_data(edockey,{"#{operation}-error"=>{'message'=>e.message}},true)
|
|
109
|
+
return false
|
|
110
|
+
end
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def _process_create(client_id,key,value,links,creates,deletes)
|
|
115
|
+
# Perform operation
|
|
116
|
+
link = @adapter.create value
|
|
117
|
+
# Store object-id link for the client
|
|
118
|
+
# If we have a link, store object in client document
|
|
119
|
+
# Otherwise, store object for delete on client
|
|
120
|
+
if link
|
|
121
|
+
links ||= {}
|
|
122
|
+
links[key] = { 'l' => link.to_s }
|
|
123
|
+
creates ||= {}
|
|
124
|
+
creates[link.to_s] = value
|
|
125
|
+
else
|
|
126
|
+
deletes ||= {}
|
|
127
|
+
deletes[key] = value
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def _process_update(client_id,key,value)
|
|
132
|
+
# Add id to object hash to forward to backend call
|
|
133
|
+
value['id'] = key
|
|
134
|
+
# Perform operation
|
|
135
|
+
@adapter.update value
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def _process_delete(client_id,key,value,dels)
|
|
139
|
+
value['id'] = key
|
|
140
|
+
# Perform operation
|
|
141
|
+
@adapter.delete value
|
|
142
|
+
dels ||= {}
|
|
143
|
+
dels[key] = value
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def _process_cud(operation,client_id)
|
|
147
|
+
errors,links,deletes,creates,dels = {},{},{},{},{}
|
|
148
|
+
client = Client.load(client_id,{:source_name => @source.name})
|
|
149
|
+
modified = client.get_data(operation)
|
|
150
|
+
# Process operation queue, one object at a time
|
|
151
|
+
modified.each do |key,value|
|
|
152
|
+
begin
|
|
153
|
+
# Remove object from queue
|
|
154
|
+
modified.delete(key)
|
|
155
|
+
# Call on source adapter to process individual object
|
|
156
|
+
case operation
|
|
157
|
+
when 'create'
|
|
158
|
+
_process_create(client_id,key,value,links,creates,deletes)
|
|
159
|
+
when 'update'
|
|
160
|
+
_process_update(client_id,key,value)
|
|
161
|
+
when 'delete'
|
|
162
|
+
_process_delete(client_id,key,value,dels)
|
|
163
|
+
end
|
|
164
|
+
rescue Exception => e
|
|
165
|
+
log "SourceAdapter raised #{operation} exception: #{e}"
|
|
166
|
+
errors ||= {}
|
|
167
|
+
errors[key] = value
|
|
168
|
+
errors["#{key}-error"] = {'message'=>e.message}
|
|
169
|
+
break
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
# Record operation results
|
|
173
|
+
{ "delete_page" => deletes,
|
|
174
|
+
"#{operation}_links" => links,
|
|
175
|
+
"#{operation}_errors" => errors }.each do |doctype,value|
|
|
176
|
+
client.put_data(doctype,value,true) unless value.empty?
|
|
177
|
+
end
|
|
178
|
+
unless operation != 'create' and creates.empty?
|
|
179
|
+
client.put_data(:cd,creates,true)
|
|
180
|
+
client.update_count(:cd_size,creates.size)
|
|
181
|
+
@source.lock(:md) do |s|
|
|
182
|
+
s.put_data(:md,creates,true)
|
|
183
|
+
s.update_count(:md_size,creates.size)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
if operation == 'delete'
|
|
187
|
+
# Clean up deleted objects from master document and corresponding client document
|
|
188
|
+
client.delete_data(:cd,dels)
|
|
189
|
+
client.update_count(:cd_size,-dels.size)
|
|
190
|
+
@source.lock(:md) do |s|
|
|
191
|
+
s.delete_data(:md,dels)
|
|
192
|
+
s.update_count(:md_size,-dels.size)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
# Record rest of queue (if something in the middle failed)
|
|
196
|
+
if modified.empty?
|
|
197
|
+
client.flash_data(operation)
|
|
198
|
+
else
|
|
199
|
+
client.put_data(operation,modified)
|
|
200
|
+
end
|
|
201
|
+
modified.size
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Metadata Operation; source adapter returns json
|
|
205
|
+
def _get_metadata
|
|
206
|
+
if @adapter.respond_to?(:metadata)
|
|
207
|
+
metadata = @adapter.metadata
|
|
208
|
+
if metadata
|
|
209
|
+
@source.put_value(:metadata,metadata)
|
|
210
|
+
@source.put_value(:metadata_sha1,Digest::SHA1.hexdigest(metadata))
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Read Operation; params are query arguments
|
|
216
|
+
def _read(operation,client_id,params=nil)
|
|
217
|
+
errordoc = nil
|
|
218
|
+
begin
|
|
219
|
+
if operation == 'search'
|
|
220
|
+
client = Client.load(client_id,{:source_name => @source.name})
|
|
221
|
+
errordoc = client.docname(:search_errors)
|
|
222
|
+
compute_token(client.docname(:search_token))
|
|
223
|
+
@adapter.search(params)
|
|
224
|
+
@adapter.save(client.docname(:search))
|
|
225
|
+
else
|
|
226
|
+
errordoc = @source.docname(:errors)
|
|
227
|
+
_get_metadata
|
|
228
|
+
params ? @adapter.query(params) : @adapter.query
|
|
229
|
+
@adapter.sync
|
|
230
|
+
end
|
|
231
|
+
# operation,sync succeeded, remove errors
|
|
232
|
+
Store.lock(errordoc) do
|
|
233
|
+
Store.flash_data(errordoc)
|
|
234
|
+
end
|
|
235
|
+
rescue Exception => e
|
|
236
|
+
# store sync,operation exceptions to be sent to all clients for this source/user
|
|
237
|
+
log "SourceAdapter raised #{operation} exception: #{e}"
|
|
238
|
+
Store.lock(errordoc) do
|
|
239
|
+
Store.put_data(errordoc,{"#{operation}-error"=>{'message'=>e.message}},true)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
true
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|