rhosync 2.1.11 → 2.1.12

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,4 +1,9 @@
1
- ## 2.1.11 (not released)
1
+ ## 2.1.12
2
+ * #19304885 - fixing race condition in get_lock (lock is released between setnx and get calls)
3
+ * #18508155 - on failed syncs allow the user to retry it up to pre-defined number of times (another approach)
4
+ * #19143845 - added sinatra 1.3.x support
5
+
6
+ ## 2.1.11
2
7
  * #17526603 - implement clientreset support for specified sources
3
8
  * #18356697 - store lock is never released (support request #1466)
4
9
  * use redis 2.2.12 by default
data/Rakefile CHANGED
@@ -66,7 +66,7 @@ begin
66
66
  gemspec.files = FileList["[A-Z]*", "{bench,bin,generators,lib,spec,tasks}/**/*"]
67
67
 
68
68
  # TODO: Due to https://www.pivotaltracker.com/story/show/3417862, we can't use JSON 1.4.3
69
- gemspec.add_dependency "sinatra", "= 1.2.7"
69
+ gemspec.add_dependency "sinatra", ">= 1.2.7"
70
70
  gemspec.add_dependency "json", "~>1.4.2"
71
71
  gemspec.add_dependency "sqlite3-ruby", "~>1.2.5"
72
72
  gemspec.add_dependency "rubyzip", "~>0.9.4"
@@ -0,0 +1,29 @@
1
+ module Rhosync
2
+ class BodyContentTypeParser
3
+
4
+ # Constants
5
+ #
6
+ CONTENT_TYPE = 'CONTENT_TYPE'.freeze
7
+ POST_BODY = 'rack.input'.freeze
8
+ FORM_INPUT = 'rack.request.form_input'.freeze
9
+ FORM_HASH = 'rack.request.form_hash'.freeze
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ if env['CONTENT_TYPE'] && env['CONTENT_TYPE'].match(/^application\/json/)
17
+ begin
18
+ if (body = env[POST_BODY].read).length != 0
19
+ env.update(FORM_HASH => JSON.parse(body), FORM_INPUT => env[POST_BODY])
20
+ end
21
+ rescue JSON::ParserError => jpe
22
+ log jpe.message + jpe.backtrace.join("\n")
23
+ return [500, {'Content-Type' => 'text/plain'}, ["Server error while processing client data"]]
24
+ end
25
+ end
26
+ @app.call(env)
27
+ end
28
+ end
29
+ end
@@ -40,7 +40,7 @@ module Rhosync
40
40
  return _resend_search_result if params[:token] and params[:resend]
41
41
  if params[:token] and !_ack_search(params[:token])
42
42
  formatted_result = _format_search_result
43
- @client.flash_data('search*')
43
+ _delete_search
44
44
  return formatted_result
45
45
  end
46
46
  end
@@ -279,7 +279,7 @@ module Rhosync
279
279
 
280
280
  def _ack_search(search_token)
281
281
  if @client.get_value(:search_token) != search_token
282
- @client.flash_data('search*')
282
+ _delete_search
283
283
  @client.put_data(:search_errors,
284
284
  {'search-error'=>{'message'=>'Search error - invalid token'}}
285
285
  )
@@ -294,7 +294,7 @@ module Rhosync
294
294
  @source_sync.search(@client.id,search_params) if params.nil? or !params[:token]
295
295
  res,diffsize = compute_search
296
296
  formatted_res = _format_search_result(res,diffsize)
297
- @client.flash_data('search*') if diffsize == 0
297
+ _delete_search if diffsize == 0
298
298
  formatted_res
299
299
  end
300
300
 
@@ -361,6 +361,12 @@ module Rhosync
361
361
  end
362
362
  @client.flash_data("update_rollback_page")
363
363
  end
364
+
365
+ def _delete_search
366
+ [:search, :search_page, :search_token, :search_errors].each do |search_doc|
367
+ @client.flash_data(search_doc)
368
+ end
369
+ end
364
370
 
365
371
  def _send_errors
366
372
  res = {}
@@ -15,9 +15,13 @@ module RhosyncConsole
15
15
  end
16
16
 
17
17
  class Server < Sinatra::Base
