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