rhosync 2.1.6 → 2.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|