class Mhc::Sync::StatusManager
It wraps existing database to adds ability to manage etag cache for sync status tracking. The downstream database is supposed to respond to:
-
report_etags
report_etags(uids = nil) # returns one of: => {uid_string => etag_object } # Hash => {uid_string => etag_string } # Hash => [uid_etag_object] # Array
uid_etag_object is an object which respond to
etag
and uid method. etag_object is an object which respond toetag
method. -
get_with_etag
get_with_etag(uid) # => [RECORD, etag]
Each RECORD has to respond to to_ics_string or body that returns iCalendar-conformed string.
-
put_if_match
put_if_match(uid, ics_string, expected_etag) # => put ics_string if "ETAG" equals to expected_etag
-
delete_if_match
delete_if_match(uid, expected_etag) # => delete uid if "ETAG" equals to expected_etag
expected_etag is a string for sync. if expected_etag is omitted (or nil), put_if_match and delete_if_match will ignore conflictions.
Public Class Methods
# File lib/mhc/sync/status_manager.rb, line 36 def initialize(real_db, etag_db) @db, @etag_db = real_db, etag_db refresh_status end
Public Instance Methods
# File lib/mhc/sync/status_manager.rb, line 91 def delete(uid) current_record = syncinfo(uid) if @db.delete_if_match(current_record.uid, current_record.ex_etag) refresh_status(current_record.uid) current_record.mark_synced return Status.new(current_record.uid, self) else return nil end end
# File lib/mhc/sync/status_manager.rb, line 109 def etag(uid) if @db_status[uid].respond_to?(:etag) return @db_status[uid].etag else return @db_status[uid] end end
# File lib/mhc/sync/status_manager.rb, line 117 def ex_etag(uid) @etag_status[uid] end
delegation to original DB with sync-status check.
To make sure any kind of update is safe, you may want to check current_record.ex_etag == current_record.etag (means current_record.unmodified? is true) like:
if current_record.unmodified? @db.put(new_record) end
However, this will not work. Because this check-and-update must be an atomic operation. So, instead, we have to do:
@db.put_if_match(new_record, current_record.ex_etag)
# File lib/mhc/sync/status_manager.rb, line 66 def get(uid) res, etag = @db.get_with_etag(uid) @db_status[uid] = etag if etag return Status.new(uid, self, res) end
# File lib/mhc/sync/status_manager.rb, line 121 def mark_synced(uid, etag) @etag_db.put(uid, etag) return self end
# File lib/mhc/sync/status_manager.rb, line 72 def put(modified_record, overwrite = false) current_record = syncinfo(modified_record.uid) expected_etag = overwrite ? nil : current_record.ex_etag if @db.put_if_match(current_record.uid, modified_record.to_ics_string, expected_etag) ## XXX: put_if_match should return the new etag value, and we ## have to use it as a new etag for the current record. ## However, some CalDAV servers (Google Calendar) do not ## return any etag on PUT. ## So, we have to PROPFIND immediately after the ## PUT. This is a small crack on atomicity refresh_status(current_record.uid) # refresh propfind cache. current_record.mark_synced return Status.new(current_record.uid, self) else return nil # put failed. end end
propfind with cache
# File lib/mhc/sync/status_manager.rb, line 103 def refresh_status(uids = :all) ### XXX: care fore UIDs for partial update. @db_status = make_hash(@db.report_etags) @etag_status = make_hash(@etag_db.report_etags) end
# File lib/mhc/sync/status_manager.rb, line 41 def syncinfo(uid) Status.new(uid, self) end
# File lib/mhc/sync/status_manager.rb, line 45 def uid_list (@db_status.keys + @etag_status.keys).sort.uniq end
Private Instance Methods
etag_report is one of:
+ {uid_string => etag_object } style hash, + {uid_string => etag_string } style hash, + [uid_etag_object] style array,
uid_etag_object is an object which respond to etag
and uid method. etag_object is an object which respond to etag
method.
# File lib/mhc/sync/status_manager.rb, line 133 def make_hash(etag_report) return etag_report if etag_report.respond_to?(:keys) hash = {} etag_report.map {|o| hash[o.uid] = o.etag} if etag_report return hash end