18
- set :views, RhosyncConsole::root_path("app","views")
19
- set :public, RhosyncConsole::root_path("app","public")
20
- set :static, true
18
+ set :views, RhosyncConsole::root_path("app","views")
19
+ if Sinatra.const_defined?("VERSION") && Gem::Version.new(Sinatra::VERSION) >= Gem::Version.new("1.3.0")
20
+ set :public_folder, RhosyncConsole::root_path("app","public")
21
+ else
22
+ set :public, RhosyncConsole::root_path("app","public")
23
+ end
24
+ set :static, true
21
25
  use Rack::Session::Cookie
22
26
  before do
23
27
  headers['Expires'] = 'Sun, 19 Nov 1978 05:00:00 GMT'
@@ -1,6 +1,7 @@
1
1
  module Rhosync
2
2
  class ReadState < Model
3
3
  field :refresh_time, :integer
4
+ field :prev_refresh_time, :integer
4
5
  field :retry_counter, :integer
5
6
 
6
7
  def self.create(fields)
@@ -9,6 +10,7 @@ module Rhosync
9
10
  fields.delete(:user_id)
10
11
  fields.delete(:source_name)
11
12
  fields[:refresh_time] ||= Time.now.to_i
13
+ fields[:prev_refresh_time] ||= Time.now.to_i
12
14
  fields[:retry_counter] ||= 0
13
15
  super(fields,{})
14
16
  end
@@ -4,6 +4,7 @@ require 'erb'
4
4
  require 'json'
5
5
  require 'fileutils'
6
6
  require 'rhosync'
7
+ require 'rhosync/body_content_type_parser'
7
8
 
8
9
  module Rhosync
9
10
 
@@ -17,15 +18,17 @@ module Rhosync
17
18
 
18
19
  class Server < Sinatra::Base
19
20
  libdir = File.dirname(File.expand_path(__FILE__))
20
- set :views, "#{libdir}/server/views"
21
- set :public, "#{libdir}/server/public"
22
- set :static, true
21
+ set :views, "#{libdir}/server/views"
22
+ if Sinatra.const_defined?("VERSION") && Gem::Version.new(Sinatra::VERSION) >= Gem::Version.new("1.3.0")
23
+ set :public_folder, "#{libdir}/server/public"
24
+ else
25
+ set :public, "#{libdir}/server/public"
26
+ end
27
+ set :static, true
28
+ set :stats, false
23
29
 
24
30
  # default secret
25
31
  @@secret = '<changeme>'
26
-
27
- # stats middleware disabled by default
28
- @@stats = false
29
32
 
30
33
  # Setup route and mimetype for bulk data downloads
31
34
  # TODO: Figure out why "mime :data, 'application/octet-stream'" doesn't work
@@ -60,7 +63,7 @@ module Rhosync
60
63
  def login
61
64
  if params[:login] == 'rhoadmin'
62
65
  user = User.authenticate(params[:login], params[:password])
63
- elsif current_app and current_app.can_authenticate?
66
+ elsif current_app and current_app.can_authenticate? and params[:login]
64
67
  user = current_app.authenticate(params[:login], params[:password], session)
65
68
  end
66
69
  if user
@@ -142,26 +145,25 @@ module Rhosync
142
145
 
143
146
  # hook into new so we can enable middleware
144
147
  def self.new
145
- if @@stats == true
148
+ use Rhosync::BodyContentTypeParser
149
+ if settings.respond_to?(:stats) and settings.send(:stats) == true
146
150
  use Rhosync::Stats::Middleware
147
- Rhosync.stats = true
151
+ Rhosync.stats = true
152
+ else
153
+ Rhosync::Server.disable :stats
154
+ Rhosync.stats = false
148
155
  end
156
+ Rhosync::Server.set :secret, @@secret unless settings.respond_to?(:secret)
149
157
  use Rack::Session::Cookie,
150
- :key => 'rhosync_session',
151
- :expire_after => 31536000,
152
- :secret => @@secret
153
- super
154
- end
155
-
156
- def self.set(option, value=self, &block)
157
- @@stats = value if option == :stats and (value.is_a?(TrueClass) or value.is_a?(FalseClass))
158
- @@secret = value if option == :secret and value.is_a?(String)
158
+ :key => 'rhosync_session',
159
+ :expire_after => 31536000,
160
+ :secret => Rhosync::Server.secret
159
161
  super
160
162
  end
161
163
 
162
164
  def initialize
163
165
  # Whine about default session secret
164
- check_default_secret!(@@secret)
166
+ check_default_secret!(Rhosync::Server.secret)
165
167
  super
166
168
  end
167
169
 
@@ -173,11 +175,6 @@ module Rhosync
173
175
  cud = JSON.parse(params["cud"])
174
176
  params.delete("cud")
