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 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", ">=0.8.7"
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"
@@ -12,7 +12,7 @@ module Rhosync
12
12
  end
13
13
 
14
14
  def user
15
- @user ||= User.load(self.user_id)
15
+ User.load(self.user_id)
16
16
  end
17
17
  end
18
18
  end
@@ -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
@@ -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('Unknown client') unless client
9
- raise ArgumentError.new('Unknown source') unless source
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
- if client.device_type and client.device_type.size > 0 and client.device_pin and client.device_pin.size > 0
15
- unless device_pins.include? client.device_pin
16
- device_pins << client.device_pin
17
- klass = Object.const_get(camelize(client.device_type.downcase))
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
@@ -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
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Rhosync
2
2
  class ReadState < Model
3
- field :refresh_time,:integer
3
+ field :refresh_time, :integer
4
4
 
5
5
  def self.create(fields)
6
6
  fields[:id] = get_id(fields)
@@ -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
- instance_variable_set(:"@#{attrib}", value)
24
+ @@model_data[self.name.to_sym][attrib.to_sym] = value
17
25
  end
18
26
  define_method("#{attrib}") do
19
- instance_variable_get(:"@#{attrib}")
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
- instance_variable_set(:"@#{attrib}", value)
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(:"@#{attrib}")
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, :rho__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, :poll_interval])
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
- obj = @@model_data[obj_id.to_sym]
111
- obj.assign_args(params) if 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
- @@model_data.each { |k,v| v.delete }
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
- @user = User.load(self.user_id)
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
- @app = App.load(self.app_id)
208
+ App.load(self.app_id)
177
209
  end
178
210
 
179
211
  def schema
180
- @schema ||= self.get_value(:schema)
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]
@@ -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,doc.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,doc.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
- if not @@db.setnx(lock_key,ts)
154
- loop do
155
- if @@db.get(lock_key).to_i <= current_time and
156
- @@db.getset(lock_key,ts).to_i <= current_time
157
- break
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
@@ -1,3 +1,3 @@
1
1
  module Rhosync
2
- VERSION = '2.1.6'
2
+ VERSION = '2.1.7'
3
3
  end
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
 
@@ -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
@@ -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(@s_fields[:name],@s_params)
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
- @s.name = 'SubAdapter'
30
- @sa = SourceAdapter.create(@s,nil)
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
- @s_fields[:name] = 'Broken'
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
- @s.name = 'SimpleAdapter '
46
- SourceAdapter.create(@s,nil).is_a?(SimpleAdapter).should be_true
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
@@ -34,7 +34,6 @@ describe "Source" do
34
34
  @s1.poll_interval.should == 300
35
35
  @s1.app_id.should == @s_params[:app_id]
36
36
  @s1.user_id.should == @s_params[:user_id]
37
- # puts "#{@s1.inspect()}" # FIXME:
38
37
  end
39
38
 
40
39
  it "should create source with user" do
data/spec/spec.opts CHANGED
@@ -1,3 +1,4 @@
1
1
  --color
2
2
  -fn
3
- -b
3
+ -b
4
+ -R
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.load('FixedSchemaAdapter',@s_params)
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
@@ -65,6 +65,7 @@ describe "Record" do
65
65
  end
66
66
  end
67
67
  Store.db.zrange('stat:foo', 0, -1).should == ["1,1.0:14", "1,1.0:18", "1,1.0:20"]
68
+ Rhosync.stats = false
68
69
  end
69
70
 
70
71
  it "should get range of metric values" do
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
- Store.db.set "lock:#{doc}", Time.now.to_i+3
163
- Store.should_receive(:sleep).at_least(:once).with(1).and_return { sleep 1 }
164
- m_lock = Store.get_lock(doc,2)
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: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 1
9
- - 6
10
- version: 2.1.6
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-05-25 00:00:00 Z
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.3
575
+ rubygems_version: 1.8.5
576
576
  signing_key:
577
577
  specification_version: 3
578
578
  summary: RhoSync Synchronization Framework