rhosync 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/CHANGELOG +5 -0
  2. data/LICENSE +674 -0
  3. data/README.md +26 -0
  4. data/Rakefile +109 -0
  5. data/bench/bench +6 -0
  6. data/bench/benchapp/Rakefile +14 -0
  7. data/bench/benchapp/application.rb +13 -0
  8. data/bench/benchapp/config.ru +32 -0
  9. data/bench/benchapp/settings/license.key +1 -0
  10. data/bench/benchapp/settings/settings.yml +18 -0
  11. data/bench/benchapp/sources/mock_adapter.rb +55 -0
  12. data/bench/benchapp/sources/queue_mock_adapter.rb +2 -0
  13. data/bench/benchapp/vendor/rhosync/lib/rhosync.rb +7 -0
  14. data/bench/lib/bench/cli.rb +16 -0
  15. data/bench/lib/bench/logging.rb +18 -0
  16. data/bench/lib/bench/mock_client.rb +41 -0
  17. data/bench/lib/bench/result.rb +50 -0
  18. data/bench/lib/bench/runner.rb +44 -0
  19. data/bench/lib/bench/session.rb +65 -0
  20. data/bench/lib/bench/statistics.rb +56 -0
  21. data/bench/lib/bench/test_data.rb +55 -0
  22. data/bench/lib/bench/timer.rb +10 -0
  23. data/bench/lib/bench/utils.rb +49 -0
  24. data/bench/lib/bench.rb +128 -0
  25. data/bench/lib/testdata/100-data.txt +148 -0
  26. data/bench/lib/testdata/5-data.txt +11 -0
  27. data/bench/scripts/cud_script.rb +77 -0
  28. data/bench/scripts/helpers.rb +101 -0
  29. data/bench/scripts/query_md_script.rb +46 -0
  30. data/bench/scripts/query_script.rb +46 -0
  31. data/bench/spec/bench_spec_helper.rb +65 -0
  32. data/bench/spec/logging_spec.rb +19 -0
  33. data/bench/spec/mock_adapter_spec.rb +61 -0
  34. data/bench/spec/mock_client_spec.rb +64 -0
  35. data/bench/spec/result_spec.rb +59 -0
  36. data/bench/spec/utils_spec.rb +35 -0
  37. data/bin/rhosync +34 -0
  38. data/doc/protocol.html +1901 -0
  39. data/doc/public/css/print.css +29 -0
  40. data/doc/public/css/screen.css +257 -0
  41. data/doc/public/css/style.css +20 -0
  42. data/examples/simple/application.rb +13 -0
  43. data/examples/simple/sources/sample_adapter.rb +5 -0
  44. data/examples/simple/sources/simple_adapter.rb +5 -0
  45. data/examples/simple/vendor/rhosync/lib/rhosync.rb +7 -0
  46. data/generators/rhosync.rb +98 -0
  47. data/generators/templates/application/Rakefile +19 -0
  48. data/generators/templates/application/application.rb +27 -0
  49. data/generators/templates/application/config.ru +33 -0
  50. data/generators/templates/application/settings/license.key +1 -0
  51. data/generators/templates/application/settings/settings.yml +14 -0
  52. data/generators/templates/source/source_adapter.rb +49 -0
  53. data/lib/rhosync/api/create_client.rb +3 -0
  54. data/lib/rhosync/api/create_user.rb +7 -0
  55. data/lib/rhosync/api/delete_client.rb +5 -0
  56. data/lib/rhosync/api/delete_user.rb +5 -0
  57. data/lib/rhosync/api/get_api_token.rb +7 -0
  58. data/lib/rhosync/api/get_client_params.rb +3 -0
  59. data/lib/rhosync/api/get_db_doc.rb +7 -0
  60. data/lib/rhosync/api/get_license_info.rb +7 -0
  61. data/lib/rhosync/api/get_source_params.rb +3 -0
  62. data/lib/rhosync/api/list_client_docs.rb +12 -0
  63. data/lib/rhosync/api/list_clients.rb +3 -0
  64. data/lib/rhosync/api/list_source_docs.rb +10 -0
  65. data/lib/rhosync/api/list_sources.rb +15 -0
  66. data/lib/rhosync/api/list_users.rb +3 -0
  67. data/lib/rhosync/api/ping.rb +7 -0
  68. data/lib/rhosync/api/push_deletes.rb +6 -0
  69. data/lib/rhosync/api/push_objects.rb +6 -0
  70. data/lib/rhosync/api/reset.rb +10 -0
  71. data/lib/rhosync/api/set_db_doc.rb +8 -0
  72. data/lib/rhosync/api/set_refresh_time.rb +8 -0
  73. data/lib/rhosync/api/update_user.rb +4 -0
  74. data/lib/rhosync/api/upload_file.rb +4 -0
  75. data/lib/rhosync/api_token.rb +19 -0
  76. data/lib/rhosync/app.rb +69 -0
  77. data/lib/rhosync/bulk_data/bulk_data.rb +75 -0
  78. data/lib/rhosync/bulk_data/syncdb.index.schema +3 -0
  79. data/lib/rhosync/bulk_data/syncdb.schema +37 -0
  80. data/lib/rhosync/bulk_data.rb +2 -0
  81. data/lib/rhosync/client.rb +74 -0
  82. data/lib/rhosync/client_sync.rb +296 -0
  83. data/lib/rhosync/console/app/helpers/auth_helper.rb +18 -0
  84. data/lib/rhosync/console/app/helpers/extensions.rb +19 -0
  85. data/lib/rhosync/console/app/helpers/helpers.rb +52 -0
  86. data/lib/rhosync/console/app/public/main.css +7 -0
  87. data/lib/rhosync/console/app/public/text.txt +0 -0
  88. data/lib/rhosync/console/app/routes/auth.rb +29 -0
  89. data/lib/rhosync/console/app/routes/client.rb +32 -0
  90. data/lib/rhosync/console/app/routes/docs.rb +84 -0
  91. data/lib/rhosync/console/app/routes/home.rb +22 -0
  92. data/lib/rhosync/console/app/routes/user.rb +63 -0
  93. data/lib/rhosync/console/app/views/client.erb +30 -0
  94. data/lib/rhosync/console/app/views/doc.erb +56 -0
  95. data/lib/rhosync/console/app/views/docs.erb +29 -0
  96. data/lib/rhosync/console/app/views/index.erb +50 -0
  97. data/lib/rhosync/console/app/views/layout.erb +12 -0
  98. data/lib/rhosync/console/app/views/newuser.erb +17 -0
  99. data/lib/rhosync/console/app/views/ping.erb +28 -0
  100. data/lib/rhosync/console/app/views/result.erb +11 -0
  101. data/lib/rhosync/console/app/views/user.erb +32 -0
  102. data/lib/rhosync/console/app/views/users.erb +14 -0
  103. data/lib/rhosync/console/rhosync_api.rb +102 -0
  104. data/lib/rhosync/console/server.rb +27 -0
  105. data/lib/rhosync/credential.rb +9 -0
  106. data/lib/rhosync/document.rb +43 -0
  107. data/lib/rhosync/indifferent_access.rb +132 -0
  108. data/lib/rhosync/jobs/bulk_data_job.rb +104 -0
  109. data/lib/rhosync/jobs/ping_job.rb +19 -0
  110. data/lib/rhosync/jobs/source_job.rb +16 -0
  111. data/lib/rhosync/license.rb +79 -0
  112. data/lib/rhosync/lock_ops.rb +11 -0
  113. data/lib/rhosync/model.rb +410 -0
  114. data/lib/rhosync/ping/blackberry.rb +55 -0
  115. data/lib/rhosync/ping/iphone.rb +44 -0
  116. data/lib/rhosync/ping.rb +2 -0
  117. data/lib/rhosync/read_state.rb +27 -0
  118. data/lib/rhosync/server/views/index.erb +12 -0
  119. data/lib/rhosync/server.rb +242 -0
  120. data/lib/rhosync/source.rb +112 -0
  121. data/lib/rhosync/source_adapter.rb +95 -0
  122. data/lib/rhosync/source_sync.rb +245 -0
  123. data/lib/rhosync/store.rb +199 -0
  124. data/lib/rhosync/tasks.rb +151 -0
  125. data/lib/rhosync/user.rb +83 -0
  126. data/lib/rhosync/version.rb +3 -0
  127. data/lib/rhosync.rb +251 -0
  128. data/spec/api/api_helper.rb +44 -0
  129. data/spec/api/create_client_spec.rb +13 -0
  130. data/spec/api/create_user_spec.rb +16 -0
  131. data/spec/api/delete_client_spec.rb +13 -0
  132. data/spec/api/delete_user_spec.rb +18 -0
  133. data/spec/api/get_api_token_spec.rb +25 -0
  134. data/spec/api/get_client_params_spec.rb +18 -0
  135. data/spec/api/get_db_doc_spec.rb +21 -0
  136. data/spec/api/get_license_info_spec.rb +16 -0
  137. data/spec/api/get_source_params_spec.rb +26 -0
  138. data/spec/api/list_client_docs_spec.rb +33 -0
  139. data/spec/api/list_clients_spec.rb +23 -0
  140. data/spec/api/list_source_docs_spec.rb +26 -0
  141. data/spec/api/list_sources_spec.rb +27 -0
  142. data/spec/api/list_users_spec.rb +21 -0
  143. data/spec/api/ping_spec.rb +24 -0
  144. data/spec/api/push_deletes_spec.rb +16 -0
  145. data/spec/api/push_objects_spec.rb +27 -0
  146. data/spec/api/reset_spec.rb +22 -0
  147. data/spec/api/set_db_doc_spec.rb +20 -0
  148. data/spec/api/set_refresh_time_spec.rb +43 -0
  149. data/spec/api/update_user_spec.rb +31 -0
  150. data/spec/api/upload_file_spec.rb +26 -0
  151. data/spec/api_token_spec.rb +13 -0
  152. data/spec/app_spec.rb +20 -0
  153. data/spec/apps/rhotestapp/Rakefile +1 -0
  154. data/spec/apps/rhotestapp/application.rb +16 -0
  155. data/spec/apps/rhotestapp/config.ru +1 -0
  156. data/spec/apps/rhotestapp/settings/apple_fake_cert.pem +1 -0
  157. data/spec/apps/rhotestapp/settings/license.key +1 -0
  158. data/spec/apps/rhotestapp/settings/settings.yml +23 -0
  159. data/spec/apps/rhotestapp/sources/base_adapter.rb +9 -0
  160. data/spec/apps/rhotestapp/sources/sample_adapter.rb +66 -0
  161. data/spec/apps/rhotestapp/sources/simple_adapter.rb +39 -0
  162. data/spec/apps/rhotestapp/sources/sub_adapter.rb +7 -0
  163. data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem/mygem.rb +8 -0
  164. data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem.rb +1 -0
  165. data/spec/bulk_data/bulk_data_spec.rb +79 -0
  166. data/spec/client_spec.rb +58 -0
  167. data/spec/client_sync_spec.rb +377 -0
  168. data/spec/doc/base.html +72 -0
  169. data/spec/doc/doc_spec.rb +303 -0
  170. data/spec/doc/footer.html +4 -0
  171. data/spec/doc/header.html +30 -0
  172. data/spec/document_spec.rb +27 -0
  173. data/spec/generator/generator_spec.rb +53 -0
  174. data/spec/generator/generator_spec_helper.rb +8 -0
  175. data/spec/jobs/bulk_data_job_spec.rb +76 -0
  176. data/spec/jobs/ping_job_spec.rb +26 -0
  177. data/spec/jobs/source_job_spec.rb +25 -0
  178. data/spec/license_spec.rb +48 -0
  179. data/spec/model_spec.rb +269 -0
  180. data/spec/perf/bulk_data_perf_spec.rb +33 -0
  181. data/spec/perf/perf_spec_helper.rb +51 -0
  182. data/spec/perf/store_perf_spec.rb +28 -0
  183. data/spec/ping/blackberry_spec.rb +62 -0
  184. data/spec/ping/iphone_spec.rb +50 -0
  185. data/spec/read_state_spec.rb +25 -0
  186. data/spec/rhosync_spec.rb +43 -0
  187. data/spec/server/server_spec.rb +341 -0
  188. data/spec/source_adapter_spec.rb +114 -0
  189. data/spec/source_spec.rb +77 -0
  190. data/spec/source_sync_spec.rb +248 -0
  191. data/spec/spec_helper.rb +240 -0
  192. data/spec/store_spec.rb +149 -0
  193. data/spec/sync_states_spec.rb +101 -0
  194. data/spec/testdata/1000-data.txt +1414 -0
  195. data/spec/testdata/compressed/compress-data.txt +1 -0
  196. data/spec/testdata/upload1.txt +1 -0
  197. data/spec/testdata/upload2.txt +1 -0
  198. data/spec/user_spec.rb +79 -0
  199. data/tasks/redis.rake +134 -0
  200. 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