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