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.
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