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.
- 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,199 @@
|
|
|
1
|
+
module Rhosync
|
|
2
|
+
class Store
|
|
3
|
+
RESERVED_ATTRIB_NAMES = ["attrib_type", "id"] unless defined? RESERVED_ATTRIB_NAMES
|
|
4
|
+
@@db = nil
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def db; @@db || @@db = _get_redis end
|
|
8
|
+
|
|
9
|
+
def db=(server=nil)
|
|
10
|
+
@@db = _get_redis(server)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create(server=nil)
|
|
14
|
+
@@db ||= _get_redis(server)
|
|
15
|
+
raise "Error connecting to Redis store." unless @@db and (@@db.is_a?(Redis) or @@db.is_a?(Redis::Client))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Adds set with given data, replaces existing set
|
|
19
|
+
# if it exists or appends data to the existing set
|
|
20
|
+
# if append flag set to true
|
|
21
|
+
def put_data(dockey,data={},append=false)
|
|
22
|
+
if dockey and data
|
|
23
|
+
flash_data(dockey) unless append
|
|
24
|
+
# Inserts a hash or array
|
|
25
|
+
if data.is_a?(Hash)
|
|
26
|
+
@@db.pipelined do |pipeline|
|
|
27
|
+
data.each do |key,value|
|
|
28
|
+
value.each do |attrib,value|
|
|
29
|
+
unless _is_reserved?(attrib,value)
|
|
30
|
+
pipeline.sadd(dockey,setelement(key,attrib,value))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
@@db.pipelined do |pipeline|
|
|
37
|
+
data.each do |value|
|
|
38
|
+
pipeline.sadd(dockey,value)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Adds a simple key/value pair
|
|
47
|
+
def put_value(dockey,value)
|
|
48
|
+
if dockey
|
|
49
|
+
@@db.del(dockey)
|
|
50
|
+
@@db.set(dockey,value.to_s) if value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Retrieves value for a given key
|
|
55
|
+
def get_value(dockey)
|
|
56
|
+
@@db.get(dockey) if dockey
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Retrieves set for given dockey,source,user
|
|
60
|
+
def get_data(dockey,type=Hash)
|
|
61
|
+
res = type == Hash ? {} : []
|
|
62
|
+
if dockey
|
|
63
|
+
@@db.smembers(dockey).each do |element|
|
|
64
|
+
if type == Hash
|
|
65
|
+
key,attrib,value = getelement(element)
|
|
66
|
+
res[key] = {} unless res[key]
|
|
67
|
+
res[key].merge!({attrib => value})
|
|
68
|
+
else
|
|
69
|
+
res << element
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
res
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Retrieves diff data hash between two sets
|
|
77
|
+
def get_diff_data(src_dockey,dst_dockey,p_size=nil)
|
|
78
|
+
res = {}
|
|
79
|
+
if src_dockey and dst_dockey
|
|
80
|
+
@@db.sdiff(dst_dockey,src_dockey).each do |element|
|
|
81
|
+
key,attrib,value = getelement(element)
|
|
82
|
+
res[key] = {} unless res[key]
|
|
83
|
+
res[key].merge!({attrib => value})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
if p_size
|
|
87
|
+
diff = {}
|
|
88
|
+
page_size = p_size
|
|
89
|
+
res.each do |key,item|
|
|
90
|
+
diff[key] = item
|
|
91
|
+
page_size -= 1
|
|
92
|
+
break if page_size <= 0
|
|
93
|
+
end
|
|
94
|
+
[diff,res.size]
|
|
95
|
+
else
|
|
96
|
+
[res,res.size]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Deletes data from a given doctype,source,user
|
|
101
|
+
def delete_data(dockey,data={})
|
|
102
|
+
if dockey and data
|
|
103
|
+
@@db.pipelined do |pipeline|
|
|
104
|
+
data.each do |key,value|
|
|
105
|
+
value.each do |attrib,val|
|
|
106
|
+
pipeline.srem(dockey,setelement(key,attrib,val))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Deletes all keys matching a given mask
|
|
115
|
+
def flash_data(keymask)
|
|
116
|
+
@@db.keys(keymask).each do |key|
|
|
117
|
+
@@db.del(key)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns array of keys matching a given keymask
|
|
122
|
+
def get_keys(keymask)
|
|
123
|
+
@@db.keys(keymask)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns true if given item is a member of the given set
|
|
127
|
+
def ismember?(setkey,item)
|
|
128
|
+
@@db.sismember(setkey,item)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Lock a given key and release when provided block is finished
|
|
132
|
+
def lock(dockey,timeout=0)
|
|
133
|
+
m_lock = get_lock(dockey,timeout)
|
|
134
|
+
res = yield
|
|
135
|
+
release_lock(dockey,m_lock)
|
|
136
|
+
res
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def get_lock(dockey,timeout=0)
|
|
140
|
+
lock_key = _lock_key(dockey)
|
|
141
|
+
current_time = Time.now.to_i
|
|
142
|
+
if not @@db.setnx(lock_key,current_time+timeout+1)
|
|
143
|
+
loop do
|
|
144
|
+
if @@db.get(lock_key).to_i <= current_time and
|
|
145
|
+
@@db.getset(lock_key,current_time+timeout+1).to_i <= current_time
|
|
146
|
+
break
|
|
147
|
+
end
|
|
148
|
+
sleep(1)
|
|
149
|
+
current_time = Time.now.to_i
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
current_time+timeout+1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Due to redis bug #140, setnx always returns true so this doesn't work
|
|
156
|
+
# def get_lock(dockey,timeout=0)
|
|
157
|
+
# lock_key = _lock_key(dockey)
|
|
158
|
+
# until @@db.setnx(lock_key,1) do
|
|
159
|
+
# sleep(1)
|
|
160
|
+
# end
|
|
161
|
+
# @@db.expire(lock_key,timeout+1)
|
|
162
|
+
# Time.now.to_i+timeout+1
|
|
163
|
+
# end
|
|
164
|
+
|
|
165
|
+
def release_lock(dockey,lock)
|
|
166
|
+
@@db.del(_lock_key(dockey)) if (lock >= Time.now.to_i)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Create a copy of srckey in dstkey
|
|
170
|
+
def clone(srckey,dstkey)
|
|
171
|
+
@@db.sdiffstore(dstkey,srckey,'')
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Rename srckey to dstkey
|
|
175
|
+
def rename(srckey,dstkey)
|
|
176
|
+
@@db.rename(srckey,dstkey) if @@db.exists(srckey)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
def _get_redis(server=nil)
|
|
181
|
+
if server and server.is_a?(String)
|
|
182
|
+
host,port,db,password = server.split(':')
|
|
183
|
+
Redis.new(:thread_safe => true, :host => host,
|
|
184
|
+
:port => port, :db => db, :password => password)
|
|
185
|
+
else
|
|
186
|
+
Redis.new(:thread_safe => true)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def _lock_key(dockey)
|
|
191
|
+
"#{dockey}:lock"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def _is_reserved?(attrib,value) #:nodoc:
|
|
195
|
+
RESERVED_ATTRIB_NAMES.include? attrib
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'mechanize'
|
|
3
|
+
require 'zip/zip'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require File.join(File.dirname(__FILE__),'console','rhosync_api')
|
|
6
|
+
|
|
7
|
+
module Rhosync
|
|
8
|
+
module TaskHelper
|
|
9
|
+
def post(path,params)
|
|
10
|
+
req = Net::HTTP.new($host,$port)
|
|
11
|
+
resp = req.post(path, params.to_json, 'Content-Type' => 'application/json')
|
|
12
|
+
print_resp(resp, resp.is_a?(Net::HTTPSuccess) ? true : false)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def print_resp(resp,success=true)
|
|
16
|
+
if success
|
|
17
|
+
puts "=> OK"
|
|
18
|
+
else
|
|
19
|
+
puts "=> FAILED"
|
|
20
|
+
end
|
|
21
|
+
puts "=> " + resp.body if resp and resp.body and resp.body.length > 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def archive(path)
|
|
25
|
+
File.join(path,File.basename(path))+'.zip'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ask(msg)
|
|
29
|
+
print msg
|
|
30
|
+
STDIN.gets.chomp
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def load_settings(file)
|
|
34
|
+
begin
|
|
35
|
+
$settings = YAML.load_file(file)
|
|
36
|
+
rescue Exception => e
|
|
37
|
+
puts "Error opening settings file #{file}: #{e}."
|
|
38
|
+
puts e.backtrace.join("\n")
|
|
39
|
+
raise e
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def rhosync_socket
|
|
44
|
+
"/tmp/rhosync.dtach"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def rhosync_pid
|
|
48
|
+
"/tmp/rhosync.pid"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
namespace :rhosync do
|
|
54
|
+
include Rhosync::TaskHelper
|
|
55
|
+
include RhosyncApi
|
|
56
|
+
|
|
57
|
+
task :config do
|
|
58
|
+
$settings = load_settings(File.join(ENV['PWD'],'settings','settings.yml'))
|
|
59
|
+
env = (ENV['RHO_ENV'] || :development).to_sym
|
|
60
|
+
uri = URI.parse($settings[env][:syncserver])
|
|
61
|
+
$url = "#{uri.scheme}://#{uri.host}"
|
|
62
|
+
$url = "#{$url}:#{uri.port}" if uri.port && uri.port != 80
|
|
63
|
+
$host = uri.host
|
|
64
|
+
$port = uri.port
|
|
65
|
+
$agent = Mechanize.new
|
|
66
|
+
$appname = $settings[env][:syncserver].split('/').last
|
|
67
|
+
$token_file = File.join(ENV['HOME'],'.rhosync_token')
|
|
68
|
+
$token = File.read($token_file) if File.exist?($token_file)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
desc "Reset the rhosync database (you will need to run rhosync:get_api_token afterwards)"
|
|
72
|
+
task :reset => :config do
|
|
73
|
+
RhosyncApi.reset($url,$token)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
desc "Fetches current api token from rhosync"
|
|
77
|
+
task :get_token => :config do
|
|
78
|
+
login = ask "admin login: "
|
|
79
|
+
password = ask "admin password: "
|
|
80
|
+
$token = RhosyncApi.get_token($url,login,password)
|
|
81
|
+
File.open($token_file,'w') {|f| f.write $token}
|
|
82
|
+
puts "Token is saved in: #{$token_file}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "Clean rhosync, get token, and create new user"
|
|
86
|
+
task :clean_start => [:get_token, :reset, :get_token, :create_user]
|
|
87
|
+
|
|
88
|
+
desc "Alias for `rake rhosync:stop; rake rhosync:start`"
|
|
89
|
+
task :restart => [:stop, :start]
|
|
90
|
+
|
|
91
|
+
desc "Creates and subscribes user for application in rhosync"
|
|
92
|
+
task :create_user => :config do
|
|
93
|
+
login = ask "new user login: "
|
|
94
|
+
password = ask "new user password: "
|
|
95
|
+
RhosyncApi.create_user($url,$appname,$token,login,password)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
desc "Deletes the user from rhosync"
|
|
99
|
+
task :delete_user => :config do
|
|
100
|
+
login = ask "user to delete: "
|
|
101
|
+
RhosyncApi.delete_user($url,$appname,$token,login)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# desc "Updates an existing user in rhosync"
|
|
105
|
+
# task :update_user => :config do
|
|
106
|
+
# login = ask "login: "
|
|
107
|
+
# password = ask "password: "
|
|
108
|
+
# new_password = ask "new password: "
|
|
109
|
+
# post("/api/update_user", {:app_name => $appname, :api_token => $token,
|
|
110
|
+
# :login => login, :password => password, :attributes => {:new_password => new_password}})
|
|
111
|
+
# end
|
|
112
|
+
|
|
113
|
+
# desc "Reset source refresh time"
|
|
114
|
+
# task :reset_refresh_time => :config do
|
|
115
|
+
# user = ask "user: "
|
|
116
|
+
# source_name = ask "source name: "
|
|
117
|
+
# post("/api/set_refresh_time", {:api_token => $token, :app_name => $appname,
|
|
118
|
+
# :user_name => user, :source_name => source_name})
|
|
119
|
+
# end
|
|
120
|
+
|
|
121
|
+
desc "Run rhosync source adapter specs"
|
|
122
|
+
task :spec do
|
|
123
|
+
files = File.join($app_basedir,'rhosync/spec/sources/*_spec.rb')
|
|
124
|
+
Spec::Rake::SpecTask.new('rhosync:spec') do |t|
|
|
125
|
+
t.spec_files = FileList[files]
|
|
126
|
+
t.spec_opts = %w(-fs --color)
|
|
127
|
+
t.rcov = true
|
|
128
|
+
t.rcov_opts = ['--exclude', 'spec/*,gems/*']
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
desc "Start rhosync server"
|
|
133
|
+
task :start do
|
|
134
|
+
puts 'Detach with Ctrl+\ Re-attach with rake rhosync:attach'
|
|
135
|
+
sleep 1
|
|
136
|
+
command = "dtach -A #{rhosync_socket} rackup config.ru -P #{rhosync_pid}"
|
|
137
|
+
sh command
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
desc "Stop rhosync server"
|
|
141
|
+
task :stop do
|
|
142
|
+
sh "cat #{rhosync_pid} | xargs kill -3"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
desc "Attach to rhosync console"
|
|
146
|
+
task :attach do
|
|
147
|
+
sh "dtach -a #{rhosync_socket}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
load File.join(File.dirname(__FILE__),'..','..','tasks','redis.rake')
|
data/lib/rhosync/user.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
|
|
3
|
+
module Rhosync
|
|
4
|
+
# Inspired by sinatra-authentication
|
|
5
|
+
# Password uses simple sha1 digest for hashing
|
|
6
|
+
class User < Model
|
|
7
|
+
field :login,:string
|
|
8
|
+
field :email,:string
|
|
9
|
+
field :salt,:string
|
|
10
|
+
field :hashed_password,:string
|
|
11
|
+
set :clients, :string
|
|
12
|
+
field :admin, :int
|
|
13
|
+
field :token_id, :string
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def create(fields={})
|
|
17
|
+
fields[:id] = fields[:login]
|
|
18
|
+
super(fields)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def authenticate(login,password)
|
|
22
|
+
return unless is_exist?(login)
|
|
23
|
+
current_user = load(login)
|
|
24
|
+
return if current_user.nil?
|
|
25
|
+
return current_user if User.encrypt(password, current_user.salt) == current_user.hashed_password
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def new_password=(pass)
|
|
30
|
+
self.password=(pass)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def password=(pass)
|
|
34
|
+
@password = pass
|
|
35
|
+
self.salt = User.random_string(10) if !self.salt
|
|
36
|
+
self.hashed_password = User.encrypt(@password, self.salt)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def delete
|
|
40
|
+
clients.members.each do |client_id|
|
|
41
|
+
Client.load(client_id,{:source_name => '*'}).delete
|
|
42
|
+
end
|
|
43
|
+
self.token.delete if self.token
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_token
|
|
48
|
+
if self.token_id && ApiToken.is_exist?(self.token_id)
|
|
49
|
+
self.token.delete
|
|
50
|
+
end
|
|
51
|
+
self.token_id = ApiToken.create(:user_id => self.login).id
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def token
|
|
55
|
+
ApiToken.load(self.token_id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def token=(value)
|
|
59
|
+
if self.token_id && ApiToken.is_exist?(self.token_id)
|
|
60
|
+
self.token.delete
|
|
61
|
+
end
|
|
62
|
+
self.token_id = ApiToken.create(:user_id => self.login, :value => value).id
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update(fields)
|
|
66
|
+
fields.each do |key,value|
|
|
67
|
+
self.send("#{key.to_sym}=", value) unless key == 'login'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
def self.encrypt(pass, salt)
|
|
73
|
+
Digest::SHA1.hexdigest(pass+salt)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.random_string(len)
|
|
77
|
+
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
|
78
|
+
newpass = ""
|
|
79
|
+
1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
|
|
80
|
+
return newpass
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/rhosync.rb
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
require 'redis'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'zip/zip'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'rhosync/license'
|
|
7
|
+
require 'rhosync/version'
|
|
8
|
+
require 'rhosync/document'
|
|
9
|
+
require 'rhosync/lock_ops'
|
|
10
|
+
require 'rhosync/model'
|
|
11
|
+
require 'rhosync/source'
|
|
12
|
+
require 'rhosync/user'
|
|
13
|
+
require 'rhosync/api_token'
|
|
14
|
+
require 'rhosync/app'
|
|
15
|
+
require 'rhosync/store'
|
|
16
|
+
require 'rhosync/client'
|
|
17
|
+
require 'rhosync/read_state'
|
|
18
|
+
require 'rhosync/client_sync'
|
|
19
|
+
require 'rhosync/source_adapter'
|
|
20
|
+
require 'rhosync/source_sync'
|
|
21
|
+
require 'rhosync/indifferent_access'
|
|
22
|
+
require 'rhosync/jobs/source_job'
|
|
23
|
+
require 'rhosync/jobs/ping_job'
|
|
24
|
+
require 'rhosync/bulk_data'
|
|
25
|
+
|
|
26
|
+
# Various module utilities for the store
|
|
27
|
+
module Rhosync
|
|
28
|
+
APP_NAME = 'application' unless defined? APP_NAME
|
|
29
|
+
|
|
30
|
+
class InvalidArgumentError < RuntimeError; end
|
|
31
|
+
class RhosyncServerError < RuntimeError; end
|
|
32
|
+
extend self
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
attr_accessor :base_directory, :app_directory, :data_directory,
|
|
36
|
+
:vendor_directory, :blackberry_bulk_sync, :redis, :environment,
|
|
37
|
+
:log_disabled, :license
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
### Begin Rhosync setup methods
|
|
41
|
+
# Server hook to initialize Rhosync
|
|
42
|
+
def bootstrap(basedir)
|
|
43
|
+
config = get_config(basedir)
|
|
44
|
+
#Load environment
|
|
45
|
+
environment = (ENV['RHO_ENV'] || :development).to_sym
|
|
46
|
+
# Initialize Rhosync and Resque
|
|
47
|
+
Rhosync.base_directory = basedir
|
|
48
|
+
Rhosync.app_directory = get_setting(config,environment,:app_directory)
|
|
49
|
+
Rhosync.data_directory = get_setting(config,environment,:data_directory)
|
|
50
|
+
Rhosync.vendor_directory = get_setting(config,environment,:vendor_directory)
|
|
51
|
+
Rhosync.blackberry_bulk_sync = get_setting(config,environment,:blackberry_bulk_sync,false)
|
|
52
|
+
Rhosync.redis = get_setting(config,environment,:redis,false)
|
|
53
|
+
Rhosync.log_disabled = get_setting(config,environment,:log_disabled,false)
|
|
54
|
+
Rhosync.environment = environment
|
|
55
|
+
yield self if block_given?
|
|
56
|
+
Store.create(Rhosync.redis)
|
|
57
|
+
Resque.redis = Store.db
|
|
58
|
+
Rhosync.base_directory ||= File.join(File.dirname(__FILE__),'..')
|
|
59
|
+
Rhosync.app_directory ||= Rhosync.base_directory
|
|
60
|
+
Rhosync.data_directory ||= File.join(Rhosync.base_directory,'data')
|
|
61
|
+
Rhosync.vendor_directory ||= File.join(Rhosync.base_directory,'vendor')
|
|
62
|
+
Rhosync.blackberry_bulk_sync ||= false
|
|
63
|
+
Rhosync.log_disabled ||= false
|
|
64
|
+
Rhosync.license = License.new
|
|
65
|
+
|
|
66
|
+
check_and_add(File.join(Rhosync.app_directory,'sources'))
|
|
67
|
+
start_app(config)
|
|
68
|
+
create_admin_user
|
|
69
|
+
check_hsql_lib! if Rhosync.blackberry_bulk_sync
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def start_app(config)
|
|
73
|
+
if config and config[Rhosync.environment]
|
|
74
|
+
app = nil
|
|
75
|
+
app_name = APP_NAME
|
|
76
|
+
if App.is_exist?(app_name)
|
|
77
|
+
app = App.load(app_name)
|
|
78
|
+
else
|
|
79
|
+
app = App.create(:name => app_name)
|
|
80
|
+
end
|
|
81
|
+
sources = config[:sources] || []
|
|
82
|
+
sources.each do |source_name,fields|
|
|
83
|
+
if Source.is_exist?(source_name)
|
|
84
|
+
s = Source.load(source_name,{:app_id => app.name,:user_id => '*'})
|
|
85
|
+
s.update(fields)
|
|
86
|
+
else
|
|
87
|
+
fields[:name] = source_name
|
|
88
|
+
Source.create(fields,{:app_id => app.name})
|
|
89
|
+
end
|
|
90
|
+
unless app.sources.members.include?(source_name)
|
|
91
|
+
app.sources << source_name
|
|
92
|
+
end
|
|
93
|
+
# load ruby file for source adapter to re-load class
|
|
94
|
+
load under_score(source_name+'.rb')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Generate admin user on first load
|
|
100
|
+
def create_admin_user
|
|
101
|
+
unless User.is_exist?('admin')
|
|
102
|
+
admin = User.create({:login => 'admin', :admin => 1})
|
|
103
|
+
admin.password = ''
|
|
104
|
+
admin.create_token
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Add path to load_path unless it has been added already
|
|
109
|
+
def check_and_add(path)
|
|
110
|
+
$:.unshift path unless $:.include?(path)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_config(basedir)
|
|
114
|
+
# Load settings
|
|
115
|
+
settings_file = File.join(basedir,'settings','settings.yml') if basedir
|
|
116
|
+
config = YAML.load_file(settings_file) if settings_file and File.exist?(settings_file)
|
|
117
|
+
end
|
|
118
|
+
### End Rhosync setup methods
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def check_default_secret!(secret)
|
|
122
|
+
if secret == '<changeme>'
|
|
123
|
+
log "*"*60+"\n\n"
|
|
124
|
+
log "WARNING: Change the session secret in config.ru from <changeme> to something secure."
|
|
125
|
+
log " i.e. running `rake secret` in a rails app will generate a secret you could use.\n\n"
|
|
126
|
+
log "*"*60
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Serializes oav to set element
|
|
131
|
+
def setelement(obj,attrib,value)
|
|
132
|
+
"#{obj}:#{attrib}:#{Base64.encode64(value.to_s)}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# De-serializes oav from set element
|
|
136
|
+
def getelement(element)
|
|
137
|
+
res = element.split(':')
|
|
138
|
+
[res[0], res[1], Base64.decode64(res[2].to_s)]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Get random UUID string
|
|
142
|
+
def get_random_uuid
|
|
143
|
+
UUIDTools::UUID.random_create.to_s.gsub(/\-/,'')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Generates new token (64-bit integer) based on # of
|
|
147
|
+
# microseconds since Jan 1 2009
|
|
148
|
+
def get_token
|
|
149
|
+
((Time.now.to_f - Time.mktime(2009,"jan",1,0,0,0,0).to_f) * 10**6).to_i
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Computes token for a single client request
|
|
153
|
+
def compute_token(doc_key)
|
|
154
|
+
token = get_token
|
|
155
|
+
Store.put_value(doc_key,token)
|
|
156
|
+
token.to_s
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Returns require-friendly filename for a class
|
|
160
|
+
def under_score(camel_cased_word)
|
|
161
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
|
162
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
|
163
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
|
164
|
+
tr("-", "_").
|
|
165
|
+
downcase
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Taken from rails inflector
|
|
169
|
+
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
|
170
|
+
if first_letter_in_uppercase
|
|
171
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def check_hsql_lib!
|
|
176
|
+
unless File.exists?(File.join(Rhosync.vendor_directory,'hsqldata.jar'))
|
|
177
|
+
log "*"*60
|
|
178
|
+
log ""
|
|
179
|
+
log "WARNING: Missing vendor/hsqldata.jar, please install it for BlackBerry bulk sync support."
|
|
180
|
+
log ""
|
|
181
|
+
log "*"*60
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def unzip_file(file_dir,params)
|
|
186
|
+
uploaded_file = File.join(file_dir, params[:filename])
|
|
187
|
+
begin
|
|
188
|
+
File.open(uploaded_file, 'wb') do |file|
|
|
189
|
+
file.write(params[:tempfile].read)
|
|
190
|
+
end
|
|
191
|
+
Zip::ZipFile.open(uploaded_file) do |zip_file|
|
|
192
|
+
zip_file.each do |f|
|
|
193
|
+
f_path = File.join(file_dir,f.name)
|
|
194
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
|
195
|
+
zip_file.extract(f, f_path) { true }
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
rescue Exception => e
|
|
199
|
+
log "Failed to unzip `#{uploaded_file}`"
|
|
200
|
+
raise e
|
|
201
|
+
ensure
|
|
202
|
+
FileUtils.rm_f(uploaded_file)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def lap_timer(msg,start)
|
|
207
|
+
duration = timenow - start
|
|
208
|
+
log "#{msg}: #{duration}"
|
|
209
|
+
timenow
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def start_timer(msg='starting')
|
|
213
|
+
log "#{msg}"
|
|
214
|
+
timenow
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def timenow
|
|
218
|
+
(Time.now.to_f * 1000)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def log(*args)
|
|
222
|
+
now = Time.now.strftime('%I:%M:%S %p %Y-%m-%d')
|
|
223
|
+
puts "[#{now}] #{args.join}" unless Rhosync.log_disabled
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Base rhosync application class
|
|
227
|
+
class Base
|
|
228
|
+
# Add everything in vendor to load path
|
|
229
|
+
# TODO: Integrate with 3rd party dependency management
|
|
230
|
+
def self.initializer(path=nil)
|
|
231
|
+
Dir["vendor/*"].each do |dir|
|
|
232
|
+
$:.unshift File.join(dir,'lib')
|
|
233
|
+
end
|
|
234
|
+
require 'rhosync'
|
|
235
|
+
require 'rhosync/server'
|
|
236
|
+
# Bootstrap Rhosync system
|
|
237
|
+
Rhosync.bootstrap(path || ENV['PWD'])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def self.store_blob(obj,field_name,blob)
|
|
241
|
+
blob[:tempfile].path if blob[:tempfile]
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
protected
|
|
246
|
+
def get_setting(config,environment,setting,default=nil)
|
|
247
|
+
res = nil
|
|
248
|
+
res = config[environment][setting] if config and environment
|
|
249
|
+
res || default
|
|
250
|
+
end
|
|
251
|
+
end
|