1 Evernote
1.1 基本介紹
Evernote包括筆記(Note)、筆記本(Notebook)、標簽(Tag)、資源(Resource)、搜索記錄(SavedSearch)等概念。
USN(Update Sequence Number)是整個同步系統中最重要的東西,它用於標識賬戶中的每一次修改。每次修改后賬戶的USN就會+1。每一個對象(筆記本、筆記、標簽等所有的東西)都會有一個USN,標識着一個對象最后一次被修改時的賬戶USN。例如,在某一個時刻賬戶的USN是100,我添加一個筆記Note1,那么賬戶的USN會變成101,Note1的USN也是101;然后我再添加一個筆記Note2,這時賬戶的USN會變成102,Note2的USN也是102,Note1的還是101。這樣一來每次同步后記錄一下當時賬戶的USN保存為lastUSN,下次同步的時候,如果賬戶的USN>lastUSN,說明賬戶中有東西被修改了。對於每一個對象,比如Note1的USN>lastUSN說明服務器端的Note1被修改了。
服務端變量
updateCount:當前賬戶最新(最大)的USN
fullSyncBefore:客戶端執行增量同步或者完全同步的緩存截止時間。就是一個時間戳,這個變量的值通常是有東西被從賬戶永久性刪除的時間點,或者是非法客戶端USN造成一些服務器問題的時間點。
客戶端變量
lastUpdateCount:上次同步獲取的服務器端的updateCount變量
lastSyncTime:上次同步的時間(這個時間是從服務器上獲取的,也就是服務器時間)
1.2 同步策略
同步狀態
1 如果客戶端從來沒有和服務器同步過,就跳轉到*完全同步*
2 使用NoteStrore.getSyncState(…)獲取服務器端的updateCount和fullSyncBefore的值
a 如果fullSyncBrfore > lastSyncTime 跳轉到*完全同步*
b 如果 updateCount = lastUpdateCount 說明服務器端沒有更新過,跳轉到*發送改變*
c 不然就跳轉到*增量同步*
完全同步
1 使用NoteStore.getSyncChunk(…,afterUSN=0,maxEntries),從服務器獲取第一塊數據。這里要解釋兩個東西。一個是數據,這里數據指所有的類型對象,但是針對於像筆記、資源這種大對象,這里指返回一些元數據(包括Guid、Title等,而不包括Content、binary、 resources這種很大的字段)。如果是標簽(Tag)、搜索記錄(SavedSearches)等這種小對象,那么就會返回完整的對象。還有被永久性刪除的對象只返回GUID。二是關於兩個參數的解釋,服務器會返回USN>agterUSN的對象,但是最多返回maxEntries個,這就意味着我們可能需要通過多次獲取數據,並合並才能獲取到全部的數據。
a 如果上一步返回的Chunk對象的chunkHighUSN小於Chunk對象的updateCount,保存現在這個Chunk對象,並且請求下一個Chunk,通過反復執行NoteStroe.getSyncChunk(…,afterUSN=cunkHighUSN,..)。
2 按順序數據保存的多個Chunk對象(我們把這多個Chunk對象集合稱為同步塊),來構建當前服務器的狀態
a 為同步塊里服務器端的標簽(tags)建立一個列表(以GUID為唯一標示符),搜索同步塊,按順序把標簽添加到列表,從列表中移除被標記為永久性刪除(expunged)的標簽(通過看guid)
i 如果一個標簽在服務器列表,但是不在客戶端,那么把它添加到客戶端的數據庫。
ii 如果有同名標簽,但是GUID不同,按以下步驟處理
1 如果已存在的標簽有臟標記(在本地被修改過),那么說明用戶在服務器創建了一個標簽,另外一個客戶端在離線的時候也創建了一個同名的標簽。這個時候需要把他們合並, 或者報告沖突,讓用戶決定如何處理
2 不然就把客戶端的tag重命名一下
iii 如果一個標簽在客戶端,但是不在服務端
1 如果標簽沒有臟標記,或者如果它之前已經有被上傳到服務器過,就把它從客戶端刪除
2 不然就說明這個是客戶端新建的,需要上傳
iv 如果一個標簽在客戶端和服務器兩邊都存在
1 如果他們有相同的USN並且沒有臟標記,那么他們是已經同步了的
2 如果他們有相同的USN,但是客戶端的有臟標記,那么他一會會被上傳到服務器的
3 如果服務器端的標簽有比較高的USN並且客戶端沒有臟標記,那就把客戶端的標簽更新成服務端的樣子(注意要處理同名沖突)
4 如果服務端有更高的USN,並且客戶端有臟標記。說明它被兩端都修改過了,可以嘗試合並或者報告沖突讓用戶決定吧
b 對搜索記錄(SavedSearches)實現相同的算法
c 對筆記本(Notebook)實現相同的算法,如果在客戶端刪除一個筆記本,那么要把它所有的筆記(Notes)和資源(Resources)也都刪除掉
d 對鏈接筆記本(LinkedNotesbooks)實現相同算法
e 對筆記(Note)實現相同算法,注意上面提到過的我們只獲取到了沒有Content的元數據,所以還需要使用NoteSrore.getNoteContent(...)來獲取筆記的完整數據。另外筆記的Title是允許重名的,所以就不用擔心同名沖突
3 完成了和服務器的數據合並,把服務器變量updateCount保存到lastUpdateCount,還有吧服務器的當前時間(currenttime)保存到lastSyncTime
4 轉去*發送改變*
增量同步
1 用完全同步的第一步獲取同步塊,但是afterUSN要設置成lastUpdateCount
2 處理同步塊中的列表在客戶端添加或者更新數據
a 為同步塊里服務器端的標簽(tags)建立一個列表(以GUID為唯一標示符),搜索同步塊,按順序把標簽添加到列表,從列表中移除被標記為永久性刪除(expunged)的標簽(通過看guid)
i 如果一個標簽在服務器列表,但是不在客戶端,那么把它添加到客戶端的數據庫。
ii 如果有同名標簽,但是GUID不同,按以下步驟處理
1 如果已存在的標簽有臟標記(在本地被修改過),那么說明用戶在服務器創建了一個標簽,在客戶端也離線的時候創建了一個同名的標簽。這個時候需要把他們合並, 或者報告沖突,讓用戶決定如何處理
2 不然就把客戶端的tag重命名一下
iii 如果一個標簽在客戶端和服務器兩邊都存在
1 如果客戶端沒有臟標記,那就把客戶端的標簽更新成服務端的樣子(注意要處理同名沖突)
2 如果客戶端有臟標記。說明它被兩端都修改過了,可以嘗試合並或者報告沖突讓用戶決定吧
b 對資源實現相同的算法
c 對搜索記錄(SavedSearches)實現相同的算法
d 對筆記本(Notebook)實現相同的算法
e 對鏈接筆記本(LinkedNotesbooks)實現相同算法
f 對筆記(Note)實現相同算法,使用NoteSrore.getNoteContent(...)來獲取筆記的完整數據。
3 按順序處理需要從客戶端刪除的數據
a 從同步匯集所有被永久刪除(Expunge)的GUID,從客戶端刪除
b 對筆記本做相同處理,注意刪除筆記本時要刪除所有的筆記和資源
c 對搜索記錄做相同處理
d 對標簽做相同處理
e 對鏈接筆記多相同處理
4 完成了和服務器的數據合並,把服務器變量updateCount保存到lastUpdateCount,還有把服務器的當前時間(currenttime)保存到lastSyncTime
5 轉去*發送改變*
發送改變
1 對每一個本地有臟標記的標簽進行如下處理
a 如果標記是新的(本地的USN沒有被設置過),通過NoteStore.createTag(..)把它添加到服務器。如果服務器報告了一個沖突,客戶端必須在本地處理這個沖突,產生沖突的原因是Evernote並不對同步提供鎖,所以當你獲取同步塊之后,另外有一個客戶端對服務器的內容作了部分修改,不過這個概率是在很小。如果服務端報告GUID重復了,那就在本地換一個GUID(這個概率就更小了)。
b 如果標簽被修改過的(本地的USN被設置過)使用NoteStore.updateTag(....)把服務器的內容更新,要處理同名沖突。
c 不論是上面哪種情況,都要做一下的USN的驗證。
i 如果USN = lastUpdateCount +1說明客戶端同步成功,把lastUpdateCount修改成新的USN
ii 如果USN > lastUpdateCount +1說明同步不成功,需要重新增量同步
2 對有臟標記的搜索記錄執行同樣的算法
3 對有臟標記的筆記本執行同樣的算法
4 對有臟標記的筆記執行同樣的算法,注意客戶端是使用NoteStore.createNote()必須傳送有完整數據的筆記(這里注意使用createNote添加到服務器端添加的Note的GUID和本地的會不一樣,就好添加成果后進行修補),在使用NoteStore.updateNote(..)的時候只需要傳送有修改的字段就可以了。
2 Dropbox
Dropbox使用類diff算法(據說早期Dropbox使用的是rsync算法)將A1文件和A2文件之間的不同之處生成一個增量文件。如果Dropbox Server已經有個A1文件,那么只會上傳增量文件。增量文件會被應用於A1文件,使其變為A2文件。