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,3 @@
1
+ Rhosync::Server.api :list_clients do |params,user|
2
+ User.load(params[:user_id]).clients.members.to_json
3
+ end
@@ -0,0 +1,10 @@
1
+ Rhosync::Server.api :list_source_docs do |params,user|
2
+ res = {}
3
+ s = Source.load(params[:source_id], {:app_id => APP_NAME,:user_id => params[:user_id]})
4
+ [:md,:md_size,:md_copy,:errors].each do |doc|
5
+ db_key = s.docname(doc)
6
+ res.merge!(doc => db_key)
7
+ end
8
+ res.to_json
9
+ end
10
+
@@ -0,0 +1,15 @@
1
+ Rhosync::Server.api :list_sources do |params,user|
2
+ sources = App.load(APP_NAME).sources.members
3
+ if params[:partition_type].nil? or params[:partition_type] == 'all'
4
+ sources.to_json
5
+ else
6
+ res = []
7
+ sources.each do |name|
8
+ s = Source.load(name,{:app_id => APP_NAME,:user_id => '*'})
9
+ if s.partition_type and s.partition_type == params[:partition_type]
10
+ res << name
11
+ end
12
+ end
13
+ res.to_json
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ Rhosync::Server.api :list_users do |params,user|
2
+ App.load(APP_NAME).users.members.to_json
3
+ end
@@ -0,0 +1,7 @@
1
+ Rhosync::Server.api :ping do |params,user|
2
+ if params[:async]
3
+ PingJob.enqueue(params)
4
+ else
5
+ PingJob.perform(params)
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ Rhosync::Server.api :push_deletes do |params,user|
2
+ source = Source.load(params[:source_id],{:app_id=>APP_NAME,:user_id=>params[:user_id]})
3
+ source_sync = SourceSync.new(source)
4
+ source_sync.push_deletes(params[:objects])
5
+ 'done'
6
+ end
@@ -0,0 +1,6 @@
1
+ Rhosync::Server.api :push_objects do |params,user|
2
+ source = Source.load(params[:source_id],{:app_id=>APP_NAME,:user_id=>params[:user_id]})
3
+ source_sync = SourceSync.new(source)
4
+ source_sync.push_objects(params[:objects])
5
+ 'done'
6
+ end
@@ -0,0 +1,10 @@
1
+ Rhosync::Server.api :reset do |params,user|
2
+ Store.db.flushdb
3
+ app_klass = Object.const_get(camelize(APP_NAME))
4
+ if app_klass.singleton_methods.include?("initializer")
5
+ app_klass.send :initializer, Rhosync.base_directory
6
+ end
7
+ # restoring previous token value after flushdb
8
+ user.token = params[:api_token]
9
+ "DB reset"
10
+ end
@@ -0,0 +1,8 @@
1
+ Rhosync::Server.api :set_db_doc do |params,user|
2
+ if params[:data_type] and params[:data_type] == 'string'
3
+ Store.put_value(params[:doc],params[:data])
4
+ else
5
+ Store.put_data(params[:doc],params[:data])
6
+ end
7
+ ''
8
+ end
@@ -0,0 +1,8 @@
1
+ Rhosync::Server.api :set_refresh_time do |params,user|
2
+ source = Source.load(params[:source_name],
3
+ {:app_id => APP_NAME, :user_id => params[:user_name]})
4
+ source.poll_interval = params[:poll_interval] if params[:poll_interval]
5
+ params[:refresh_time] ||= 0
6
+ source.read_state.refresh_time = Time.now.to_i + params[:refresh_time].to_i
7
+ ''
8
+ end
@@ -0,0 +1,4 @@
1
+ Rhosync::Server.api :update_user do |params,user|
2
+ user.update(params[:attributes])
3
+ "User updated"
4
+ end
@@ -0,0 +1,4 @@
1
+ Rhosync::Server.api :upload_file do |params,user|
2
+ unzip_file(Rhosync.app_directory,params[:upload_file]) if File.exists?(Rhosync.app_directory)
3
+ "File uploaded"
4
+ end
@@ -0,0 +1,19 @@
1
+ require 'uuidtools'
2
+ module Rhosync
3
+ class ApiToken < Model
4
+ field :value,:string
5
+ field :user_id,:string
6
+ validates_presence_of :user_id
7
+
8
+ def self.create(fields)
9
+ fields[:value] = fields[:value] || get_random_uuid
10
+ fields[:id] = fields[:value]
11
+ object = super(fields)
12
+ end
13
+
14
+ def user
15
+ @user ||= User.load(self.user_id)
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,69 @@
1
+ module Rhosync
2
+ class App < Model
3
+ field :name, :string
4
+ set :users, :string
5
+ set :sources, :string
6
+ attr_reader :delegate
7
+ validates_presence_of :name
8
+
9
+ class << self
10
+ def create(fields={})
11
+ fields[:id] = fields[:name]
12
+ begin
13
+ require under_score(fields[:name])
14
+ rescue Exception; end
15
+ super(fields)
16
+ end
17
+ end
18
+
19
+ def can_authenticate?
20
+ self.delegate && self.delegate.singleton_methods.include?("authenticate")
21
+ end
22
+
23
+ def authenticate(login, password, session)
24
+ if self.delegate && self.delegate.authenticate(login, password, session)
25
+ user = User.load(login) if User.is_exist?(login)
26
+ if not user
27
+ user = User.create(:login => login)
28
+ users << user.id
29
+ end
30
+ return user
31
+ end
32
+ end
33
+
34
+ # TODO: Do we need this anymore?
35
+ # def delete
36
+ # sources.members.each do |source_name|
37
+ # Source.load(source_name,{:app_id => self.name,
38
+ # :user_id => '*'}).delete
39
+ # end
40
+ # users.members.each do |user_name|
41
+ # User.load(user_name).delete
42
+ # end
43
+ # ReadState.delete(self.name)
44
+ # super
45
+ # end
46
+
47
+ def delegate
48
+ @delegate.nil? ? Object.const_get(camelize(self.name)) : @delegate
49
+ end
50
+
51
+ def partition_sources(partition,user_id)
52
+ names = []
53
+ need_refresh = false
54
+ sources.members.each do |source|
55
+ s = Source.load(source,{:app_id => self.name,
56
+ :user_id => user_id})
57
+ if s.partition == partition
58
+ names << s.name
59
+ need_refresh = true if !need_refresh and s.check_refresh_time
60
+ end
61
+ end
62
+ {:names => names,:need_refresh => need_refresh}
63
+ end
64
+
65
+ def store_blob(obj,field_name,blob)
66
+ self.delegate.send :store_blob, obj,field_name,blob
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,75 @@
1
+ require 'resque'
2
+ require 'rhosync/jobs/bulk_data_job'
3
+
4
+ module Rhosync
5
+ class BulkData < Model
6
+ field :name, :string
7
+ field :state, :string
8
+ field :app_id, :string
9
+ field :user_id, :string
10
+ field :refresh_time, :integer
11
+ field :dbfile,:string
12
+ set :sources, :string
13
+ validates_presence_of :app_id, :user_id, :sources
14
+
15
+ def completed?
16
+ if state.to_sym == :completed and
17
+ dbfile and File.exist?(dbfile)
18
+ return true
19
+ end
20
+ false
21
+ end
22
+
23
+ def delete
24
+ sources.members.each do |source|
25
+ s = Source.load(source,{:app_id => app_id, :user_id => user_id})
26
+ Store.flash_data(s.docname(:md_copy)) if s
27
+ end
28
+ super
29
+ end
30
+
31
+ def process_sources
32
+ sources.members.each do |source|
33
+ s = Source.load(source,{:app_id => app_id, :user_id => user_id})
34
+ if s
35
+ SourceSync.new(s).process_query(nil)
36
+ s.clone(:md,:md_copy) unless s.sync_type.to_sym == :bulk_sync_only
37
+ end
38
+ end
39
+ end
40
+
41
+ def url
42
+ dbfile
43
+ end
44
+
45
+ class << self
46
+ def create(fields={})
47
+ fields[:id] = fields[:name]
48
+ fields[:state] ||= :inprogress
49
+ fields[:sources] ||= []
50
+ super(fields)
51
+ end
52
+
53
+ def enqueue(params={})
54
+ Resque.enqueue(BulkDataJob,params)
55
+ end
56
+
57
+ def get_name(partition,client)
58
+ if partition == :user
59
+ File.join(client.app_id,client.user_id,client.user_id)
60
+ else
61
+ File.join(client.app_id,client.app_id)
62
+ end
63
+ end
64
+
65
+ def schema_file
66
+ File.join(File.dirname(__FILE__),'syncdb.schema')
67
+ end
68
+
69
+ def index_file
70
+ File.join(File.dirname(__FILE__),'syncdb.index.schema')
71
+ end
72
+ end
73
+ end
74
+ end
75
+
@@ -0,0 +1,3 @@
1
+ CREATE INDEX by_src_id on object_values (source_id);
2
+ CREATE UNIQUE INDEX by_src_object ON object_values (object, attrib, source_id);
3
+ CREATE INDEX by_src_value ON object_values (attrib, source_id, value);
@@ -0,0 +1,37 @@
1
+ CREATE TABLE client_info (
2
+ client_id VARCHAR(255) default NULL,
3
+ session VARCHAR(255) default NULL,
4
+ token VARCHAR(255) default NULL,
5
+ token_sent BIGINT default 0,
6
+ reset BIGINT default 0,
7
+ port VARCHAR(10) default NULL,
8
+ bulksync_state BIGINT default 0,
9
+ last_sync_success VARCHAR(100) default NULL);
10
+ CREATE TABLE object_values (
11
+ source_id BIGINT default NULL,
12
+ attrib varchar(255) default NULL,
13
+ object varchar(255) default NULL,
14
+ value varchar default NULL,
15
+ attrib_type varchar(255) default NULL);
16
+ CREATE TABLE changed_values (
17
+ source_id BIGINT default NULL,
18
+ attrib varchar(255) default NULL,
19
+ object varchar(255) default NULL,
20
+ value varchar default NULL,
21
+ attrib_type varchar(255) default NULL,
22
+ update_type varchar(255) default NULL,
23
+ sent BIGINT default 0);
24
+ CREATE TABLE sources (
25
+ source_id BIGINT PRIMARY KEY,
26
+ name VARCHAR(255) default NULL,
27
+ token BIGINT default NULL,
28
+ priority BIGINT,
29
+ partition VARCHAR(255),
30
+ sync_type VARCHAR(255),
31
+ last_updated BIGINT default 0,
32
+ last_inserted_size BIGINT default 0,
33
+ last_deleted_size BIGINT default 0,
34
+ last_sync_duration BIGINT default 0,
35
+ last_sync_success BIGINT default 0,
36
+ backend_refresh_time BIGINT default 0,
37
+ source_attribs varchar default NULL);
@@ -0,0 +1,2 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'bulk_data/bulk_data'
@@ -0,0 +1,74 @@
1
+ module Rhosync
2
+ class InvalidSourceNameError < RuntimeError; end
3
+
4
+ class Client < Model
5
+ field :device_type,:string
6
+ field :device_pin,:string
7
+ field :device_port,:string
8
+
9
+ field :user_id,:string
10
+ field :app_id,:string
11
+ attr_accessor :source_name
12
+ validates_presence_of :app_id, :user_id
13
+
14
+ include Document
15
+ include LockOps
16
+
17
+ def self.create(fields,params={})
18
+ Rhosync.license.check_and_use_seat
19
+ fields[:id] = get_random_uuid
20
+ res = super(fields,params)
21
+ user = User.load(fields[:user_id])
22
+ user.clients << res.id
23
+ res
24
+ end
25
+
26
+ def self.load(id,params)
27
+ validate_attributes(params)
28
+ super(id,params)
29
+ end
30
+
31
+ def app
32
+ @app ||= App.load(app_id)
33
+ end
34
+
35
+ def doc_suffix(doctype)
36
+ doctype = doctype.to_s
37
+ if doctype == '*'
38
+ "#{self.user_id}:#{self.id}:*"
39
+ elsif self.source_name
40
+ "#{self.user_id}:#{self.id}:#{self.source_name}:#{doctype}"
41
+ else
42
+ raise InvalidSourceNameError.new('Invalid Source Name For Client')
43
+ end
44
+ end
45
+
46
+ def delete
47
+ flash_data('*')
48
+ Rhosync.license.free_seat
49
+ super
50
+ end
51
+
52
+ def update_clientdoc(sources)
53
+ sources.each do |source|
54
+ s = Source.load(source,{:app_id => app_id,:user_id => user_id})
55
+ unless s.sync_type.to_sym == :bulk_sync_only
56
+ self.source_name = source
57
+ Store.clone(s.docname(:md_copy),self.docname(:cd))
58
+ end
59
+ end
60
+ end
61
+
62
+ def update_fields(params)
63
+ [:device_type,:device_pin,:device_port].each do |setting|
64
+ self.send "#{setting}=".to_sym, params[setting].to_s if params[setting]
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def self.validate_attributes(params)
71
+ raise ArgumentError.new('Missing required attribute source_name') unless params[:source_name]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,296 @@
1
+ module Rhosync
2
+ class ClientSync
3
+ attr_accessor :source,:client,:p_size,:source_sync
4
+
5
+ VERSION = 3
6
+
7
+ def initialize(source,client,p_size=nil)
8
+ raise ArgumentError.new('Missing required attribute client') unless client
9
+ raise ArgumentError.new('Missing required attribute source') unless source
10
+ @source,@client,@p_size = source,client,p_size ? p_size.to_i : 500
11
+ @source_sync = SourceSync.new(@source)
12
+ end
13
+
14
+ def receive_cud(cud_params={},query_params=nil)
15
+ _process_blobs(cud_params)
16
+ processed = 0
17
+ ['create','update','delete'].each do |op|
18
+ key,value = op,cud_params[op]
19
+ processed += _receive_cud(key,value) if value
20
+ end
21
+ @source_sync.process_cud(@client.id) if processed > 0
22
+ end
23
+
24
+ def send_cud(token=nil,query_params=nil)
25
+ res = []
26
+ if not _ack_token(token)
27
+ res = resend_page(token)
28
+ else
29
+ @source_sync.process_query(query_params)
30
+ res = send_new_page
31
+ end
32
+ _format_result(res[0],res[1],res[2],res[3])
33
+ end
34
+
35
+ def search(params)
36
+ res = []
37
+ return _resend_search_result(params[:search_token]) if params[:search_token] and params[:resend]
38
+ _ack_search(params[:search_token]) if params[:search_token]
39
+ res = _do_search(params[:search]) if params[:search]
40
+ res
41
+ end
42
+
43
+ def build_page
44
+ res = {}
45
+ yield res
46
+ res.reject! {|key,value| value.nil? or value.empty?}
47
+ res.merge!(_send_errors)
48
+ res
49
+ end
50
+
51
+ def send_new_page
52
+ compute_errors_page
53
+ token,progress_count,total_count = '',0,0
54
+ res = build_page do |r|
55
+ progress_count,total_count,r['insert'] = compute_page
56
+ r['delete'] = compute_deleted_page
57
+ r['links'] = compute_links_page
58
+ r['metadata'] = compute_metadata
59
+ end
60
+ if res['insert'] or res['delete'] or res['links']
61
+ token = compute_token(@client.docname(:page_token))
62
+ else
63
+ _delete_errors_page
64
+ end
65
+ @client.put_data(:cd,res['insert'],true)
66
+ @client.delete_data(:cd,res['delete'])
67
+ [token,progress_count,total_count,res]
68
+ end
69
+
70
+ # Resend token for a client, also sends exceptions
71
+ def resend_page(token=nil)
72
+ token,progress_count,total_count = '',0,0
73
+ res = build_page do |r|
74
+ r['insert'] = @client.get_data(:page)
75
+ r['delete'] = @client.get_data(:delete_page)
76
+ r['links'] = @client.get_data(:create_links_page)
77
+ r['metadata'] = compute_metadata
78
+ progress_count = @client.get_value(:cd_size).to_i
79
+ total_count = @client.get_value(:total_count_page).to_i
80
+ end
81
+ token = @client.get_value(:page_token)
82
+ [token,progress_count,total_count,res]
83
+ end
84
+
85
+ # Computes the metadata sha1 and returns metadata if client's sha1 doesn't
86
+ # match source's sha1
87
+ def compute_metadata
88
+ metadata_sha1,metadata = @source.lock(:metadata) do |s|
89
+ [s.get_value(:metadata_sha1),s.get_value(:metadata)]
90
+ end
91
+ return if @client.get_value(:metadata_sha1) == metadata_sha1
92
+ @client.put_value(:metadata_sha1,metadata_sha1)
93
+ metadata
94
+ end
95
+
96
+ # Computes diffs between master doc and client doc, trims it to page size,
97
+ # stores page, and returns page as hash
98
+ def compute_page
99
+ res,diffsize,total_count = @source.lock(:md) do |s|
100
+ res,diffsize = Store.get_diff_data(@client.docname(:cd),s.docname(:md),@p_size)
101
+ total_count = s.get_value(:md_size).to_i
102
+ [res,diffsize,total_count]
103
+ end
104
+ @client.put_data(:page,res)
105
+ progress_count = total_count - diffsize
106
+ @client.put_value(:cd_size,progress_count)
107
+ @client.put_value(:total_count_page,total_count)
108
+ [progress_count,total_count,res]
109
+ end
110
+
111
+ # Computes search hash
112
+ def compute_search
113
+ Store.get_diff_data(@client.docname(:cd),@client.docname(:search),@p_size)
114
+ end
115
+
116
+ # Computes deleted objects (down to individual attributes)
117
+ # in the client document, trims it to page size, stores page, and returns page as hash
118
+ def compute_deleted_page
119
+ res = {}
120
+ delete_page_doc = @client.docname(:delete_page)
121
+ page_size = @p_size
122
+ diff = @source.lock(:md) { |s| Store.get_diff_data(s.docname(:md),@client.docname(:cd))[0] }
123
+ diff.each do |key,value|
124
+ res[key] = value
125
+ value.each do |attrib,val|
126
+ Store.db.sadd(delete_page_doc,setelement(key,attrib,val))
127
+ end
128
+ page_size -= 1
129
+ break if page_size <= 0
130
+ end
131
+ res
132
+ end
133
+
134
+ # Computes errors for client and stores a copy as errors page
135
+ def compute_errors_page
136
+ ['create','update','delete'].each do |operation|
137
+ @client.lock("#{operation}_errors") do |c|
138
+ c.rename("#{operation}_errors","#{operation}_errors_page")
139
+ end
140
+ end
141
+ end
142
+
143
+ # Computes create links for a client and stores a copy as links page
144
+ def compute_links_page
145
+ @client.lock(:create_links) do |c|
146
+ c.rename(:create_links,:create_links_page)
147
+ c.get_data(:create_links_page)
148
+ end
149
+ end
150
+
151
+ class << self
152
+ # Resets the store for a given app,client
153
+ def reset(client)
154
+ client.flash_data('*') if client
155
+ end
156
+
157
+ def search_all(client,params=nil)
158
+ return [] unless params[:sources]
159
+ res = []
160
+ params[:sources].each do |source|
161
+ s = Source.load(source,{:app_id => client.app_id,
162
+ :user_id => client.user_id})
163
+ client.source_name = source
164
+ cs = ClientSync.new(s,client,params[:p_size])
165
+ search_res = cs.search(params)
166
+ res << search_res if search_res
167
+ end
168
+ res
169
+ end
170
+
171
+ def bulk_data(partition,client)
172
+ name = BulkData.get_name(partition,client)
173
+ data = BulkData.load(name)
174
+ sources = client.app.partition_sources(partition,client.user_id)
175
+ if (data.nil? or (data.completed? and sources[:need_refresh] == true)) and
176
+ sources[:names].length > 0
177
+ data.delete if data
178
+ data = BulkData.create(:name => name,
179
+ :app_id => client.app_id,
180
+ :user_id => client.user_id,
181
+ :sources => sources[:names])
182
+ BulkData.enqueue("data_name" => name)
183
+ end
184
+ if data and data.completed?
185
+ client.update_clientdoc(sources[:names])
186
+ {:result => :url, :url => data.url}
187
+ elsif data
188
+ {:result => :wait}
189
+ else
190
+ {:result => :nop}
191
+ end
192
+ end
193
+ end
194
+
195
+ private
196
+ def _resend_search_result(search_token)
197
+ _format_search_result
198
+ end
199
+
200
+ def _ack_search(search_token)
201
+ token = @client.get_value(:search_token)
202
+ if token == search_token
203
+ @client.flash_data('search*')
204
+ end
205
+ end
206
+
207
+ def _do_search(params)
208
+ @source_sync.search(@client.id,params)
209
+ _format_search_result
210
+ end
211
+
212
+ def _format_search_result
213
+ error = @client.get_data(:search_errors)
214
+ if not error.empty?
215
+ [ {'version'=>VERSION},
216
+ {'source'=>@source.name},
217
+ {'search-error'=>error} ]
218
+ else
219
+ search_token = @client.get_value(:search_token)
220
+ search_token ||= ''
221
+ res,diffsize = compute_search
222
+ return [] if res.empty?
223
+ [ {'version'=>VERSION},
224
+ {'search_token' => search_token},
225
+ {'source'=>@source.name},
226
+ {'count'=>res.size},
227
+ {'insert'=>res} ]
228
+ end
229
+ end
230
+
231
+ def _receive_cud(operation,params)
232
+ return 0 if not ['create','update','delete'].include?(operation)
233
+ @client.lock(operation) { |c| c.put_data(operation,params,true) }
234
+ return 1
235
+ end
236
+
237
+ def _process_blobs(params)
238
+ unless params[:blob_fields].nil?
239
+ [:create,:update].each do |utype|
240
+ objects = params[utype] || {}
241
+ objects.each do |id,obj|
242
+ params[:blob_fields].each do |field|
243
+ blob = params["#{field}-#{id}"]
244
+ obj[field] = @client.app.store_blob(obj,field,blob)
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ def _ack_token(token)
252
+ stored_token = @client.get_value(:page_token)
253
+ if stored_token
254
+ if token and stored_token == token
255
+ @client.put_value(:page_token,nil)
256
+ @client.flash_data(:create_links_page)
257
+ @client.flash_data(:page)
258
+ @client.flash_data(:delete_page)
259
+ _delete_errors_page
260
+ return true
261
+ end
262
+ else
263
+ return true
264
+ end
265
+ false
266
+ end
267
+
268
+ def _delete_errors_page
269
+ ['create','update','delete'].each do |operation|
270
+ @client.flash_data("#{operation}_errors_page")
271
+ end
272
+ end
273
+
274
+ def _send_errors
275
+ res = {}
276
+ ['create','update','delete'].each do |operation|
277
+ res["#{operation}-error"] = @client.get_data("#{operation}_errors_page")
278
+ end
279
+ res["source-error"] = @source.lock(:errors) { |s| s.get_data(:errors) }
280
+ res.reject! {|key,value| value.nil? or value.empty?}
281
+ res
282
+ end
283
+
284
+ def _format_result(token,progress_count,total_count,res)
285
+ count = 0
286
+ count += res['insert'].length if res['insert']
287
+ count += res['delete'].length if res['delete']
288
+ [ {'version'=>VERSION},
289
+ {'token'=>(token ? token : '')},
290
+ {'count'=>count},
291
+ {'progress_count'=>progress_count},
292
+ {'total_count'=>total_count},
293
+ res ]
294
+ end
295
+ end
296
+ end