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