175
177
  params.merge!(cud)
176
- end
177
- #application/json; charset=UTF-8
178
- if request.env['CONTENT_TYPE'] && request.env['CONTENT_TYPE'].match(/^application\/json/)
179
- params.merge!(JSON.parse(request.body.read))
180
- request.body.rewind
181
178
  end
182
179
  rescue JSON::ParserError => jpe
183
180
  log jpe.message + jpe.backtrace.join("\n")
@@ -189,7 +186,6 @@ module Rhosync
189
186
  if params[:version] and params[:version].to_i < 3
190
187
  throw :halt, [404, "Server supports version 3 or higher of the protocol."]
191
188
  end
192
- #log "request params: #{params.inspect}"
193
189
  end
194
190
 
195
191
  %w[get post].each do |verb|
@@ -265,40 +265,50 @@ module Rhosync
265
265
  end
266
266
 
267
267
  def if_need_refresh(client_id=nil,params=nil)
268
- need_refresh = check_refresh_time
268
+ need_refresh = lock(:md) do |s|
269
+ check = check_refresh_time
270
+ self.read_state.prev_refresh_time = self.read_state.refresh_time if check
271
+ self.read_state.refresh_time = Time.now.to_i + self.poll_interval if check
272
+ check
273
+ end
269
274
  yield client_id,params if need_refresh
270
275
  end
271
276
 
272
- def update_refresh_time(query_failure = false)
273
- if self.poll_interval == 0
274
- self.read_state.refresh_time = Time.now.to_i + self.poll_interval
275
- return
276
- end
277
-
278
- allowed_update = true
279
- # reset number of retries on succesfull query
280
- # or if last refresh was more than 'poll_interval' time ago
281
- if not query_failure or (Time.now.to_i - self.read_state.refresh_time >= self.poll_interval)
282
- self.read_state.retry_counter = 0
283
- end
277
+ def rewind_refresh_time(query_failure)
278
+ return if self.poll_interval == 0
279
+ lock(:md) do |s|
280
+ rewind_time = false
281
+ # reset number of retries
282
+ # and prev_refresh_time on succesfull query
283
+ # or if last refresh was more than 'poll_interval' time ago
284
+ if not query_failure or ((Time.now.to_i - self.read_state.prev_refresh_time) >= self.poll_interval)
285
+ # we need to reset the prev_refresh_time here
286
+ # otherwise in case of expired poll interval
287
+ # and repeating failures - it will reset the counter
288
+ # on every error
289
+ self.read_state.prev_refresh_time = Time.now.to_i
290
+ self.read_state.retry_counter = 0
291
+ end
284
292
 
285
- # do not reset the refresh time on failure
286
- # if retry limit is not reached
287
- if query_failure
288
- if self.read_state.retry_counter < self.retry_limit
289
- allowed_update = false
290
- self.read_state.increment!(:retry_counter)
291
- # we have reached the limit - update the refresh time
292
- # and reset the counter
293
- else
294
- self.read_state.retry_counter = 0
293
+ # rewind the refresh time on failure
294
+ # if retry limit is not reached
295
+ if query_failure
296
+ if self.read_state.retry_counter < self.retry_limit
297
+ self.read_state.increment!(:retry_counter)
298
+ rewind_time = true
299
+ # we have reached the limit - do not rewind the refresh time
300
+ # and reset the counter
301
+ else
302
+ self.read_state.retry_counter = 0
303
+ end
295
304
  end
296
- end
297
- if allowed_update
298
- self.read_state.refresh_time = Time.now.to_i + self.poll_interval
299
- end
305
+
306
+ if rewind_time
307
+ self.read_state.refresh_time = self.read_state.prev_refresh_time
308
+ end
309
+ end
300
310
  end
301
-
311
+
302
312
  private
303
313
  def poll_interval_key
304
314
  "source:#{self.name}:poll_interval"
@@ -41,16 +41,20 @@ module Rhosync
41
41
 
42
42
  def sync
43
43
  if @result and @result.empty?
44
- @source.flash_data(:md)
45
- @source.put_value(:md_size,0)
44
+ @source.lock(:md) do |s|
45
+ s.flash_data(:md)
46
+ s.put_value(:md_size,0)
47
+ end
46
48
  else
47
49
  if @result
48
50
  Store.put_data(@tmp_docname,@result)
49
51
  @stash_size += @result.size
50
52
  end
