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