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文件。