51
- @source.flash_data(:md)
52
- Store.rename(@tmp_docname,@source.docname(:md))
53
- @source.put_value(:md_size,@stash_size)
53
+ @source.lock(:md) do |s|
54
+ s.flash_data(:md)
55
+ Store.rename(@tmp_docname,s.docname(:md))
56
+ s.put_value(:md_size,@stash_size)
57
+ end
54
58
  end
55
59
  end
56
60
 
@@ -59,19 +59,19 @@ module Rhosync
59
59
  end
60
60
 
61
61
  def do_query(params=nil)
62
- @source.lock(:md) do
63
- @source.if_need_refresh do
64
- Stats::Record.update("source:query:#{@source.name}") do
65
- if _auth_op('login')
66
- result = self.read(nil,params)
67
- _auth_op('logoff')
68
- end
69
- # update refresh time
70
- query_failure = Store.get_keys(@source.docname(:errors)).size > 0
71
- @source.update_refresh_time(query_failure)
62
+ result = nil
63
+ @source.if_need_refresh do
64
+ Stats::Record.update("source:query:#{@source.name}") do
65
+ if _auth_op('login')
66
+ result = self.read(nil,params)
67
+ _auth_op('logoff')
72
68
  end
69
+ # re-wind refresh time in case of error
70
+ query_failure = Store.exists?(@source.docname(:errors))
71
+ @source.rewind_refresh_time(query_failure)
73
72
  end
74
73
  end
74
+ result
75
75
  end
76
76
 
77
77
  # Enqueue a job for the source based on job type
data/lib/rhosync/store.rb CHANGED
@@ -180,10 +180,23 @@ module Rhosync
180
180
 
181
181
  # Deletes all keys matching a given mask
182
182
  def flash_data(keymask)
183
- @@db.keys(keymask).each do |key|
184
- @@db.del(key)
183
+ if keymask[/[*\[\]?]/]
184
+ # If the keymask contains any pattern matching characters
185
+ # Use keys command to find all keys matching pattern (this is extremely expensive)
186
+ # Then delete matches
187
+ @@db.keys(keymask).each do |key|
188
+ @@db.del(key)
189
+ end
190
+ else
191
+ # The keymask doesn't contain pattern matching characters
192
+ # A delete call is all that is needed
193
+ @@db.del(keymask)
185
194
  end
186
195
  end
196
+
197
+ def exists?(key)
198
+ @@db.exists(key)
199
+ end
187
200
 
188
201
  # Returns array of keys matching a given keymask
189
202
  def get_keys(keymask)
@@ -209,19 +222,27 @@ module Rhosync
209
222
  ts = current_time+(Rhosync.lock_duration || timeout)+1
210
223
  loop do
211
224
  if not @@db.setnx(lock_key,ts)
212
- if raise_on_expire or Rhosync.raise_on_expired_lock
213
- if @@db.get(lock_key).to_i <= current_time
214
- # lock expired before operation which set it up completed
215
- # this process cannot continue without corrupting locked data
216
- raise StoreLockException, "Lock \"#{lock_key}\" expired before it was released"
217
- end
218
- else
219
- if @@db.get(lock_key).to_i <= current_time and
220
- @@db.getset(lock_key,ts).to_i <= current_time
221
- # previous lock expired and we replaced it with our own
222
- break
223
- end
224
- end
225
+ current_lock = @@db.get(lock_key)
226
+ # ensure lock wasn't released between the setnx and get calls
227
+ if current_lock
228
+ current_lock_timeout = current_lock.to_i
229
+ if raise_on_expire or Rhosync.raise_on_expired_lock
230
+ if current_lock_timeout <= current_time
231
+ # lock expired before operation which set it up completed
232
+ # this process cannot continue without corrupting locked data
233
+ raise StoreLockException, "Lock \"#{lock_key}\" expired before it was released"
234
+ end
235
+ else
236
+ if current_lock_timeout <= current_time and
237
+ @@db.getset(lock_key,ts).to_i <= current_time
238
+ # previous lock expired and we replaced it with our own
239
+ break
240
+ end
241
+ end
242
+ # lock was released between setnx and get - try to acquire it again
243
+ elsif @@db.setnx(lock_key,ts)
244
+ break
245
+ end
225
246
  sleep(1)
226
247
  current_time = Time.now.to_i
227
248
  else
@@ -1,3 +1,3 @@
1
1
  module Rhosync
2
- VERSION = '2.1.11'
2
+ VERSION = '2.1.12'
3
3
  end
@@ -3,12 +3,16 @@ require File.join(File.dirname(__FILE__),'api_helper')
3
3
  describe "RhosyncApiStats" do
