rhosync 2.1.6 → 2.1.7
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 +8 -0
- data/Rakefile +1 -1
- data/lib/rhosync/api_token.rb +1 -1
- data/lib/rhosync/client.rb +2 -2
- data/lib/rhosync/client_sync.rb +7 -2
- data/lib/rhosync/jobs/ping_job.rb +22 -7
- data/lib/rhosync/lock_ops.rb +2 -2
- data/lib/rhosync/ping/android.rb +1 -1
- data/lib/rhosync/read_state.rb +1 -1
- data/lib/rhosync/source.rb +56 -19
- data/lib/rhosync/source_sync.rb +10 -6
- data/lib/rhosync/store.rb +24 -10
- data/lib/rhosync/version.rb +1 -1
- data/lib/rhosync.rb +4 -4
- data/spec/api/get_client_params_spec.rb +1 -0
- data/spec/api/push_deletes_spec.rb +1 -1
- data/spec/api/push_objects_spec.rb +1 -1
- data/spec/api/rhosync_api_spec.rb +1 -0
- data/spec/jobs/ping_job_spec.rb +21 -4
- data/spec/source_adapter_spec.rb +16 -8
- data/spec/source_spec.rb +0 -1
- data/spec/spec.opts +2 -1
- data/spec/spec_helper.rb +8 -1
- data/spec/stats/record_spec.rb +1 -0
- data/spec/store_spec.rb +34 -3
- metadata +6 -6
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 2.1.7
|
2
|
+
* #14021681 - check for client on client_sync class methods
|
3
|
+
* #14082861 - expose Store.lock timeout to high-level functions
|
4
|
+
* #14082589 - fixed source loading so a unique instance is loaded
|
5
|
+
* #14124195 - concurrency issue under load for same user via push_objects api
|
6
|
+
* #14511763 - added global options `raise_on_expired_lock` (true/false) and `lock_duration` (sec) settings
|
7
|
+
* #14514773 - REST API push_objects, push_deletes :md_size count fix
|
8
|
+
|
1
9
|
## 2.1.6
|
2
10
|
* #13830841 - fixed issue where current_user.login doesn't match @source.user_id
|
3
11
|
|
data/Rakefile
CHANGED
@@ -73,7 +73,7 @@ begin
|
|
73
73
|
gemspec.add_dependency "rest-client", "~>1.6.1"
|
74
74
|
gemspec.add_dependency "sinatra", "~>1.2"
|
75
75
|
gemspec.add_dependency "templater", "~>1.0.0"
|
76
|
-
gemspec.add_dependency "rake", "
|
76
|
+
gemspec.add_dependency "rake", "~>0.8.7"
|
77
77
|
gemspec.add_development_dependency "log4r", "~>1.1.7"
|
78
78
|
gemspec.add_development_dependency "jeweler", ">=1.4.0"
|
79
79
|
gemspec.add_development_dependency "rspec", ">=1.3.0"
|
data/lib/rhosync/api_token.rb
CHANGED
data/lib/rhosync/client.rb
CHANGED
@@ -5,7 +5,7 @@ module Rhosync
|
|
5
5
|
field :device_type,:string
|
6
6
|
field :device_pin,:string
|
7
7
|
field :device_port,:string
|
8
|
-
|
8
|
+
field :phone_id,:string
|
9
9
|
field :user_id,:string
|
10
10
|
field :app_id,:string
|
11
11
|
attr_accessor :source_name
|
@@ -80,7 +80,7 @@ module Rhosync
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def update_fields(params)
|
83
|
-
[:device_type,:device_pin,:device_port].each do |setting|
|
83
|
+
[:device_type,:device_pin,:device_port,:phone_id].each do |setting|
|
84
84
|
self.send "#{setting}=".to_sym, params[setting].to_s if params[setting]
|
85
85
|
end
|
86
86
|
end
|
data/lib/rhosync/client_sync.rb
CHANGED
@@ -3,10 +3,13 @@ module Rhosync
|
|
3
3
|
attr_accessor :source,:client,:p_size,:source_sync
|
4
4
|
|
5
5
|
VERSION = 3
|
6
|
+
UNKNOWN_CLIENT = "Unknown client"
|
7
|
+
UNKNOWN_SOURCE = "Unknown source"
|
8
|
+
|
6
9
|
|
7
10
|
def initialize(source,client,p_size=nil)
|
8
|
-
raise ArgumentError.new(
|
9
|
-
raise ArgumentError.new(
|
11
|
+
raise ArgumentError.new(UNKNOWN_CLIENT) unless client
|
12
|
+
raise ArgumentError.new(UNKNOWN_SOURCE) unless source
|
10
13
|
@source,@client,@p_size = source,client,p_size ? p_size.to_i : 500
|
11
14
|
@source_sync = SourceSync.new(@source)
|
12
15
|
end
|
@@ -199,6 +202,7 @@ module Rhosync
|
|
199
202
|
end
|
200
203
|
|
201
204
|
def search_all(client,params=nil)
|
205
|
+
raise ArgumentError.new(UNKNOWN_CLIENT) unless client
|
202
206
|
return [] unless params[:sources]
|
203
207
|
res = []
|
204
208
|
params[:sources].each do |source|
|
@@ -214,6 +218,7 @@ module Rhosync
|
|
214
218
|
end
|
215
219
|
|
216
220
|
def bulk_data(partition,client)
|
221
|
+
raise ArgumentError.new(UNKNOWN_CLIENT) unless client
|
217
222
|
name = BulkData.get_name(partition,client.user_id)
|
218
223
|
data = BulkData.load(name)
|
219
224
|
|
@@ -8,20 +8,35 @@ module Rhosync
|
|
8
8
|
def self.perform(params)
|
9
9
|
user = User.load(params["user_id"])
|
10
10
|
device_pins = []
|
11
|
+
phone_ids = []
|
11
12
|
user.clients.members.each do |client_id|
|
12
13
|
client = Client.load(client_id,{:source_name => '*'})
|
13
|
-
params.merge!('device_port' => client.device_port, 'device_pin' => client.device_pin)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
params.merge!('device_port' => client.device_port, 'device_pin' => client.device_pin, 'phone_id' => client.phone_id)
|
15
|
+
send_push = false
|
16
|
+
if client.device_type and client.device_type.size > 0
|
17
|
+
if client.phone_id and client.phone_id.size > 0
|
18
|
+
unless phone_ids.include? client.phone_id
|
19
|
+
phone_ids << client.phone_id
|
20
|
+
send_push = true
|
21
|
+
end
|
22
|
+
elsif client.device_pin and client.device_pin.size > 0
|
23
|
+
unless device_pins.include? client.device_pin
|
24
|
+
device_pins << client.device_pin
|
25
|
+
send_push = true
|
26
|
+
end
|
27
|
+
else
|
28
|
+
log "Skipping ping for non-registered client_id '#{client_id}'..."
|
29
|
+
next
|
30
|
+
end
|
31
|
+
if send_push
|
32
|
+
klass = Object.const_get(camelize(client.device_type.downcase))
|
18
33
|
if klass
|
19
34
|
params['vibrate'] = params['vibrate'].to_s
|
20
35
|
klass.ping(params)
|
21
36
|
end
|
22
37
|
else
|
23
|
-
log "Dropping ping request for client_id '#{client_id}' because it's already in user's device pin list."
|
24
|
-
end
|
38
|
+
log "Dropping ping request for client_id '#{client_id}' because it's already in user's device pin or phone_id list."
|
39
|
+
end
|
25
40
|
else
|
26
41
|
log "Skipping ping for non-registered client_id '#{client_id}'..."
|
27
42
|
end
|
data/lib/rhosync/lock_ops.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# source documents when source_sync and client_sync
|
4
4
|
# need to access them
|
5
5
|
module LockOps
|
6
|
-
def lock(doc)
|
7
|
-
Store.lock(docname(doc)) do
|
6
|
+
def lock(doc,timeout=0,raise_on_expire=false)
|
7
|
+
Store.lock(docname(doc),timeout,raise_on_expire) do
|
8
8
|
yield self
|
9
9
|
end
|
10
10
|
end
|
data/lib/rhosync/ping/android.rb
CHANGED
@@ -32,7 +32,7 @@ module Rhosync
|
|
32
32
|
data['data.alert'] = params['message'] if params['message']
|
33
33
|
data['data.vibrate'] = params['vibrate'] if params['vibrate']
|
34
34
|
data['data.sound'] = params['sound'] if params['sound']
|
35
|
-
|
35
|
+
data['data.phone_id'] = params['phone_id'] if params['phone_id']
|
36
36
|
data
|
37
37
|
end
|
38
38
|
end
|
data/lib/rhosync/read_state.rb
CHANGED
data/lib/rhosync/source.rb
CHANGED
@@ -7,28 +7,45 @@ module Rhosync
|
|
7
7
|
|
8
8
|
class << self
|
9
9
|
attr_accessor :validates_presence
|
10
|
+
|
11
|
+
def create(fields,params)
|
12
|
+
if self.validates_presence
|
13
|
+
self.validates_presence.each do |field|
|
14
|
+
raise ArgumentError.new("Missing required field '#{field}'") unless fields[field]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
10
18
|
|
11
19
|
def define_fields(string_fields = [], integer_fields = [])
|
12
20
|
@@string_fields,@@integer_fields = string_fields,integer_fields
|
13
21
|
integer_fields.each do |attrib|
|
14
22
|
define_method("#{attrib}=") do |value|
|
15
23
|
value = (value.nil?) ? nil : value.to_i
|
16
|
-
|
24
|
+
@@model_data[self.name.to_sym][attrib.to_sym] = value
|
17
25
|
end
|
18
26
|
define_method("#{attrib}") do
|
19
|
-
|
27
|
+
@@model_data[self.name.to_sym][attrib.to_sym]
|
20
28
|
end
|
21
29
|
end
|
22
30
|
string_fields.each do |attrib|
|
23
31
|
define_method("#{attrib}=") do |value|
|
24
|
-
|
32
|
+
attrib = attrib.to_sym
|
33
|
+
name = nil
|
34
|
+
if attrib == :name
|
35
|
+
instance_variable_set(:@name, value)
|
36
|
+
name = value
|
37
|
+
else
|
38
|
+
name = self.name
|
39
|
+
end
|
40
|
+
@@model_data[name.to_sym] ||= {} # TODO: shouldn't be nil here
|
41
|
+
@@model_data[name.to_sym][attrib] = value
|
25
42
|
end
|
26
43
|
define_method("#{attrib}") do
|
27
|
-
instance_variable_get(
|
44
|
+
@@model_data[instance_variable_get(:@name).to_sym][attrib.to_sym]
|
28
45
|
end
|
46
|
+
# we have separate methods for this
|
47
|
+
@@integer_fields << :poll_interval unless @@integer_fields.include?(:poll_interval)
|
29
48
|
end
|
30
|
-
@@string_fields << :id
|
31
|
-
@@string_fields << :rho__id
|
32
49
|
end
|
33
50
|
|
34
51
|
def validates_presence_of(*names)
|
@@ -64,7 +81,7 @@ module Rhosync
|
|
64
81
|
end
|
65
82
|
|
66
83
|
class Source < MemoryModel
|
67
|
-
attr_accessor :app_id, :user_id
|
84
|
+
attr_accessor :app_id, :user_id
|
68
85
|
|
69
86
|
validates_presence_of :name
|
70
87
|
|
@@ -72,10 +89,11 @@ module Rhosync
|
|
72
89
|
include LockOps
|
73
90
|
|
74
91
|
# source fields
|
75
|
-
define_fields([:name, :url, :login, :password, :callback_url, :partition_type, :sync_type,
|
76
|
-
:queue, :query_queue, :cud_queue, :belongs_to, :has_many], [:source_id, :priority
|
92
|
+
define_fields([:id, :rho__id, :name, :url, :login, :password, :callback_url, :partition_type, :sync_type,
|
93
|
+
:queue, :query_queue, :cud_queue, :belongs_to, :has_many], [:source_id, :priority])
|
77
94
|
|
78
95
|
def initialize(fields)
|
96
|
+
self.name = fields['name'] || fields[:name]
|
79
97
|
fields.each do |name,value|
|
80
98
|
arg = "#{name}=".to_sym
|
81
99
|
self.send(arg, value) if self.respond_to?(arg)
|
@@ -98,17 +116,22 @@ module Rhosync
|
|
98
116
|
|
99
117
|
def self.create(fields,params)
|
100
118
|
fields = fields.with_indifferent_access # so we can access hash keys as symbols
|
119
|
+
super(fields,params)
|
120
|
+
@@model_data[fields[:name].to_sym] = {}
|
101
121
|
set_defaults(fields)
|
102
122
|
obj = new(fields)
|
103
123
|
obj.assign_args(params)
|
104
|
-
@@model_data[obj.rho__id.to_sym] = obj
|
105
124
|
obj
|
106
125
|
end
|
107
|
-
|
126
|
+
|
108
127
|
def self.load(obj_id,params)
|
109
128
|
validate_attributes(params)
|
110
|
-
|
111
|
-
obj
|
129
|
+
model_hash = @@model_data[obj_id.to_sym]
|
130
|
+
obj = new(model_hash) if model_hash
|
131
|
+
if obj
|
132
|
+
obj = obj.dup
|
133
|
+
obj.assign_args(params)
|
134
|
+
end
|
112
135
|
obj
|
113
136
|
end
|
114
137
|
|
@@ -136,13 +159,14 @@ module Rhosync
|
|
136
159
|
end
|
137
160
|
|
138
161
|
def self.delete_all
|
139
|
-
|
162
|
+
params = {:app_id => APP_NAME,:user_id => '*'}
|
163
|
+
@@model_data.each { |k,v| Source.load(k,params).delete }
|
140
164
|
@@model_data = {}
|
141
165
|
end
|
142
166
|
|
143
167
|
def assign_args(params)
|
144
168
|
self.user_id = params[:user_id]
|
145
|
-
self.app_id = params[:app_id]
|
169
|
+
self.app_id = params[:app_id]
|
146
170
|
end
|
147
171
|
|
148
172
|
def blob_attribs
|
@@ -159,25 +183,33 @@ module Rhosync
|
|
159
183
|
def update(fields)
|
160
184
|
fields = fields.with_indifferent_access # so we can access hash keys as symbols
|
161
185
|
self.class.set_defaults(fields)
|
162
|
-
#super(fields)
|
163
186
|
end
|
164
187
|
|
165
188
|
def clone(src_doctype,dst_doctype)
|
166
189
|
Store.clone(docname(src_doctype),docname(dst_doctype))
|
167
190
|
end
|
168
191
|
|
192
|
+
def poll_interval
|
193
|
+
value = Store.get_value(poll_interval_key)
|
194
|
+
value ? value.to_i : nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def poll_interval=(interval)
|
198
|
+
Store.put_value(poll_interval_key, interval)
|
199
|
+
end
|
200
|
+
|
169
201
|
# Return the user associated with a source
|
170
202
|
def user
|
171
|
-
|
203
|
+
User.load(self.user_id)
|
172
204
|
end
|
173
205
|
|
174
206
|
# Return the app the source belongs to
|
175
207
|
def app
|
176
|
-
|
208
|
+
App.load(self.app_id)
|
177
209
|
end
|
178
210
|
|
179
211
|
def schema
|
180
|
-
|
212
|
+
self.get_value(:schema)
|
181
213
|
end
|
182
214
|
|
183
215
|
def read_state
|
@@ -192,6 +224,7 @@ module Rhosync
|
|
192
224
|
|
193
225
|
def delete
|
194
226
|
flash_data('*')
|
227
|
+
flash_data(poll_interval_key)
|
195
228
|
@@model_data.delete(rho__id.to_sym) if rho__id
|
196
229
|
end
|
197
230
|
|
@@ -222,6 +255,10 @@ module Rhosync
|
|
222
255
|
end
|
223
256
|
|
224
257
|
private
|
258
|
+
def poll_interval_key
|
259
|
+
"source:#{self.name}:poll_interval"
|
260
|
+
end
|
261
|
+
|
225
262
|
def self.validate_attributes(params)
|
226
263
|
raise ArgumentError.new('Missing required attribute user_id') unless params[:user_id]
|
227
264
|
raise ArgumentError.new('Missing required attribute app_id') unless params[:app_id]
|
data/lib/rhosync/source_sync.rb
CHANGED
@@ -75,26 +75,30 @@ module Rhosync
|
|
75
75
|
@source.app_id,@source.user_id,client_id,params)
|
76
76
|
end
|
77
77
|
|
78
|
-
def push_objects(objects)
|
79
|
-
@source.lock(:md) do |s|
|
78
|
+
def push_objects(objects,timeout=10,raise_on_expire=false)
|
79
|
+
@source.lock(:md,timeout,raise_on_expire) do |s|
|
80
80
|
doc = @source.get_data(:md)
|
81
|
+
orig_doc_size = doc.size
|
81
82
|
objects.each do |id,obj|
|
82
83
|
doc[id] ||= {}
|
83
84
|
doc[id].merge!(obj)
|
84
85
|
end
|
86
|
+
diff_count = doc.size - orig_doc_size
|
85
87
|
@source.put_data(:md,doc)
|
86
|
-
@source.update_count(:md_size,
|
88
|
+
@source.update_count(:md_size,diff_count)
|
87
89
|
end
|
88
90
|
end
|
89
91
|
|
90
|
-
def push_deletes(objects)
|
91
|
-
@source.lock(:md) do |s|
|
92
|
+
def push_deletes(objects,timeout=10,raise_on_expire=false)
|
93
|
+
@source.lock(:md,timeout,raise_on_expire) do |s|
|
92
94
|
doc = @source.get_data(:md)
|
95
|
+
orig_doc_size = doc.size
|
93
96
|
objects.each do |id|
|
94
97
|
doc.delete(id)
|
95
98
|
end
|
99
|
+
diff_count = doc.size - orig_doc_size
|
96
100
|
@source.put_data(:md,doc)
|
97
|
-
@source.update_count(:md_size,
|
101
|
+
@source.update_count(:md_size,diff_count)
|
98
102
|
end
|
99
103
|
end
|
100
104
|
|
data/lib/rhosync/store.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module Rhosync
|
2
|
+
|
3
|
+
class StoreLockException < RuntimeError; end
|
4
|
+
|
2
5
|
class Store
|
3
6
|
RESERVED_ATTRIB_NAMES = ["attrib_type", "id"] unless defined? RESERVED_ATTRIB_NAMES
|
4
7
|
@@db = nil
|
@@ -139,25 +142,36 @@ module Rhosync
|
|
139
142
|
end
|
140
143
|
|
141
144
|
# Lock a given key and release when provided block is finished
|
142
|
-
def lock(dockey,timeout=0)
|
143
|
-
m_lock = get_lock(dockey,timeout)
|
145
|
+
def lock(dockey,timeout=0,raise_on_expire=false)
|
146
|
+
m_lock = get_lock(dockey,timeout,raise_on_expire)
|
144
147
|
res = yield
|
145
148
|
release_lock(dockey,m_lock)
|
146
149
|
res
|
147
150
|
end
|
148
|
-
|
149
|
-
def get_lock(dockey,timeout=0)
|
151
|
+
|
152
|
+
def get_lock(dockey,timeout=0,raise_on_expire=false)
|
150
153
|
lock_key = _lock_key(dockey)
|
151
154
|
current_time = Time.now.to_i
|
152
|
-
ts = current_time+timeout+1
|
153
|
-
|
154
|
-
|
155
|
-
if
|
156
|
-
@@db.
|
157
|
-
|
155
|
+
ts = current_time+(Rhosync.lock_duration || timeout)+1
|
156
|
+
loop do
|
157
|
+
if not @@db.setnx(lock_key,ts)
|
158
|
+
if raise_on_expire or Rhosync.raise_on_expired_lock
|
159
|
+
if @@db.get(lock_key).to_i <= current_time
|
160
|
+
# lock expired before operation which set it up completed
|
161
|
+
# this process cannot continue without corrupting locked data
|
162
|
+
raise StoreLockException, "Lock \"#{lock_key}\" expired before it was released"
|
163
|
+
end
|
164
|
+
else
|
165
|
+
if @@db.get(lock_key).to_i <= current_time and
|
166
|
+
@@db.getset(lock_key,ts).to_i <= current_time
|
167
|
+
# previous lock expired and we replaced it with our own
|
168
|
+
break
|
169
|
+
end
|
158
170
|
end
|
159
171
|
sleep(1)
|
160
172
|
current_time = Time.now.to_i
|
173
|
+
else
|
174
|
+
break #no lock was set, so we set ours and leaving
|
161
175
|
end
|
162
176
|
end
|
163
177
|
return ts
|
data/lib/rhosync/version.rb
CHANGED
data/lib/rhosync.rb
CHANGED
@@ -43,7 +43,8 @@ module Rhosync
|
|
43
43
|
class << self
|
44
44
|
attr_accessor :base_directory, :app_directory, :data_directory,
|
45
45
|
:vendor_directory, :blackberry_bulk_sync, :redis, :environment,
|
46
|
-
:log_disabled, :license, :bulk_sync_poll_interval, :stats
|
46
|
+
:log_disabled, :license, :bulk_sync_poll_interval, :stats,
|
47
|
+
:raise_on_expired_lock, :lock_duration
|
47
48
|
end
|
48
49
|
|
49
50
|
### Begin Rhosync setup methods
|
@@ -61,6 +62,8 @@ module Rhosync
|
|
61
62
|
Rhosync.bulk_sync_poll_interval = get_setting(config,environment,:bulk_sync_poll_interval,3600)
|
62
63
|
Rhosync.redis = get_setting(config,environment,:redis,false)
|
63
64
|
Rhosync.log_disabled = get_setting(config,environment,:log_disabled,false)
|
65
|
+
Rhosync.raise_on_expired_lock = get_setting(config,environment,:raise_on_expired_lock,false)
|
66
|
+
Rhosync.lock_duration = get_setting(config,environment,:lock_duration)
|
64
67
|
Rhosync.environment = environment
|
65
68
|
yield self if block_given?
|
66
69
|
Store.create(Rhosync.redis)
|
@@ -69,9 +72,6 @@ module Rhosync
|
|
69
72
|
Rhosync.app_directory ||= Rhosync.base_directory
|
70
73
|
Rhosync.data_directory ||= File.join(Rhosync.base_directory,'data')
|
71
74
|
Rhosync.vendor_directory ||= File.join(Rhosync.base_directory,'vendor')
|
72
|
-
Rhosync.blackberry_bulk_sync ||= false
|
73
|
-
Rhosync.bulk_sync_poll_interval ||= 3600
|
74
|
-
Rhosync.log_disabled ||= false
|
75
75
|
Rhosync.stats ||= false
|
76
76
|
Rhosync.license = License.new
|
77
77
|
|
@@ -11,6 +11,7 @@ describe "RhosyncApiGetClientParams" do
|
|
11
11
|
{"name"=>"device_type", "value"=>"Apple", "type"=>"string"},
|
12
12
|
{"name"=>"device_pin", "value"=>"abcd", "type"=>"string"},
|
13
13
|
{"name"=>"device_port", "value"=>"3333", "type"=>"string"},
|
14
|
+
{"name"=>"phone_id", "value"=>nil, "type"=>"string"},
|
14
15
|
{"name"=>"user_id", "value"=>"testuser", "type"=>"string"},
|
15
16
|
{"name"=>"app_id", "value"=>"application", "type"=>"string"}].sort{|x,y| x['name']<=>y['name']}
|
16
17
|
end
|
@@ -6,7 +6,7 @@ describe "RhosyncApiPushDeletes" do
|
|
6
6
|
it "should delete object from :md" do
|
7
7
|
data = {'1' => @product1, '2' => @product2, '3' => @product3}
|
8
8
|
@s = Source.load(@s_fields[:name],@s_params)
|
9
|
-
set_state(@s.docname(:md) => data)
|
9
|
+
set_state(@s.docname(:md) => data,@s.docname(:md_size) => '3')
|
10
10
|
data.delete('2')
|
11
11
|
post "/api/push_deletes", :api_token => @api_token,
|
12
12
|
:user_id => @u.id, :source_id => @s_fields[:name], :objects => ['2']
|
@@ -15,7 +15,7 @@ describe "RhosyncApiPushObjects" do
|
|
15
15
|
data = {'1' => @product1, '2' => @product2, '3' => @product3}
|
16
16
|
update = {'price' => '0.99', 'new_field' => 'value'}
|
17
17
|
@s = Source.load(@s_fields[:name],@s_params)
|
18
|
-
set_state(@s.docname(:md) => data)
|
18
|
+
set_state(@s.docname(:md) => data,@s.docname(:md_size) => '3')
|
19
19
|
update.each do |key,value|
|
20
20
|
data['2'][key] = value
|
21
21
|
end
|
@@ -127,6 +127,7 @@ describe "RhosyncApi" do
|
|
127
127
|
{"name"=>"device_pin", "value"=>"abcd", "type"=>"string"},
|
128
128
|
{"name"=>"device_port", "value"=>"3333", "type"=>"string"},
|
129
129
|
{"name"=>"user_id", "value"=>"testuser", "type"=>"string"},
|
130
|
+
{"name"=>"phone_id", "value"=>nil, "type"=>"string"},
|
130
131
|
{"name"=>"app_id", "value"=>"application", "type"=>"string"}].sort{|x,y| x['name']<=>y['name']}
|
131
132
|
end
|
132
133
|
|
data/spec/jobs/ping_job_spec.rb
CHANGED
@@ -7,7 +7,7 @@ describe "PingJob" do
|
|
7
7
|
it "should perform apple ping" do
|
8
8
|
params = {"user_id" => @u.id, "api_token" => @api_token,
|
9
9
|
"sources" => [@s.name], "message" => 'hello world',
|
10
|
-
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3'}
|
10
|
+
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3', "phone_id" => nil}
|
11
11
|
Apple.should_receive(:ping).once.with({'device_pin' => @c.device_pin,
|
12
12
|
'device_port' => @c.device_port}.merge!(params))
|
13
13
|
PingJob.perform(params)
|
@@ -16,7 +16,7 @@ describe "PingJob" do
|
|
16
16
|
it "should perform blackberry ping" do
|
17
17
|
params = {"user_id" => @u.id, "api_token" => @api_token,
|
18
18
|
"sources" => [@s.name], "message" => 'hello world',
|
19
|
-
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3'}
|
19
|
+
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3', "phone_id" => nil}
|
20
20
|
@c.device_type = 'blackberry'
|
21
21
|
Blackberry.should_receive(:ping).once.with({'device_pin' => @c.device_pin,
|
22
22
|
'device_port' => @c.device_port}.merge!(params))
|
@@ -35,7 +35,7 @@ describe "PingJob" do
|
|
35
35
|
it "should skip ping for empty device_pin" do
|
36
36
|
params = {"user_id" => @u.id, "api_token" => @api_token,
|
37
37
|
"sources" => [@s.name], "message" => 'hello world',
|
38
|
-
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3'}
|
38
|
+
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3',"phone_id"=>nil}
|
39
39
|
@c.device_type = 'blackberry'
|
40
40
|
@c.device_pin = nil
|
41
41
|
PingJob.should_receive(:log).once.with("Skipping ping for non-registered client_id '#{@c.id}'...")
|
@@ -45,7 +45,7 @@ describe "PingJob" do
|
|
45
45
|
it "should drop ping if it's already in user's device pin list" do
|
46
46
|
params = {"user_id" => @u.id, "api_token" => @api_token,
|
47
47
|
"sources" => [@s.name], "message" => 'hello world',
|
48
|
-
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3'}
|
48
|
+
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3', "phone_id"=>nil}
|
49
49
|
|
50
50
|
# another client with the same device pin ...
|
51
51
|
@c1 = Client.create(@c_fields,{:source_name => @s_fields[:name]})
|
@@ -57,4 +57,21 @@ describe "PingJob" do
|
|
57
57
|
lambda { PingJob.perform(params) }.should_not raise_error
|
58
58
|
end
|
59
59
|
|
60
|
+
it "should drop ping if it's already in user's phone id list and device pin is different" do
|
61
|
+
params = {"user_id" => @u.id, "api_token" => @api_token,
|
62
|
+
"sources" => [@s.name], "message" => 'hello world',
|
63
|
+
"vibrate" => '5', "badge" => '5', "sound" => 'hello.mp3'}
|
64
|
+
@c.phone_id = '3'
|
65
|
+
@c_fields.merge!(:phone_id => '3')
|
66
|
+
# another client with the same phone id..
|
67
|
+
@c1 = Client.create(@c_fields,{:source_name => @s_fields[:name]})
|
68
|
+
# yet another...
|
69
|
+
@c2 = Client.create(@c_fields,{:source_name => @s_fields[:name]})
|
70
|
+
|
71
|
+
Apple.should_receive(:ping).with({'device_pin' => @c.device_pin, 'phone_id' => @c.phone_id, 'device_port' => @c.device_port}.merge!(params))
|
72
|
+
PingJob.should_receive(:log).twice.with(/Dropping ping request for client/)
|
73
|
+
lambda { PingJob.perform(params) }.should_not raise_error
|
74
|
+
end
|
75
|
+
|
76
|
+
|
60
77
|
end
|
data/spec/source_adapter_spec.rb
CHANGED
@@ -16,18 +16,27 @@ describe "SourceAdapter" do
|
|
16
16
|
it_should_behave_like "SourceAdapterHelper"
|
17
17
|
|
18
18
|
before(:each) do
|
19
|
-
@s = Source.load(
|
20
|
-
@s.name = 'SimpleAdapter'
|
19
|
+
@s = Source.load('SimpleAdapter',@s_params)
|
21
20
|
@sa = SourceAdapter.create(@s,nil)
|
22
21
|
end
|
23
22
|
|
23
|
+
def setup_adapter(name)
|
24
|
+
fields = {
|
25
|
+
:name => name,
|
26
|
+
:url => 'http://example.com',
|
27
|
+
:login => 'testuser',
|
28
|
+
:password => 'testpass',
|
29
|
+
}
|
30
|
+
Source.create(fields,@s_params)
|
31
|
+
end
|
32
|
+
|
24
33
|
it "should create SourceAdapter with source" do
|
25
34
|
@sa.class.name.should == @s.name
|
26
35
|
end
|
27
36
|
|
28
37
|
it "should create and execute SubAdapter that extends BaseAdapter" do
|
29
|
-
|
30
|
-
@sa = SourceAdapter.create(
|
38
|
+
sub = setup_adapter('SubAdapter')
|
39
|
+
@sa = SourceAdapter.create(sub,nil)
|
31
40
|
@sa.class.name.should == 'SubAdapter'
|
32
41
|
expected = {'1'=>@product1,'2'=>@product2}
|
33
42
|
@sa.inject_result expected
|
@@ -35,15 +44,14 @@ describe "SourceAdapter" do
|
|
35
44
|
end
|
36
45
|
|
37
46
|
it "should fail to create SourceAdapter" do
|
38
|
-
|
39
|
-
broken_source = Source.create(@s_fields,@s_params)
|
47
|
+
broken_source = setup_adapter('Broken')
|
40
48
|
lambda { SourceAdapter.create(broken_source) }.should raise_error(Exception)
|
41
49
|
broken_source.delete
|
42
50
|
end
|
43
51
|
|
44
52
|
it "should create SourceAdapter with trailing spaces" do
|
45
|
-
|
46
|
-
SourceAdapter.create(
|
53
|
+
s = setup_adapter('SimpleAdapter ')
|
54
|
+
SourceAdapter.create(s,nil).is_a?(SimpleAdapter).should be_true
|
47
55
|
end
|
48
56
|
|
49
57
|
describe "SourceAdapter methods" do
|
data/spec/source_spec.rb
CHANGED
data/spec/spec.opts
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -280,13 +280,20 @@ describe "DBObjectsHelper", :shared => true do
|
|
280
280
|
:login => 'testuser',
|
281
281
|
:password => 'testpass',
|
282
282
|
}
|
283
|
+
@s1_fields = {
|
284
|
+
:name => 'FixedSchemaAdapter',
|
285
|
+
:url => 'http://example.com',
|
286
|
+
:login => 'testuser',
|
287
|
+
:password => 'testpass',
|
288
|
+
}
|
283
289
|
@s_params = {
|
284
290
|
:user_id => @u.id,
|
285
291
|
:app_id => @a.id
|
286
292
|
}
|
287
293
|
@c = Client.create(@c_fields,{:source_name => @s_fields[:name]})
|
288
294
|
@s = Source.create(@s_fields,@s_params)
|
289
|
-
@s1 = Source.
|
295
|
+
@s1 = Source.create(@s1_fields,@s_params)
|
296
|
+
@s1.belongs_to = [{'brand' => 'SampleAdapter'}].to_json
|
290
297
|
config = Rhosync.source_config["sources"]['FixedSchemaAdapter']
|
291
298
|
@s1.update(config)
|
292
299
|
@r = @s.read_state
|
data/spec/stats/record_spec.rb
CHANGED
data/spec/store_spec.rb
CHANGED
@@ -159,11 +159,42 @@ describe "Store" do
|
|
159
159
|
|
160
160
|
it "should lock key for timeout" do
|
161
161
|
doc = "locked_data"
|
162
|
-
|
163
|
-
Store.
|
164
|
-
|
162
|
+
lock = Time.now.to_i+3
|
163
|
+
Store.db.set "lock:#{doc}", lock
|
164
|
+
Store.should_receive(:sleep).at_least(:once).with(1).and_return { sleep 1; Store.release_lock(doc,lock); }
|
165
|
+
Store.get_lock(doc,4)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should raise exception if lock expires" do
|
169
|
+
doc = "locked_data"
|
170
|
+
Store.get_lock(doc)
|
171
|
+
lambda { sleep 2; Store.get_lock(doc,4,true) }.should raise_error(StoreLockException,"Lock \"lock:locked_data\" expired before it was released")
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should raise lock expires exception on global setting" do
|
175
|
+
doc = "locked_data"
|
176
|
+
Store.get_lock(doc)
|
177
|
+
Rhosync.raise_on_expired_lock = true
|
178
|
+
lambda { sleep 2; Store.get_lock(doc,4) }.should raise_error(StoreLockException,"Lock \"lock:locked_data\" expired before it was released")
|
179
|
+
Rhosync.raise_on_expired_lock = false
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should acquire lock if it expires" do
|
183
|
+
doc = "locked_data"
|
184
|
+
Store.get_lock(doc)
|
185
|
+
sleep 2
|
186
|
+
Store.get_lock(doc,1).should > Time.now.to_i
|
165
187
|
end
|
166
188
|
|
189
|
+
it "should use global lock duration" do
|
190
|
+
doc = "locked_data"
|
191
|
+
Rhosync.lock_duration = 2
|
192
|
+
Store.get_lock(doc)
|
193
|
+
Store.should_receive(:sleep).exactly(3).times.with(1).and_return { sleep 1 }
|
194
|
+
Store.get_lock(doc)
|
195
|
+
Rhosync.lock_duration = nil
|
196
|
+
end
|
197
|
+
|
167
198
|
it "should lock document in block" do
|
168
199
|
doc = "locked_data"
|
169
200
|
Store.lock(doc,0) do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rhosync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 5
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 2.1.
|
9
|
+
- 7
|
10
|
+
version: 2.1.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Rhomobile
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-06-27 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: json
|
@@ -166,7 +166,7 @@ dependencies:
|
|
166
166
|
requirement: &id010 !ruby/object:Gem::Requirement
|
167
167
|
none: false
|
168
168
|
requirements:
|
169
|
-
- -
|
169
|
+
- - ~>
|
170
170
|
- !ruby/object:Gem::Version
|
171
171
|
hash: 49
|
172
172
|
segments:
|
@@ -572,7 +572,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
572
572
|
requirements: []
|
573
573
|
|
574
574
|
rubyforge_project:
|
575
|
-
rubygems_version: 1.8.
|
575
|
+
rubygems_version: 1.8.5
|
576
576
|
signing_key:
|
577
577
|
specification_version: 3
|
578
578
|
summary: RhoSync Synchronization Framework
|