预备结构
这里同步某一个特定笔记本的所有笔记的实现,而且笔记都是不带资源的。另外同步笔记是不需要处理重名问题的
在同步之前首先必然需要构造两个类,分别代表服务器端的数据,和本地端的数据,以及他们的一些操作。
下面给出这两个类的定义,详细代码太长。明后天完成整个程序后,会把所有的代码放到GitHub中进行开源。
其中在服务端的构造函数中完成了登入获取NoteStroe等功能,本地端的构造函数完成了从本地文件读取数据的功能
1: class EvernoteServer
2: {
3: public EvernoteServer()
4:
5: //创建AdageNotebook
6: private Notebook CreateAdageNotebook()
7:
8: //获取AdageNotebook下的所有的Note的元数据
9: //执行这个函数会在更新服务器中Note的Updated
10: public List<Note> GetActiveNotes()
11:
12: //获取在被删除的(在回收站中)Notes的元数据
13: //但是这个方法没办法获取永久性删除的数据。
14: public List<Note> GetInactiveNotes()
15:
16: //向服务端添加Note
17: //会对参数note的Guid 时间戳 USN等信息做修改
18: public void AddNote(Note note)
19:
20: //向服务端更新Note
21: //会对参数note的Guid 时间戳 USN等信息做修改
22: public void UpdateNote(Note note)
23:
24: //从服务端删除Note
25: public void DeleteNote(Note note)
26:
27: //从服务器获取带Content信息的Note
28: //执行这个函数会在更新服务器中note的Updated
29: public Note GetNote(Note note)
30:
31: //从服务器获取USN
32: public Int32 USN
33:
34: }
1: class LocalStroage
2: {
3: public List<Note> AllNotes
4:
5: //获取本地存储的实例,从Config.LocalDataFileName读取获得。
6: public LocalStroage()
7:
12:
13: //根据模板创建一个Note,可以不添加到_list
14: public Note CreateNote(String adage, Boolean isAdd)
15:
16: //在本地存储添加Note
17: public void AddNote(Note note)
18:
19: //将于note.guid相同的节点,内容用newAdage替换
20: public void UpdateNote(Note note, String newAdage)
21:
22: //把list中与note.guid相同的节点用note的内容更新
23: //这里有可能发生,同步时服务器端把一个标记为deleted的节点更新成active的节点
24: public void UpdateNote(Note note)
25:
26: //从本地存储删除Note
27: public void DeleteNote(Note note)
28:
29: //永久性删除Note
30: public void ExpungeNote(Note note)
31:
32: //清除有删除标记的节点
33: public void Clean()
34:
35: //根据参数note的内容克隆一个Note
36: //克隆出来的Guid是不同的
37: //参数isAdd,时候将克隆出来的note加入到list
38: public Note CloneNote(Note note, Boolean isAdd)
39:
40: //保存到文件中
41: public void Save()
42:
43: //从文件中读取
44: private List<Note> Read()
45: }
细节
从网络端获取的Note和本地的Note是不同对象的引用,所以做所有的本地操作的内部都要进行GUID的比对。
执行向服务器添加Note时,参数中的note的GUID和正在添加到服务器的GUID是不同的。可以通过NoteStore.createNote(...)的返回值进行修正。这个返回值是刚刚添加进去的Note的元数据
与添加和更新时传入的Note参数中的属性中的USN,Created,Updated等参数是不会被更新的,也是要根据返回值进行修正,这点对USN特别重要。
GetActiveNotes 和 GetInactiveNotes在有非常多笔记的情况下要时候多次读取合并,类似于之前理论中的同步块问题
进行读取操作(GetActiveNotes GetInactiveNotes GetNote)时会刷新服务器端的Updated时间戳,这个直接导致不能使用Updated判断服务器端是否被修改
本地要设计一个彻底删除的方法,不然本地的数据文件会越来越大
数据分析
通过服务器端的特性和本地操作时的一些设置。我设计出一下的数据
//本地新建的数据 是本地没有被删除的Notes和服务端所有(包括回收站中)Notes的差集
//本地删除的数据 Active == false 这个在本地执行删除时要设置
//本地修改的数据 Updated > Config.LastSyncTime 本地执行时要修改Note.Updated 因为时间我只用来判断本地数据是否被修改,所以我的LastSyncTime其实是本地时间
//服务器新建的数据 服务器没有被删除的Notes和本地所有的(包括被标记为删除的)Notes的差集
//服务器删除的数据 通过GetInactiveNotes 获取到的数据
//服务器修改的数据 USN> Config.LastUSN
同步策略
有两个个会冲突的地方
冲突1:X端删除了note1,Y端修改了note1,处理方式两端都保留被Y端删除过的note1
冲突2:X端修改了note1,Y端也修改了note1,报告给用户,让用户决定保存那个端的,或者保存两端。保存两端即做一个克隆,个头克隆出来note一个新的GUID。关于这个冲突处理,我设计了一个事件机制,里面放了一个ConfilctResolution的枚举,通过事件传递到外部,让外部处理。或者是采用默认方案(保存两侧)
同步代码
主要分为查询和执行两个部分。
执行部分的顺序是 新建 更新除了冲突2以外的东西 删除 处理冲突2
这个顺序先更新后删除很自然的处理好了冲突1。
查询部分大量的使用了LINQ语句,为了调试方便,没有进行太多优化(我也不太会优化这个东西)
具体代码如下
1: //将本地数据文件与网络同步
2: public void Sync()
3: {
4: //本地新建的数据 serverNotesList.Exists(s => s.Guid == l.Guid) == false
5: //本地删除的数据 Active == false
6: //本地修改的数据 Updated > Config.LastSyncTime
7:
8: //服务器新建的数据 loaclNotesList.Exists(l=>l.Guid==s.Guid) == false
9: //服务器删除的数据 InactiveNotesList
10: //服务器修改的数据 USN > Config.LastUSN
11:
12: _server = new EvernoteServer();
13:
14: #region 查询
15:
16: //获取本地的所有Note
17: List<Note> localAllNotes = new List<Note>();
18: localAllNotes.AddRange(_local.AllNotes);
19: //获取本地活动的Note
20: List<Note> localActiveNotes = localAllNotes.FindAll(l => l.Active == true);
21: //获取本地删除掉的Notes
22: List<Note> localInactiveList = localAllNotes.FindAll(n => n.Active == false);
23:
24: //获取服务器端中活动的Note
25: List<Note> serverActiveNotes = _server.GetActiveNotes();
26: //获取服务器端回收站中的Note
27: List<Note> serverInactiveNotes = _server.GetInactiveNotes();
28: //获取服务器端所有的Note
29: List<Note> serverAllNotes = new List<Note>();
30: serverAllNotes.AddRange(serverActiveNotes);
31: serverAllNotes.AddRange(serverInactiveNotes);
32:
33: //需要添加到本地的Notes////////////////////
34: var localNeedAddNotes = serverActiveNotes.FindAll(
35: s => !localAllNotes.Exists(l => l.Guid == s.Guid));
36:
37: //需要添加到服务器的Nots///////////////////
38: var serverNeedAddNotes = localActiveNotes.FindAll(
39: l => !serverAllNotes.Exists(s => s.Guid == l.Guid));
40:
41:
42: //本地修改过的Notes
43: var localUpdatedNotes =
44: localActiveNotes.FindAll(l =>
45: serverAllNotes.Exists(s => s.Guid == l.Guid) &&
46: l.Updated > Config.LastSyncTime);
47: //服务器端修改过的Notes
48: var serverUpdatedNotes =
49: serverActiveNotes.FindAll(s =>
50: localAllNotes.Exists(l => l.Guid == s.Guid) &&
51: s.UpdateSequenceNum > Config.LastUSN);
52:
53: //出现冲突的Notes///////////////////////
54: var conflictList = localUpdatedNotes.FindAll(
55: l => serverUpdatedNotes.Exists(s => s.Guid == l.Guid));
56:
57: //本地需要更新的Notes/////////////////////
58: var localNeedUpdateNotes = serverUpdatedNotes.FindAll(
59: s => !conflictList.Exists(c => c.Guid == s.Guid));
60:
61:
62: //服务器需要更新的Notes////////////////////
63: var serverNeedUpdateNotes = localUpdatedNotes.FindAll(
64: l => !conflictList.Exists(c => c.Guid == l.Guid));
65:
66:
67:
68: //需要删除的本地Notes/////////////////////////
69: var localNeedDeleteNotes = serverInactiveNotes.FindAll(
70: i => localAllNotes.Exists(l => l.Guid == i.Guid) &&
71: !localUpdatedNotes.Exists(l => l.Guid == i.Guid));
72:
73:
74: //需要删除的服务器端的Notes////////////////////
75: var serverNeedDeleteNotes = localInactiveList.FindAll(
76: l => serverActiveNotes.Exists(s => s.Guid == l.Guid) &&
77: !serverUpdatedNotes.Exists(s => s.Guid == l.Guid));
78: #endregion
79:
80: #region 操作
81: //向本地添加Notes
82: foreach(Note metadata in localNeedAddNotes)
83: {
84: Note note = _server.GetNote(metadata);
85: _local.AddNote(note);
86: }
87: //向服务器添加Notes
88: foreach(Note note in serverNeedAddNotes)
89: {
90: _server.AddNote(note);
91: }
92:
93: //更新本地
94: foreach(Note metadata in localNeedUpdateNotes)
95: {
96: Note note = _server.GetNote(metadata);
97: _local.UpdateNote(note);
98: }
99: //更新服务器
100: foreach(Note note in serverNeedUpdateNotes)
101: {
102: _server.UpdateNote(note);
103: }
104:
105: //本地删除
106: foreach(Note note in localNeedDeleteNotes)
107: {
108: _local.ExpungeNote(note);
109: }
110: //从服务器删除
111: foreach(Note note in serverNeedDeleteNotes)
112: {
113: _server.DeleteNote(note);
114: _local.ExpungeNote(note);
115: }
116: ////////////冲突处理//////////
117: //报告冲突,获得处理方法
118: //这里只实现在在两端都保留的方法
119: foreach(Note localNote in conflictList)
120: {
121: //获取服务器冲突的Note
122: Note serverNote = _server.GetNote(localNote);
123:
124: //尝试交给外部,获取冲突解决方案
125: if(ConfilctHappen != null)
126: {
127: _confilctEvenArgs.LocalAdage = localNote.Title;
128: _confilctEvenArgs.ServerAdage = serverNote.Title;
129: ConfilctHappen(this, _confilctEvenArgs);
130: }
131:
132: switch(_confilctEvenArgs.Result)
133: {
134: case ConfilctResolution.SaveBoth:
135: //将localNote进行一个克隆,并且把克隆的Note加入到local
136: Note cloneNote = _local.CloneNote(localNote, true);
137: //把localNote更新成serverNote
138: _local.UpdateNote(serverNote);
139: //把cloneNote提交到服务器
140: _server.AddNote(cloneNote);
141: break;
142: case ConfilctResolution.SaveServer:
143: _local.ExpungeNote(localNote);
144: _local.AddNote(serverNote);
145: break;
146: case ConfilctResolution.SaveLocal:
147: _server.DeleteNote(localNote);
148: _server.AddNote(localNote);
149: break;
150: default:
151: break;
152: }
153:
154: //消除这一次验证的选择
155: _confilctEvenArgs.Result = ConfilctResolution.SaveBoth;
156: }
157:
158: //记录LastSyncTime
159: Config.LastSyncTime = Config.NowTime;
160: //记录LastUSN
161: Config.LastUSN = _server.USN;
162: //保持local,因为在server.addNote等方法中可能会改变Note的部分属性
163: _local.Save();
164: //清理local中没有用到的标记为删除的节点
165: _local.Clean();
166: #endregion
167:
168: //刷新内存中的列表
169: RefreshAdageCollection();
170: this.GenRandomAdage();
171: }