4
4
  it_should_behave_like "ApiHelper"
5
5
 
6
+ def app
7
+ @app = Rhosync::Server.new
8
+ end
9
+
6
10
  before(:each) do
7
- Rhosync.stats = true
11
+ Rhosync::Server.set :stats, true
8
12
  end
9
13
 
10
14
  after(:each) do
11
- Rhosync.stats = false
15
+ Rhosync::Server.set :stats, false
12
16
  end
13
17
 
14
18
  it "should retrieve metric names" do
@@ -55,6 +59,7 @@ describe "RhosyncApiStats" do
55
59
  end
56
60
 
57
61
  it "should raise error if stats not enabled" do
62
+ Rhosync::Server.set :stats, false
58
63
  Rhosync.stats = false
59
64
  post "/api/stats", {
60
65
  :api_token => @api_token,
@@ -48,10 +48,10 @@ describe "Server" do
48
48
  User.load('new_user').should_not be_nil
49
49
  end
50
50
 
51
- it "should failt to login if wrong content-type" do
51
+ it "should fail to login if wrong content-type" do
52
52
  User.load('unknown').should be_nil
53
53
  post "/login", {"login" => 'unknown', "password" => 'testpass'}.to_json, {'CONTENT_TYPE'=>'application/x-www-form-urlencoded'}
54
- last_response.should be_ok
54
+ last_response.should_not be_ok
55
55
  User.load('unknown').should be_nil
56
56
  end
57
57
 
@@ -255,11 +255,10 @@ describe "Server" do
255
255
  last_response.body.should == "Server error while processing client data"
256
256
  end
257
257
 
258
- it "should handle client posting broken body" do
259
- broken_json = ['foo']
260
- post "/application", broken_json, {'CONTENT_TYPE'=>'application/json'}
261
- last_response.status.should == 500
262
- last_response.body.should == "Internal server error"
258
+ it "should not login if login is empty" do
259
+ post "/application/clientlogin", ''
260
+ last_response.status.should == 401
261
+ last_response.body.should == ""
263
262
  end
264
263
 
265
264
  it "should get inserts json" do
@@ -219,13 +219,13 @@ describe "SourceSync" do
219
219
  # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
220
220
  @s.read_state.retry_counter.should == 1
221
221
  @s.read_state.refresh_time.should <= Time.now.to_i
222
-
222
+
223
223
  # try once more and fail again
224
224
  set_test_data('test_db_storage',{},msg,"query error")
225
225
  res = @ss.do_query
226
226
  verify_result(@s.docname(:md) => {},
227
227
  @s.docname(:errors) => {'query-error'=>{'message'=>msg}})
228
-
228
+
229
229
  # 2) if retry_limit is set to N and number of retries exceeded it - update refresh_time
230
230
  @s.read_state.retry_counter.should == 0
231
231
  @s.read_state.refresh_time.should > Time.now.to_i
@@ -241,7 +241,7 @@ describe "SourceSync" do
241
241
  # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
242
242
  @s.read_state.retry_counter.should == 1
243
243
  @s.read_state.refresh_time.should <= Time.now.to_i
244
-
244
+
245
245
  # try once more (with success)
246
246
  expected = {'1'=>@product1,'2'=>@product2}
247
247
  set_test_data('test_db_storage',expected)
@@ -251,7 +251,40 @@ describe "SourceSync" do
251
251
  @s.read_state.retry_counter.should == 0
252
252
  @s.read_state.refresh_time.should > Time.now.to_i
253
253
  end
254
-
254
+
255
+ it "should reset the retry counter if prev_refresh_time was set more than poll_interval secs ago" do
256
+ @s.retry_limit = 3
257
+ @s.poll_interval = 2
258
+ msg = "Error during query"
259
+ set_test_data('test_db_storage',{},msg,"query error")
260
+ res = @ss.do_query
261
+ verify_result(@s.docname(:md) => {},
262
+ @s.docname(:errors) => {'query-error'=>{'message'=>msg}})
263
+ # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
264
+ @s.read_state.retry_counter.should == 1
265
+ @s.read_state.refresh_time.should <= Time.now.to_i
266
+
267
+ # 2) Make another error - results are the same
268
+ set_test_data('test_db_storage',{},msg,"query error")
269
+ res = @ss.do_query
270
+ verify_result(@s.docname(:md) => {},
271
+ @s.docname(:errors) => {'query-error'=>{'message'=>msg}})
272
+ # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
273
+ @s.read_state.retry_counter.should == 2
274
+ @s.read_state.refresh_time.should <= Time.now.to_i
275
+
276
+ # wait until time interval exprires and prev_refresh_time is too old -
277
+ # this should reset the counter on next request with error
278
+ # and do not update refresh_time
279
+ sleep(3)
280
+ set_test_data('test_db_storage',{},msg,"query error")
281
+ res = @ss.do_query
282
+ verify_result(@s.docname(:md) => {},
283
+ @s.docname(:errors) => {'query-error'=>{'message'=>msg}})
284
+ @s.read_state.retry_counter.should == 1
285
+ @s.read_state.refresh_time.should <= Time.now.to_i
286
+ end
287
+
255
288
  it "should do query with exception raised and update refresh time if retry_limit is 0" do
256
289
  @s.retry_limit = 0
257
290
  msg = "Error during query"
@@ -263,7 +296,7 @@ describe "SourceSync" do
263
296
  @s.read_state.retry_counter.should == 0
264
297
  @s.read_state.refresh_time.should > Time.now.to_i
265
298
  end
266
-
299
+
267
300
  it "should do query with exception raised and update refresh time if poll_interval == 0" do
268
301
  @s.retry_limit = 1
269
302
  @s.poll_interval = 0
@@ -292,6 +325,7 @@ describe "SourceSync" do
292
325
  verify_result("source:#{@test_app_name}:__shared__:#{@s_fields[:name]}:md" => expected)
293
326
  Store.db.keys("read_state:#{@test_app_name}:__shared__*").sort.should ==
294
327
  [ "read_state:#{@test_app_name}:__shared__:SampleAdapter:refresh_time",
328
+ "read_state:#{@test_app_name}:__shared__:SampleAdapter:prev_refresh_time",
295
329
  "read_state:#{@test_app_name}:__shared__:SampleAdapter:retry_counter",
296
330
  "read_state:#{@test_app_name}:__shared__:SampleAdapter:rho__id"].sort
297
331
  end
data/spec/store_spec.rb CHANGED
@@ -167,6 +167,29 @@ describe "Store" do
167
167
  Store.get_data(@s.docname(:md)).should == {}
168
168
  end
169
169
 
170
+ it "should flash_data for all keys matching pattern" do
171
+ keys = ['test_flash_data1','test_flash_data2']
172
+ keys.each {|key| Store.put_data(key,@data)}
173
+ Store.flash_data('test_flash_data*')
174
+ keys.each {|key| Store.get_data(key).should == {} }
175
+ end
176
+
177
+ it "should flash_data without calling KEYS when there aren't pattern matching characters in the provided keymask" do
178
+ key = 'test_flash_data'
179
+ Store.put_data(key,@data)
180
+ Store.db.should_not_receive(:keys)
181
+ Store.db.should_receive(:del).once.with(key).and_return(true)
182
+ Store.flash_data(key)
183
+ end
184
+
185
+ it "should flash_data and call KEYS when there are pattern matching characters in the provided keymask" do
186
+ keys = ['test_flash_data1','test_flash_data2']
187
+ keys.each {|key| Store.put_data(key,@data)}
188
+ Store.db.should_receive(:keys).exactly(1).times.with("test_flash_data*").and_return(keys)
189
+ Store.db.should_receive(:del).exactly(2).times.with(/^test_flash_data[12]$/).and_return(true)
190
+ Store.flash_data("test_flash_data*")
191
+ end
192
+
170
193
  it "should get_keys" do
171
194
  expected = ["doc1:1:1:1:source1", "doc1:1:1:1:source2"]
172
195
  Store.put_data(expected[0],@data)
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: 29
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 1
9
- - 11
10
- version: 2.1.11
9
+ - 12
10
+ version: 2.1.12
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-10-04 00:00:00 Z
18
+ date: 2011-10-14 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: sinatra
@@ -23,7 +23,7 @@ dependencies:
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
- - - "="
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  hash: 17
29
29
  segments:
@@ -372,6 +372,7 @@ files:
372
372
  - lib/rhosync/api/upload_file.rb
373
373
  - lib/rhosync/api_token.rb
374
374
  - lib/rhosync/app.rb
375
+ - lib/rhosync/body_content_type_parser.rb
375
376
  - lib/rhosync/bulk_data.rb
376
377
  - lib/rhosync/bulk_data/bulk_data.rb
377
378
  - lib/rhosync/bulk_data/syncdb.index.schema