rhosync 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/LICENSE +674 -0
- data/README.md +26 -0
- data/Rakefile +109 -0
- data/bench/bench +6 -0
- data/bench/benchapp/Rakefile +14 -0
- data/bench/benchapp/application.rb +13 -0
- data/bench/benchapp/config.ru +32 -0
- data/bench/benchapp/settings/license.key +1 -0
- data/bench/benchapp/settings/settings.yml +18 -0
- data/bench/benchapp/sources/mock_adapter.rb +55 -0
- data/bench/benchapp/sources/queue_mock_adapter.rb +2 -0
- data/bench/benchapp/vendor/rhosync/lib/rhosync.rb +7 -0
- data/bench/lib/bench/cli.rb +16 -0
- data/bench/lib/bench/logging.rb +18 -0
- data/bench/lib/bench/mock_client.rb +41 -0
- data/bench/lib/bench/result.rb +50 -0
- data/bench/lib/bench/runner.rb +44 -0
- data/bench/lib/bench/session.rb +65 -0
- data/bench/lib/bench/statistics.rb +56 -0
- data/bench/lib/bench/test_data.rb +55 -0
- data/bench/lib/bench/timer.rb +10 -0
- data/bench/lib/bench/utils.rb +49 -0
- data/bench/lib/bench.rb +128 -0
- data/bench/lib/testdata/100-data.txt +148 -0
- data/bench/lib/testdata/5-data.txt +11 -0
- data/bench/scripts/cud_script.rb +77 -0
- data/bench/scripts/helpers.rb +101 -0
- data/bench/scripts/query_md_script.rb +46 -0
- data/bench/scripts/query_script.rb +46 -0
- data/bench/spec/bench_spec_helper.rb +65 -0
- data/bench/spec/logging_spec.rb +19 -0
- data/bench/spec/mock_adapter_spec.rb +61 -0
- data/bench/spec/mock_client_spec.rb +64 -0
- data/bench/spec/result_spec.rb +59 -0
- data/bench/spec/utils_spec.rb +35 -0
- data/bin/rhosync +34 -0
- data/doc/protocol.html +1901 -0
- data/doc/public/css/print.css +29 -0
- data/doc/public/css/screen.css +257 -0
- data/doc/public/css/style.css +20 -0
- data/examples/simple/application.rb +13 -0
- data/examples/simple/sources/sample_adapter.rb +5 -0
- data/examples/simple/sources/simple_adapter.rb +5 -0
- data/examples/simple/vendor/rhosync/lib/rhosync.rb +7 -0
- data/generators/rhosync.rb +98 -0
- data/generators/templates/application/Rakefile +19 -0
- data/generators/templates/application/application.rb +27 -0
- data/generators/templates/application/config.ru +33 -0
- data/generators/templates/application/settings/license.key +1 -0
- data/generators/templates/application/settings/settings.yml +14 -0
- data/generators/templates/source/source_adapter.rb +49 -0
- data/lib/rhosync/api/create_client.rb +3 -0
- data/lib/rhosync/api/create_user.rb +7 -0
- data/lib/rhosync/api/delete_client.rb +5 -0
- data/lib/rhosync/api/delete_user.rb +5 -0
- data/lib/rhosync/api/get_api_token.rb +7 -0
- data/lib/rhosync/api/get_client_params.rb +3 -0
- data/lib/rhosync/api/get_db_doc.rb +7 -0
- data/lib/rhosync/api/get_license_info.rb +7 -0
- data/lib/rhosync/api/get_source_params.rb +3 -0
- data/lib/rhosync/api/list_client_docs.rb +12 -0
- data/lib/rhosync/api/list_clients.rb +3 -0
- data/lib/rhosync/api/list_source_docs.rb +10 -0
- data/lib/rhosync/api/list_sources.rb +15 -0
- data/lib/rhosync/api/list_users.rb +3 -0
- data/lib/rhosync/api/ping.rb +7 -0
- data/lib/rhosync/api/push_deletes.rb +6 -0
- data/lib/rhosync/api/push_objects.rb +6 -0
- data/lib/rhosync/api/reset.rb +10 -0
- data/lib/rhosync/api/set_db_doc.rb +8 -0
- data/lib/rhosync/api/set_refresh_time.rb +8 -0
- data/lib/rhosync/api/update_user.rb +4 -0
- data/lib/rhosync/api/upload_file.rb +4 -0
- data/lib/rhosync/api_token.rb +19 -0
- data/lib/rhosync/app.rb +69 -0
- data/lib/rhosync/bulk_data/bulk_data.rb +75 -0
- data/lib/rhosync/bulk_data/syncdb.index.schema +3 -0
- data/lib/rhosync/bulk_data/syncdb.schema +37 -0
- data/lib/rhosync/bulk_data.rb +2 -0
- data/lib/rhosync/client.rb +74 -0
- data/lib/rhosync/client_sync.rb +296 -0
- data/lib/rhosync/console/app/helpers/auth_helper.rb +18 -0
- data/lib/rhosync/console/app/helpers/extensions.rb +19 -0
- data/lib/rhosync/console/app/helpers/helpers.rb +52 -0
- data/lib/rhosync/console/app/public/main.css +7 -0
- data/lib/rhosync/console/app/public/text.txt +0 -0
- data/lib/rhosync/console/app/routes/auth.rb +29 -0
- data/lib/rhosync/console/app/routes/client.rb +32 -0
- data/lib/rhosync/console/app/routes/docs.rb +84 -0
- data/lib/rhosync/console/app/routes/home.rb +22 -0
- data/lib/rhosync/console/app/routes/user.rb +63 -0
- data/lib/rhosync/console/app/views/client.erb +30 -0
- data/lib/rhosync/console/app/views/doc.erb +56 -0
- data/lib/rhosync/console/app/views/docs.erb +29 -0
- data/lib/rhosync/console/app/views/index.erb +50 -0
- data/lib/rhosync/console/app/views/layout.erb +12 -0
- data/lib/rhosync/console/app/views/newuser.erb +17 -0
- data/lib/rhosync/console/app/views/ping.erb +28 -0
- data/lib/rhosync/console/app/views/result.erb +11 -0
- data/lib/rhosync/console/app/views/user.erb +32 -0
- data/lib/rhosync/console/app/views/users.erb +14 -0
- data/lib/rhosync/console/rhosync_api.rb +102 -0
- data/lib/rhosync/console/server.rb +27 -0
- data/lib/rhosync/credential.rb +9 -0
- data/lib/rhosync/document.rb +43 -0
- data/lib/rhosync/indifferent_access.rb +132 -0
- data/lib/rhosync/jobs/bulk_data_job.rb +104 -0
- data/lib/rhosync/jobs/ping_job.rb +19 -0
- data/lib/rhosync/jobs/source_job.rb +16 -0
- data/lib/rhosync/license.rb +79 -0
- data/lib/rhosync/lock_ops.rb +11 -0
- data/lib/rhosync/model.rb +410 -0
- data/lib/rhosync/ping/blackberry.rb +55 -0
- data/lib/rhosync/ping/iphone.rb +44 -0
- data/lib/rhosync/ping.rb +2 -0
- data/lib/rhosync/read_state.rb +27 -0
- data/lib/rhosync/server/views/index.erb +12 -0
- data/lib/rhosync/server.rb +242 -0
- data/lib/rhosync/source.rb +112 -0
- data/lib/rhosync/source_adapter.rb +95 -0
- data/lib/rhosync/source_sync.rb +245 -0
- data/lib/rhosync/store.rb +199 -0
- data/lib/rhosync/tasks.rb +151 -0
- data/lib/rhosync/user.rb +83 -0
- data/lib/rhosync/version.rb +3 -0
- data/lib/rhosync.rb +251 -0
- data/spec/api/api_helper.rb +44 -0
- data/spec/api/create_client_spec.rb +13 -0
- data/spec/api/create_user_spec.rb +16 -0
- data/spec/api/delete_client_spec.rb +13 -0
- data/spec/api/delete_user_spec.rb +18 -0
- data/spec/api/get_api_token_spec.rb +25 -0
- data/spec/api/get_client_params_spec.rb +18 -0
- data/spec/api/get_db_doc_spec.rb +21 -0
- data/spec/api/get_license_info_spec.rb +16 -0
- data/spec/api/get_source_params_spec.rb +26 -0
- data/spec/api/list_client_docs_spec.rb +33 -0
- data/spec/api/list_clients_spec.rb +23 -0
- data/spec/api/list_source_docs_spec.rb +26 -0
- data/spec/api/list_sources_spec.rb +27 -0
- data/spec/api/list_users_spec.rb +21 -0
- data/spec/api/ping_spec.rb +24 -0
- data/spec/api/push_deletes_spec.rb +16 -0
- data/spec/api/push_objects_spec.rb +27 -0
- data/spec/api/reset_spec.rb +22 -0
- data/spec/api/set_db_doc_spec.rb +20 -0
- data/spec/api/set_refresh_time_spec.rb +43 -0
- data/spec/api/update_user_spec.rb +31 -0
- data/spec/api/upload_file_spec.rb +26 -0
- data/spec/api_token_spec.rb +13 -0
- data/spec/app_spec.rb +20 -0
- data/spec/apps/rhotestapp/Rakefile +1 -0
- data/spec/apps/rhotestapp/application.rb +16 -0
- data/spec/apps/rhotestapp/config.ru +1 -0
- data/spec/apps/rhotestapp/settings/apple_fake_cert.pem +1 -0
- data/spec/apps/rhotestapp/settings/license.key +1 -0
- data/spec/apps/rhotestapp/settings/settings.yml +23 -0
- data/spec/apps/rhotestapp/sources/base_adapter.rb +9 -0
- data/spec/apps/rhotestapp/sources/sample_adapter.rb +66 -0
- data/spec/apps/rhotestapp/sources/simple_adapter.rb +39 -0
- data/spec/apps/rhotestapp/sources/sub_adapter.rb +7 -0
- data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem/mygem.rb +8 -0
- data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem.rb +1 -0
- data/spec/bulk_data/bulk_data_spec.rb +79 -0
- data/spec/client_spec.rb +58 -0
- data/spec/client_sync_spec.rb +377 -0
- data/spec/doc/base.html +72 -0
- data/spec/doc/doc_spec.rb +303 -0
- data/spec/doc/footer.html +4 -0
- data/spec/doc/header.html +30 -0
- data/spec/document_spec.rb +27 -0
- data/spec/generator/generator_spec.rb +53 -0
- data/spec/generator/generator_spec_helper.rb +8 -0
- data/spec/jobs/bulk_data_job_spec.rb +76 -0
- data/spec/jobs/ping_job_spec.rb +26 -0
- data/spec/jobs/source_job_spec.rb +25 -0
- data/spec/license_spec.rb +48 -0
- data/spec/model_spec.rb +269 -0
- data/spec/perf/bulk_data_perf_spec.rb +33 -0
- data/spec/perf/perf_spec_helper.rb +51 -0
- data/spec/perf/store_perf_spec.rb +28 -0
- data/spec/ping/blackberry_spec.rb +62 -0
- data/spec/ping/iphone_spec.rb +50 -0
- data/spec/read_state_spec.rb +25 -0
- data/spec/rhosync_spec.rb +43 -0
- data/spec/server/server_spec.rb +341 -0
- data/spec/source_adapter_spec.rb +114 -0
- data/spec/source_spec.rb +77 -0
- data/spec/source_sync_spec.rb +248 -0
- data/spec/spec_helper.rb +240 -0
- data/spec/store_spec.rb +149 -0
- data/spec/sync_states_spec.rb +101 -0
- data/spec/testdata/1000-data.txt +1414 -0
- data/spec/testdata/compressed/compress-data.txt +1 -0
- data/spec/testdata/upload1.txt +1 -0
- data/spec/testdata/upload2.txt +1 -0
- data/spec/user_spec.rb +79 -0
- data/tasks/redis.rake +134 -0
- metadata +545 -0
@@ -0,0 +1,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,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_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,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
|
+
|
data/lib/rhosync/app.rb
ADDED
@@ -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,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,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
|