OSDN Git Service

NeverNote 0.88.
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / threads / SyncRunner.java
1 /*\r
2  * This file is part of NeverNote \r
3  * Copyright 2009 Randy Baumgarte\r
4  * \r
5  * This file may be licensed under the terms of of the\r
6  * GNU General Public License Version 2 (the ``GPL'').\r
7  *\r
8  * Software distributed under the License is distributed\r
9  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either\r
10  * express or implied. See the GPL for the specific language\r
11  * governing rights and limitations.\r
12  *\r
13  * You should have received a copy of the GPL along with this\r
14  * program. If not, go to http://www.gnu.org/licenses/gpl.html\r
15  * or write to the Free Software Foundation, Inc.,\r
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\r
17  *\r
18 */\r
19 package cx.fbn.nevernote.threads;\r
20 \r
21 import java.net.UnknownHostException;\r
22 import java.util.ArrayList;\r
23 import java.util.Calendar;\r
24 import java.util.Date;\r
25 import java.util.GregorianCalendar;\r
26 import java.util.List;\r
27 import java.util.Vector;\r
28 import java.util.concurrent.LinkedBlockingQueue;\r
29 \r
30 import org.apache.thrift.TException;\r
31 import org.apache.thrift.protocol.TBinaryProtocol;\r
32 import org.apache.thrift.transport.THttpClient;\r
33 import org.apache.thrift.transport.TTransportException;\r
34 \r
35 import com.evernote.edam.error.EDAMNotFoundException;\r
36 import com.evernote.edam.error.EDAMSystemException;\r
37 import com.evernote.edam.error.EDAMUserException;\r
38 import com.evernote.edam.notestore.NoteStore;\r
39 import com.evernote.edam.notestore.SyncChunk;\r
40 import com.evernote.edam.notestore.SyncState;\r
41 import com.evernote.edam.type.Data;\r
42 import com.evernote.edam.type.Note;\r
43 import com.evernote.edam.type.Notebook;\r
44 import com.evernote.edam.type.Resource;\r
45 import com.evernote.edam.type.SavedSearch;\r
46 import com.evernote.edam.type.Tag;\r
47 import com.evernote.edam.type.User;\r
48 import com.evernote.edam.userstore.AuthenticationResult;\r
49 import com.evernote.edam.userstore.UserStore;\r
50 import com.trolltech.qt.core.QObject;\r
51 import com.trolltech.qt.gui.QMessageBox;\r
52 \r
53 import cx.fbn.nevernote.Global;\r
54 import cx.fbn.nevernote.signals.NoteIndexSignal;\r
55 import cx.fbn.nevernote.signals.NoteResourceSignal;\r
56 import cx.fbn.nevernote.signals.NoteSignal;\r
57 import cx.fbn.nevernote.signals.NotebookSignal;\r
58 import cx.fbn.nevernote.signals.SavedSearchSignal;\r
59 import cx.fbn.nevernote.signals.StatusSignal;\r
60 import cx.fbn.nevernote.signals.SyncSignal;\r
61 import cx.fbn.nevernote.signals.TagSignal;\r
62 import cx.fbn.nevernote.sql.DatabaseConnection;\r
63 import cx.fbn.nevernote.sql.runners.DeletedItemRecord;\r
64 import cx.fbn.nevernote.utilities.ApplicationLogger;\r
65 \r
66 public class SyncRunner extends QObject implements Runnable {\r
67         \r
68         private final ApplicationLogger logger;\r
69                 private final DatabaseConnection                conn;\r
70                 private boolean                                 idle;\r
71                 private boolean                                 error;\r
72                 public volatile boolean                 isConnected;\r
73                 public volatile boolean                 keepRunning;\r
74                 public volatile String                  authToken;\r
75                 private long                                    evernoteUpdateCount;\r
76                 \r
77                 public volatile NoteStore.Client                noteStore;\r
78                 private UserStore.Client                                userStore;\r
79                 \r
80                 public volatile StatusSignal                    status;\r
81                 public volatile TagSignal                               tagSignal;\r
82                 public volatile NotebookSignal                  notebookSignal;\r
83                 public volatile NoteIndexSignal                 noteIndexSignal;\r
84                 public volatile NoteSignal                              noteSignal;\r
85                 public volatile SavedSearchSignal               searchSignal;\r
86                 public volatile NoteResourceSignal              resourceSignal;\r
87                 public volatile SyncSignal                              syncSignal;\r
88                 public volatile boolean                                 authRefreshNeeded;\r
89                 public volatile boolean                                 syncNeeded;\r
90                 public volatile boolean                                 disableUploads;\r
91                 public volatile boolean                                 syncDeletedContent;\r
92                 private volatile Vector<String>                 dirtyNoteGuids;\r
93                 \r
94             public volatile String username = ""; \r
95             public volatile String password = ""; \r
96                 public volatile String userStoreUrl;\r
97             private final static String consumerKey = "baumgarte"; \r
98             private final static String consumerSecret = "eb8b5740e17cb55f";\r
99             public String noteStoreUrlBase;\r
100             private THttpClient userStoreTrans;\r
101             private TBinaryProtocol userStoreProt;\r
102             private AuthenticationResult authResult;\r
103             private User user; \r
104             private long authTimeRemaining;\r
105             public long authRefreshTime;\r
106             public long failedRefreshes = 0;\r
107             public  THttpClient noteStoreTrans;\r
108             public TBinaryProtocol noteStoreProt;\r
109             public String noteStoreUrl;\r
110             public long sequenceDate;\r
111             public int updateSequenceNumber;\r
112             private boolean refreshNeeded;\r
113             private volatile LinkedBlockingQueue<String> workQueue;\r
114 //              private static int MAX_EMPTY_QUEUE_COUNT = 1;\r
115                 private static int MAX_QUEUED_WAITING = 1000;\r
116         \r
117                 \r
118                 \r
119         public SyncRunner(String logname) {\r
120                 logger = new ApplicationLogger(logname);\r
121                 \r
122                 noteSignal = new NoteSignal();\r
123                 status = new StatusSignal();\r
124                 tagSignal = new TagSignal();\r
125                 notebookSignal = new NotebookSignal();\r
126                 noteIndexSignal = new NoteIndexSignal();\r
127                 noteSignal = new NoteSignal();\r
128                 searchSignal = new SavedSearchSignal();\r
129                 syncSignal = new SyncSignal();\r
130                 resourceSignal = new NoteResourceSignal();\r
131                 \r
132 //              this.setAutoDelete(false);\r
133                 conn = new DatabaseConnection(logger, Global.syncThreadId);\r
134                 isConnected = false;\r
135                 syncNeeded = false;\r
136                 authRefreshNeeded = false;\r
137                 keepRunning = true;\r
138                 idle = true;\r
139                 noteStore = null;\r
140                 userStore = null;\r
141                 authToken = null;\r
142                 disableUploads = false;\r
143 //              setAutoDelete(false);\r
144                 workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);\r
145         }\r
146         @Override\r
147         public void run() {\r
148                 try {\r
149                         logger.log(logger.EXTREME, "Starting thread");\r
150                         while(keepRunning) {\r
151                                 String work = workQueue.take();\r
152                                 logger.log(logger.EXTREME, "Work found: " +work);\r
153                                 if (work.equalsIgnoreCase("stop"))\r
154                                         return;\r
155                                 idle=false;\r
156                                 error=false;\r
157                                 if (authRefreshNeeded == true) {\r
158                                         logger.log(logger.EXTREME, "Refreshing connection");\r
159                                         refreshConnection();\r
160                                 }\r
161                                 if (syncNeeded) {\r
162                                         logger.log(logger.EXTREME, "SyncNeeded is true");\r
163                                         refreshNeeded=false;\r
164                                         sequenceDate = conn.getSyncTable().getLastSequenceDate();\r
165                                         updateSequenceNumber = conn.getSyncTable().getUpdateSequenceNumber();\r
166                                         try {\r
167                                                 logger.log(logger.EXTREME, "Beginning sync");\r
168                                                 evernoteSync();\r
169                                                 logger.log(logger.EXTREME, "Sync finished");\r
170                                         } catch (UnknownHostException e) {\r
171                                                 status.message.emit(e.getMessage());\r
172                                         }\r
173                                 }\r
174                                 dirtyNoteGuids = null;\r
175                                 idle=true;\r
176                                 logger.log(logger.EXTREME, "Signaling refresh finished.  refreshNeeded=" +refreshNeeded);\r
177                                 syncSignal.finished.emit(refreshNeeded);\r
178                         }\r
179                 }       \r
180                 catch (InterruptedException e1) {\r
181                         e1.printStackTrace();\r
182                 }\r
183         }\r
184 \r
185         \r
186         public DatabaseConnection getConnection() {\r
187                 return conn;\r
188         }\r
189 \r
190         public boolean isIdle() {\r
191                 return idle;\r
192         }\r
193 \r
194 \r
195         public void setConnected(boolean c) {\r
196                 isConnected = c;\r
197         }\r
198         public void setKeepRunning(boolean r) {\r
199                 logger.log(logger.EXTREME, "Setting keepRunning=" +r);\r
200                 keepRunning = r;\r
201         }\r
202         public void setNoteStore(NoteStore.Client c) {\r
203                 logger.log(logger.EXTREME, "Setting NoteStore in sync thread");\r
204                 noteStore = c;\r
205         }\r
206         public void setUserStore(UserStore.Client c) {\r
207                 logger.log(logger.EXTREME, "Setting UserStore in sync thread");\r
208                 userStore = c;\r
209         }\r
210 \r
211         public void setEvernoteUpdateCount(long s) {\r
212                 logger.log(logger.EXTREME, "Setting Update Count in sync thread");\r
213                 evernoteUpdateCount = s;\r
214         }\r
215         \r
216         //***************************************************************\r
217     //***************************************************************\r
218     //** These functions deal with Evernote communications\r
219     //***************************************************************\r
220     //***************************************************************\r
221         // Synchronize changes with Evernote\r
222         @SuppressWarnings("unused")\r
223         private void evernoteSync() throws java.net.UnknownHostException {\r
224                 logger.log(logger.HIGH, "Entering SyncRunner.evernoteSync");\r
225 \r
226                 if (isConnected && keepRunning) {\r
227                         error = false;\r
228                         logger.log(logger.EXTREME, "Synchronizing with Evernote");\r
229                         status.message.emit("Synchronizing with Evernote");\r
230                         \r
231                         // Get user information\r
232                         try {\r
233                                 logger.log(logger.EXTREME, "getting user from userstore");\r
234                                 User user = userStore.getUser(authToken);\r
235                                 logger.log(logger.EXTREME, "Saving user information");\r
236                                 syncSignal.saveUserInformation.emit(user);\r
237                         } catch (EDAMUserException e1) {\r
238                                 e1.printStackTrace();\r
239                                 status.message.emit("User exception getting user account information.  Aborting sync and disconnecting");\r
240                                 syncSignal.errorDisconnect.emit();\r
241                                 enDisconnect();\r
242                                 return;\r
243                         } catch (EDAMSystemException e1) {\r
244                                 e1.printStackTrace();\r
245                                 status.message.emit("System error user account information.  Aborting sync and disconnecting!");\r
246                                 syncSignal.errorDisconnect.emit();\r
247                                 enDisconnect();\r
248                                 return;\r
249                         } catch (TException e1) {\r
250                                 e1.printStackTrace();\r
251                                 syncSignal.errorDisconnect.emit();\r
252                                 status.message.emit("Transaction error getting user account information.  Aborting sync and disconnecting!");\r
253                                 enDisconnect();\r
254                                 return;\r
255                         }\r
256                         \r
257                         // Get sync state\r
258                         SyncState syncState = null;\r
259                         try {   \r
260                                 logger.log(logger.EXTREME, "Getting sync state");\r
261                                 syncState = noteStore.getSyncState(authToken);  \r
262                                 syncSignal.saveUploadAmount.emit(syncState.getUploaded());\r
263                                 syncSignal.saveEvernoteUpdateCount.emit(syncState.getUpdateCount());\r
264                                 evernoteUpdateCount = syncState.getUpdateCount();\r
265                         } catch (EDAMUserException e) {\r
266                                 e.printStackTrace();\r
267                                 status.message.emit("Error getting sync state! Aborting sync and disconnecting!");\r
268                                 syncSignal.errorDisconnect.emit();\r
269                                 enDisconnect();\r
270                                 return;\r
271                         } catch (EDAMSystemException e) {\r
272                                 e.printStackTrace();\r
273                                 status.message.emit("Error getting sync state! Aborting sync and disconnecting!");\r
274                                 syncSignal.errorDisconnect.emit();\r
275                                 enDisconnect();\r
276                                 return;\r
277                         } catch (TException e) {\r
278                                 e.printStackTrace();\r
279                                 status.message.emit("Error getting sync state! Aborting sync and disconnecting!");\r
280                                 syncSignal.errorDisconnect.emit();\r
281                                 enDisconnect();\r
282                                 return;\r
283                         }\r
284                         \r
285                         if (syncState == null) {\r
286                                 logger.log(logger.EXTREME, "Sync State is null");\r
287                                 status.message.emit("Syncronization Error!");\r
288                                 return;\r
289                         }\r
290 \r
291                         // Determine what to do. \r
292                         // If we need to do a full sync.\r
293                         logger.log(logger.LOW, "Full Sequence Before: " +syncState.getFullSyncBefore());\r
294                         logger.log(logger.LOW, "Last Sequence Date: " +sequenceDate);\r
295                         if (syncState.getFullSyncBefore() > sequenceDate) {\r
296                                 logger.log(logger.EXTREME, "Full sequence date has expired");\r
297                                 sequenceDate = 0;\r
298                                 conn.getSyncTable().setLastSequenceDate(0);\r
299                                 updateSequenceNumber = 0;\r
300                                 conn.getSyncTable().setUpdateSequenceNumber(0);\r
301                         }\r
302 \r
303                         // If there are remote changes\r
304                         logger.log(logger.LOW, "Update Count: " +syncState.getUpdateCount());\r
305                         logger.log(logger.LOW, "Last Update Count: " +updateSequenceNumber);\r
306                         \r
307                         if (syncState.getUpdateCount() > updateSequenceNumber) {\r
308                                 logger.log(logger.EXTREME, "Refresh needed is true");\r
309                                 refreshNeeded = true;\r
310                                 logger.log(logger.EXTREME, "Downloading changes");\r
311                                 syncRemoteToLocal();\r
312                         }\r
313                         \r
314                         if (!disableUploads) {\r
315                                 logger.log(logger.EXTREME, "Uploading changes");\r
316                                 // Synchronize remote changes\r
317                                 if (!error)\r
318                                         syncExpunged();\r
319                                 if (!error)\r
320                                         syncLocalTags();\r
321                                 if (!error)\r
322                                         syncLocalNotebooks();\r
323                                 if (!error) \r
324                                         syncDeletedNotes();\r
325                                 if (!error)\r
326                                         syncLocalNotes();\r
327                                 if (!error)\r
328                                         syncLocalSavedSearches();\r
329                         }\r
330                         if (refreshNeeded)\r
331                                 syncSignal.refreshLists.emit();\r
332                         if (!error) {\r
333                                 logger.log(logger.EXTREME, "Sync completed.  Errors=" +error);\r
334                                 if (!disableUploads)\r
335                                         status.message.emit("Synchronizing complete");\r
336                                 else\r
337                                         status.message.emit("Download syncronization complete.  Uploads have been disabled.");\r
338                                 \r
339                                 logger.log(logger.EXTREME, "Saving sync time");\r
340                                 if (syncState.getCurrentTime() > sequenceDate)\r
341                                         sequenceDate = syncState.getCurrentTime();\r
342                                 if (syncState.getUpdateCount() > updateSequenceNumber)\r
343                                         updateSequenceNumber = syncState.getUpdateCount();\r
344                                 conn.getSyncTable().setLastSequenceDate(sequenceDate);\r
345                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
346                         }\r
347                 }\r
348                 logger.log(logger.HIGH, "Leaving SyncRunner.evernoteSync");\r
349         }\r
350         // Sync deleted items with Evernote\r
351         private void syncExpunged() {\r
352                 logger.log(logger.HIGH, "Entering SyncRunner.syncExpunged");\r
353                 \r
354                 List<DeletedItemRecord> expunged = conn.getDeletedTable().getAllDeleted();\r
355                 boolean error = false;\r
356                 for (int i=0; i<expunged.size(); i++) {\r
357                         \r
358                         if (authRefreshNeeded)\r
359                                 refreshConnection();\r
360 \r
361                         try {\r
362                                 if (expunged.get(i).type.equalsIgnoreCase("TAG")) {\r
363                                         logger.log(logger.EXTREME, "Tag expunged");\r
364                                         updateSequenceNumber = noteStore.expungeTag(authToken, expunged.get(i).guid);\r
365                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
366                                         \r
367                                 }\r
368                                 if      (expunged.get(i).type.equalsIgnoreCase("NOTEBOOK")) {\r
369                                         logger.log(logger.EXTREME, "Notebook expunged");\r
370                                         updateSequenceNumber = noteStore.expungeNotebook(authToken, expunged.get(i).guid);\r
371                                         conn.getSyncTable().setLastSequenceDate(sequenceDate);\r
372                                 }\r
373                                 if (expunged.get(i).type.equalsIgnoreCase("NOTE")) {\r
374                                         logger.log(logger.EXTREME, "Note expunged");\r
375                                         updateSequenceNumber = noteStore.deleteNote(authToken, expunged.get(i).guid);\r
376                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
377                                 }\r
378                                 if (expunged.get(i).type.equalsIgnoreCase("SAVEDSEARCH")) {\r
379                                         logger.log(logger.EXTREME, "saved search expunged");\r
380                                         updateSequenceNumber = noteStore.expungeSearch(authToken, expunged.get(i).guid);\r
381                                         conn.getSyncTable().setLastSequenceDate(sequenceDate);\r
382                                 }\r
383                         } catch (EDAMUserException e) {\r
384                                 logger.log(logger.LOW, "EDAM User Excepton in syncExpunged: " +expunged.get(i).guid);\r
385                                 logger.log(logger.LOW, e.getStackTrace());\r
386                                 error = true;\r
387                         } catch (EDAMSystemException e) {\r
388                                 logger.log(logger.LOW, "EDAM System Excepton in syncExpunged: "+expunged.get(i).guid);\r
389                                 logger.log(logger.LOW, e.getStackTrace());\r
390                                 error=true;\r
391                         } catch (EDAMNotFoundException e) {\r
392                                 logger.log(logger.LOW, "EDAM Not Found Excepton in syncExpunged: "+expunged.get(i).guid);\r
393 //                              logger.log(logger.LOW, e.getStackTrace());\r
394                                 //error=true;\r
395                         } catch (TException e) {\r
396                                 logger.log(logger.LOW, "EDAM TExcepton in syncExpunged: "+expunged.get(i).guid);\r
397                                 logger.log(logger.LOW, e.getStackTrace());\r
398                                 error=true;\r
399                         }\r
400                 }\r
401                 if (!error)\r
402                         conn.getDeletedTable().expungeAllDeletedRecords();\r
403                 \r
404                 logger.log(logger.HIGH, "Leaving SyncRunner.syncExpunged");\r
405 \r
406         }\r
407         private void syncDeletedNotes() {\r
408                 if (syncDeletedContent)\r
409                         return;\r
410                 logger.log(logger.HIGH, "Entering SyncRunner.syncDeletedNotes");\r
411                 status.message.emit("Synchronizing deleted notes.");\r
412 \r
413                 List<Note> notes = conn.getNoteTable().getDirty();\r
414                 // Sync the local notebooks with Evernote's\r
415                 for (int i=0; i<notes.size() && keepRunning; i++) {\r
416                         \r
417                         if (authRefreshNeeded)\r
418                                 refreshConnection();\r
419                         \r
420                         Note enNote = notes.get(i);\r
421                         try {\r
422                                 if (enNote.getUpdateSequenceNum() > 0 && (enNote.isActive() == false || enNote.getDeleted() > 0)) {\r
423                                         if (syncDeletedContent) {\r
424                                                 logger.log(logger.EXTREME, "Deleted note found & synch content selected");\r
425                                                 Note delNote = conn.getNoteTable().getNote(enNote.getGuid(), true, true, true, true, true);\r
426                                                 delNote = getNoteContent(delNote);\r
427                                                 delNote = noteStore.updateNote(authToken, delNote);\r
428                                                 enNote.setUpdateSequenceNum(delNote.getUpdateSequenceNum());\r
429                                                 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());\r
430                                         } else {\r
431                                                 logger.log(logger.EXTREME, "Deleted note found & sync content not selected");\r
432                                                 int usn = noteStore.deleteNote(authToken, enNote.getGuid());\r
433                                                 enNote.setUpdateSequenceNum(usn);\r
434                                                 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());                                                \r
435                                         }\r
436                                         logger.log(logger.EXTREME, "Resetting deleted dirty flag");\r
437                                         conn.getNoteTable().resetDirtyFlag(enNote.getGuid());\r
438                                         updateSequenceNumber = enNote.getUpdateSequenceNum();\r
439                                         logger.log(logger.EXTREME, "Saving sequence number");\r
440                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
441                                 }                               \r
442                         } catch (EDAMUserException e) {\r
443                                 //logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);\r
444                                 //status.message.emit("Error sending local note: " +e.getParameter());\r
445                                 //logger.log(logger.LOW, e.toString()); \r
446                                 //error = true;\r
447                         } catch (EDAMSystemException e) {\r
448                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);\r
449                                 status.message.emit("Error: " +e);\r
450                                 logger.log(logger.LOW, e.toString());           \r
451                                 error = true;\r
452                         } catch (EDAMNotFoundException e) {\r
453                                 //logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);\r
454                                 //status.message.emit("Error deleting local note: " +e +" - Continuing");\r
455                                 //logger.log(logger.LOW, e.toString());         \r
456                                 //error = true;\r
457                         } catch (TException e) {\r
458                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);\r
459                                 status.message.emit("Error sending local note: " +e);\r
460                                 logger.log(logger.LOW, e.toString());   \r
461                                 error = true;\r
462                         }               \r
463                 }\r
464         }\r
465         // Sync notes with Evernote\r
466         private void syncLocalNotes() {\r
467                 logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");\r
468                 status.message.emit("Sending local notes.");\r
469 \r
470                 List<Note> notes = conn.getNoteTable().getDirty();\r
471                 // Sync the local notebooks with Evernote's\r
472                 for (int i=0; i<notes.size() && keepRunning; i++) {\r
473                         \r
474                         if (authRefreshNeeded)\r
475                                 refreshConnection();\r
476                         \r
477                         Note enNote = notes.get(i);\r
478                         if (enNote.isActive()) {\r
479                                 try {\r
480                                         logger.log(logger.EXTREME, "Active dirty note found - non new");\r
481                                         if (enNote.getUpdateSequenceNum() > 0) {\r
482                                                 enNote = getNoteContent(enNote);\r
483                                                 logger.log(logger.MEDIUM, "Updating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");\r
484                                                 enNote = noteStore.updateNote(authToken, enNote);\r
485                                         } else { \r
486                                                 logger.log(logger.EXTREME, "Active dirty found - new note");\r
487                                                 logger.log(logger.MEDIUM, "Creating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");\r
488                                                 String oldGuid = enNote.getGuid();\r
489                                                 enNote = getNoteContent(enNote);\r
490                                                 enNote = noteStore.createNote(authToken, enNote);\r
491                                                 noteSignal.guidChanged.emit(oldGuid, enNote.getGuid());\r
492                                                 conn.getNoteTable().updateNoteGuid(oldGuid, enNote.getGuid());\r
493                                         }\r
494                                         updateSequenceNumber = enNote.getUpdateSequenceNum();\r
495                                         logger.log(logger.EXTREME, "Saving note");\r
496                                         conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());\r
497                                         List<Resource> rl = enNote.getResources();\r
498                                         logger.log(logger.EXTREME, "Getting note resources");\r
499                                         for (int j=0; j<enNote.getResourcesSize() && keepRunning; j++) {\r
500                                                 Resource newRes = rl.get(j);\r
501                                                 Data d = newRes.getData();\r
502                                                 if (d!=null) {  \r
503                                                         logger.log(logger.EXTREME, "Calculating resource hash");\r
504                                                         String hash = byteArrayToHexString(d.getBodyHash());\r
505                                                         logger.log(logger.EXTREME, "updating resources by hash");\r
506                                                         String oldGuid = conn.getNoteTable().noteResourceTable.getNoteResourceGuidByHashHex(enNote.getGuid(), hash);\r
507                                                         conn.getNoteTable().updateNoteResourceGuidbyHash(enNote.getGuid(), newRes.getGuid(), hash);\r
508                                                         resourceSignal.resourceGuidChanged.emit(enNote.getGuid(), oldGuid, newRes.getGuid());\r
509                                                 }\r
510                                         }\r
511                                         logger.log(logger.EXTREME, "Resetting note dirty flag");\r
512                                         conn.getNoteTable().resetDirtyFlag(enNote.getGuid());\r
513                                         updateSequenceNumber = enNote.getUpdateSequenceNum();\r
514                                         logger.log(logger.EXTREME, "Emitting note sequence number change");\r
515                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
516 \r
517                                 } catch (EDAMUserException e) {\r
518                                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);\r
519                                         status.message.emit("Error sending local note: " +e.getParameter());\r
520                                         logger.log(logger.LOW, e.toString());   \r
521                                         error = true;\r
522                                 } catch (EDAMSystemException e) {\r
523                                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);\r
524                                         status.message.emit("Error: " +e);\r
525                                         logger.log(logger.LOW, e.toString());           \r
526                                         error = true;\r
527                                 } catch (EDAMNotFoundException e) {\r
528                                         logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);\r
529                                         status.message.emit("Error sending local note: " +e);\r
530                                         logger.log(logger.LOW, e.toString());   \r
531                                         error = true;\r
532                                 } catch (TException e) {\r
533                                         logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);\r
534                                         status.message.emit("Error sending local note: " +e);\r
535                                         logger.log(logger.LOW, e.toString());   \r
536                                         error = true;\r
537                                 }\r
538                         }\r
539                 }\r
540                 logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");\r
541 \r
542         }\r
543         // Sync Notebooks with Evernote\r
544         private void syncLocalNotebooks() {\r
545                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalNotebooks");\r
546                 \r
547                 status.message.emit("Sending local notebooks.");\r
548                 List<Notebook> remoteList = new ArrayList<Notebook>();\r
549                 try {\r
550                         logger.log(logger.EXTREME, "Getting remote notebooks to compare with local");\r
551                         remoteList = noteStore.listNotebooks(authToken);\r
552                 } catch (EDAMUserException e1) {\r
553                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks getting remote Notebook List");\r
554                         status.message.emit("Error: " +e1);\r
555                         logger.log(logger.LOW, e1.toString());          \r
556                         error = true;\r
557                 } catch (EDAMSystemException e1) {\r
558                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks getting remote Notebook List");\r
559                         status.message.emit("Error: " +e1);\r
560                         logger.log(logger.LOW, e1.toString());  \r
561                         error = true;\r
562                 } catch (TException e1) {\r
563                         logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalNotebooks getting remote Notebook List");\r
564                         status.message.emit("Error: " +e1);\r
565                         logger.log(logger.LOW, e1.toString());  \r
566                         error = true;\r
567                 }\r
568                 logger.log(logger.EXTREME, "Getting local dirty notebooks");\r
569                 List<Notebook> notebooks = conn.getNotebookTable().getDirty();\r
570                 int sequence;\r
571                 // Sync the local notebooks with Evernote's\r
572                 for (int i=0; i<notebooks.size() && keepRunning; i++) {\r
573                         \r
574                         if (authRefreshNeeded)\r
575                                 refreshConnection();\r
576                         \r
577                         Notebook enNotebook = notebooks.get(i);\r
578                         try {\r
579                                 if (enNotebook.getUpdateSequenceNum() > 0) {\r
580                                         logger.log(logger.EXTREME, "Existing notebook is dirty");\r
581                                         sequence = noteStore.updateNotebook(authToken, enNotebook);\r
582                                 } else {\r
583                                         logger.log(logger.EXTREME, "New dirty notebook found");\r
584                                         String oldGuid = enNotebook.getGuid();\r
585                                         boolean found = false;\r
586                                         \r
587                                         // Look for a notebook with the same name.  If one is found, we don't need \r
588                                         // to create another one\r
589                                         logger.log(logger.EXTREME, "Looking for matching notebook name");\r
590                                         for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {\r
591                                                 if (remoteList.get(k).getName().equalsIgnoreCase(enNotebook.getName())) {\r
592                                                         enNotebook = remoteList.get(k);\r
593                                                         logger.log(logger.EXTREME, "Matching notebook found");\r
594                                                         found = true;\r
595                                                 }\r
596                                         }\r
597                                         if (!found)\r
598                                                 enNotebook = noteStore.createNotebook(authToken, enNotebook);\r
599                                         \r
600                                         logger.log(logger.EXTREME, "Updating notebook in database");\r
601                                         conn.getNotebookTable().updateNotebookGuid(oldGuid, enNotebook.getGuid());\r
602                                         sequence = enNotebook.getUpdateSequenceNum();\r
603                                 }\r
604                                 logger.log(logger.EXTREME, "Updating notebook sequence in database");\r
605                                 conn.getNotebookTable().updateNotebookSequence(enNotebook.getGuid(), sequence);\r
606                                 logger.log(logger.EXTREME, "Resetting dirty flag in notebook");\r
607                                 conn.getNotebookTable().resetDirtyFlag(enNotebook.getGuid());\r
608                                 updateSequenceNumber = sequence;\r
609                                 logger.log(logger.EXTREME, "Emitting sequence number to main thread");\r
610                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
611                         } catch (EDAMUserException e) {\r
612                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks");\r
613                                 logger.log(logger.LOW, e.toString());   \r
614                                 error = true;\r
615                         } catch (EDAMSystemException e) {\r
616                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks");\r
617                                 logger.log(logger.LOW, e.toString());           \r
618                                 error = true;\r
619                         } catch (EDAMNotFoundException e) {\r
620                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotebooks");\r
621                                 logger.log(logger.LOW, e.toString());           \r
622                                 error = true;\r
623                         } catch (TException e) {\r
624                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotebooks");\r
625                                 logger.log(logger.LOW, e.toString());   \r
626                                 error = true;\r
627                         }               \r
628                 }\r
629                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNotebooks");\r
630 \r
631         }\r
632         // Sync Tags with Evernote\r
633         private void syncLocalTags() {\r
634                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalTags");\r
635                 List<Tag> remoteList = new ArrayList<Tag>();\r
636                 status.message.emit("Sending local tags.");\r
637                 \r
638                 try {\r
639                         logger.log(logger.EXTREME, "Getting remote tags to compare names with the local tags");\r
640                         remoteList = noteStore.listTags(authToken);\r
641                 } catch (EDAMUserException e1) {\r
642                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote Tag List");\r
643                         status.message.emit("Error: " +e1);\r
644                         logger.log(logger.LOW, e1.toString());  \r
645                         error = true;\r
646                 } catch (EDAMSystemException e1) {\r
647                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote Tag List");\r
648                         status.message.emit("Error: " +e1);\r
649                         logger.log(logger.LOW, e1.toString());          \r
650                         error = true;\r
651                 } catch (TException e1) {\r
652                         logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote Tag List");\r
653                         status.message.emit("Error: " +e1);\r
654                         logger.log(logger.LOW, e1.toString());  \r
655                         error = true;\r
656                 }               \r
657                 \r
658                 int sequence;\r
659                 \r
660                 Tag enTag = findNextTag();\r
661                 while(enTag!=null) {\r
662                         if (authRefreshNeeded)\r
663                                 refreshConnection();\r
664 \r
665                         try {\r
666                                 if (enTag.getUpdateSequenceNum() > 0) {\r
667                                         logger.log(logger.EXTREME, "Updating tag");\r
668                                         sequence = noteStore.updateTag(authToken, enTag);\r
669                                 } else {\r
670                                         \r
671                                         // Look for a tag with the same name.  If one is found, we don't need \r
672                                         // to create another one\r
673                                         logger.log(logger.EXTREME, "New tag.  Comparing with remote names");\r
674                                         boolean found = false;\r
675                                         String oldGuid = enTag.getGuid();\r
676                                         for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {\r
677                                                 if (remoteList.get(k).getName().equalsIgnoreCase(enTag.getName())) {\r
678                                                         conn.getTagTable().updateTagGuid(enTag.getGuid(), remoteList.get(k).getGuid());\r
679                                                         enTag = remoteList.get(k);\r
680                                                         logger.log(logger.EXTREME, "Matching tag name found");\r
681                                                         found = true;\r
682                                                 }\r
683                                         }\r
684                                         if (!found)\r
685                                                 enTag = noteStore.createTag(authToken, enTag);\r
686                                         else\r
687                                                 enTag.setUpdateSequenceNum(noteStore.updateTag(authToken,enTag));\r
688                                         sequence = enTag.getUpdateSequenceNum();\r
689                                         if (!oldGuid.equals(enTag.getGuid())) {\r
690                                                 logger.log(logger.EXTREME, "Updating tag guid");\r
691                                                 conn.getTagTable().updateTagGuid(oldGuid, enTag.getGuid());\r
692                                         }\r
693                                 }\r
694                                 logger.log(logger.EXTREME, "Updating tag sequence number");\r
695                                 conn.getTagTable().updateTagSequence(enTag.getGuid(), sequence);\r
696                                 logger.log(logger.EXTREME, "Resetting tag dirty flag");\r
697                                 conn.getTagTable().resetDirtyFlag(enTag.getGuid());\r
698                                 logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");\r
699                                 updateSequenceNumber = sequence;\r
700                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
701                         } catch (EDAMUserException e) {\r
702                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags");\r
703                                 logger.log(logger.LOW, e.toString());           \r
704                                 error = true;\r
705                         } catch (EDAMSystemException e) {\r
706                                 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags");\r
707                                 logger.log(logger.LOW, e.toString());   \r
708                                 error = true;\r
709                         } catch (EDAMNotFoundException e) {\r
710                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags");\r
711                                 logger.log(logger.LOW, e.toString());   \r
712                                 error = true;\r
713                         } catch (TException e) {\r
714                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags");\r
715                                 logger.log(logger.LOW, e.toString());           \r
716                                 error = true;\r
717                         }       \r
718                         \r
719                         // Find the next tag\r
720                         logger.log(logger.EXTREME, "Finding next tag");\r
721                         enTag = findNextTag();\r
722                 }\r
723                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalTags");\r
724         }\r
725         // Sync Tags with Evernote\r
726         private void syncLocalSavedSearches() {\r
727                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");\r
728                 List<SavedSearch> remoteList = new ArrayList<SavedSearch>();\r
729                 status.message.emit("Sending saved searches.");\r
730         \r
731                 logger.log(logger.EXTREME, "Getting saved searches to compare with local");\r
732                 try {\r
733                         remoteList = noteStore.listSearches(authToken);\r
734                 } catch (EDAMUserException e1) {\r
735                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote saved search List");\r
736                         status.message.emit("Error: " +e1);\r
737                         logger.log(logger.LOW, e1.toString());  \r
738                         error = true;\r
739                 } catch (EDAMSystemException e1) {\r
740                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote saved search List");\r
741                         status.message.emit("Error: " +e1);\r
742                         logger.log(logger.LOW, e1.toString());          \r
743                         error = true;\r
744                 } catch (TException e1) {\r
745                         logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote saved search List");\r
746                         status.message.emit("Error: " +e1);\r
747                         logger.log(logger.LOW, e1.toString());  \r
748                         error = true;\r
749                 }               \r
750                 \r
751                 List<SavedSearch> searches = conn.getSavedSearchTable().getDirty();\r
752                 int sequence;\r
753                 // Sync the local notebooks with Evernote's\r
754                 logger.log(logger.EXTREME, "Beginning to send saved searches");\r
755                 for (int i=0; i<searches.size() &&  keepRunning; i++) {\r
756                         \r
757                         if (authRefreshNeeded)\r
758                                 refreshConnection();\r
759                         \r
760                         SavedSearch enSearch = searches.get(i);\r
761                         try {\r
762                                 if (enSearch.getUpdateSequenceNum() > 0) \r
763                                         sequence = noteStore.updateSearch(authToken, enSearch);\r
764                                 else {\r
765                                         logger.log(logger.EXTREME, "New saved search found.");\r
766                                         // Look for a tag with the same name.  If one is found, we don't need \r
767                                         // to create another one\r
768                                         boolean found = false;\r
769                                         logger.log(logger.EXTREME, "Matching remote saved search names with local");\r
770                                         for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {\r
771                                                 if (remoteList.get(k).getName().equalsIgnoreCase(enSearch.getName())) {\r
772                                                         enSearch = remoteList.get(k);\r
773                                                         found = true;\r
774                                                         logger.log(logger.EXTREME, "Matching saved search found");\r
775                                                         sequence = enSearch.getUpdateSequenceNum();\r
776                                                 }\r
777                                         }\r
778 \r
779                                         String oldGuid = enSearch.getGuid();\r
780                                         if (!found)\r
781                                                 enSearch = noteStore.createSearch(authToken, enSearch);\r
782                                         sequence = enSearch.getUpdateSequenceNum();\r
783                                         logger.log(logger.EXTREME, "Updating tag guid in local database");\r
784                                         conn.getTagTable().updateTagGuid(oldGuid, enSearch.getGuid());\r
785                                 }\r
786                                 logger.log(logger.EXTREME, "Updating tag sequence in local database");\r
787                                 conn.getSavedSearchTable().updateSavedSearchSequence(enSearch.getGuid(), sequence);\r
788                                 logger.log(logger.EXTREME, "Resetting tag dirty flag");\r
789                                 conn.getSavedSearchTable().resetDirtyFlag(enSearch.getGuid());\r
790                                 logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");\r
791                                 updateSequenceNumber = sequence;\r
792                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
793                         } catch (EDAMUserException e) {\r
794                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags");\r
795                                 logger.log(logger.LOW, e.toString());   \r
796                                 error = true;\r
797                         } catch (EDAMSystemException e) {\r
798                                 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags");\r
799                                 logger.log(logger.LOW, e.toString());   \r
800                                 error = true;\r
801                         } catch (EDAMNotFoundException e) {\r
802                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags");\r
803                                 logger.log(logger.LOW, e.toString());   \r
804                                 error = true;\r
805                         } catch (TException e) {\r
806                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags");\r
807                                 logger.log(logger.LOW, e.toString());   \r
808                                 error = true;\r
809                         }               \r
810                 }\r
811                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");\r
812         }       \r
813 \r
814         // Sync evernote changes with local database\r
815         private void syncRemoteToLocal() {\r
816                 logger.log(logger.HIGH, "Entering SyncRunner.syncRemoteToLocal");\r
817 \r
818                 List<Note> dirtyNotes = conn.getNoteTable().getDirty();\r
819                 dirtyNoteGuids = new Vector<String>();\r
820                 for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {\r
821                         dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());\r
822                 }\r
823                 \r
824                 int chunkSize = 10;\r
825                 SyncChunk chunk = null;\r
826                 boolean fullSync = false;\r
827                 boolean more = true;\r
828                 \r
829                 if (updateSequenceNumber == 0)\r
830                         fullSync = true;\r
831                 \r
832                 status.message.emit("Downloading 0% complete.");\r
833                 \r
834                 while(more &&  keepRunning) {\r
835                         \r
836                         if (authRefreshNeeded)\r
837                                 refreshConnection();\r
838                         \r
839                         chunk = null;\r
840                         int sequence = updateSequenceNumber;\r
841                         try {\r
842                                 logger.log(logger.EXTREME, "Getting chunk from Evernote");\r
843                                 chunk = noteStore.getSyncChunk(authToken, sequence, chunkSize, fullSync);\r
844                         } catch (EDAMUserException e) {\r
845                                 error = true;\r
846                                 e.printStackTrace();\r
847                                 status.message.emit(e.getMessage());\r
848                         } catch (EDAMSystemException e) {\r
849                                 error = true;\r
850                                 e.printStackTrace();\r
851                                 status.message.emit(e.getMessage());\r
852                         } catch (TException e) {\r
853                                 error = true;\r
854                                 e.printStackTrace();\r
855                                 status.message.emit(e.getMessage());\r
856                         } \r
857                         if (error || chunk == null) \r
858                                 return;\r
859                                 \r
860                 \r
861                         \r
862                         syncRemoteTags(chunk.getTags());\r
863                         syncRemoteSavedSearches(chunk.getSearches());\r
864                         syncRemoteNotebooks(chunk.getNotebooks());\r
865                         syncRemoteNotes(chunk.getNotes(), fullSync);\r
866                         syncRemoteResources(chunk.getResources());\r
867                         \r
868                         // Do the local deletes\r
869                         logger.log(logger.EXTREME, "Doing local deletes");\r
870                         List<String> guid = chunk.getExpungedNotes();\r
871                         if (guid != null) \r
872                                 for (int i=0; i<guid.size() && keepRunning; i++) {\r
873                                         logger.log(logger.EXTREME, "Expunging local note from database");\r
874                                         conn.getNoteTable().expungeNote(guid.get(i), true, false);\r
875                                 }\r
876                         guid = chunk.getExpungedNotebooks();\r
877                         if (guid != null)\r
878                                 for (int i=0; i<guid.size() && keepRunning; i++) {\r
879                                         logger.log(logger.EXTREME, "Expunging local notebook from database");\r
880                                         conn.getNotebookTable().expungeNotebook(guid.get(i), false);\r
881                                 }\r
882                         guid = chunk.getExpungedTags();\r
883                         if (guid != null)\r
884                                 for (int i=0; i<guid.size() && keepRunning; i++) {\r
885                                         logger.log(logger.EXTREME, "Expunging tags from local database");\r
886                                         conn.getTagTable().expungeTag(guid.get(i), false);\r
887                                 }\r
888                         guid = chunk.getExpungedSearches();\r
889                         if (guid != null) \r
890                                 for (int i=0; i<guid.size() && keepRunning; i++) {\r
891                                         logger.log(logger.EXTREME, "Expunging saved search from local database");\r
892                                         conn.getSavedSearchTable().expungeSavedSearch(guid.get(i), false);\r
893                                 }\r
894 \r
895                         \r
896                         // Check for more notes\r
897                         if (chunk.getChunkHighUSN() <= updateSequenceNumber) \r
898                                 more = false;\r
899                         if (error)\r
900                                 more = false;\r
901                         logger.log(logger.EXTREME, "More notes? " +more);\r
902 \r
903                         \r
904                         // Save the chunk sequence number\r
905                         if (!error && chunk.getChunkHighUSN() > 0) {\r
906                                 logger.log(logger.EXTREME, "emitting sequence number to main thread");\r
907                                 updateSequenceNumber = chunk.getChunkHighUSN();\r
908                                 conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());\r
909                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);\r
910                         }\r
911                         \r
912                         \r
913                         if (more) {\r
914                                 long pct = chunk.getChunkHighUSN() * 100;\r
915                                 conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());\r
916                                 pct = pct/evernoteUpdateCount;\r
917                                 status.message.emit("Downloading " +new Long(pct).toString()+"% complete.");\r
918                         }\r
919                 }\r
920 \r
921                 logger.log(logger.HIGH, "Leaving SyncRunner.syncRemoteToLocal");\r
922         }\r
923         // Sync remote tags\r
924         private void syncRemoteTags(List<Tag> tags) {\r
925                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");\r
926                 if (tags != null) {\r
927                         for (int i=0; i<tags.size() && keepRunning; i++) {\r
928                                 String oldGuid;\r
929                                 oldGuid = conn.getTagTable().findTagByName(tags.get(i).getName());\r
930                                 if (oldGuid != null && !tags.get(i).getGuid().equalsIgnoreCase(oldGuid))\r
931                                         conn.getTagTable().updateTagGuid(oldGuid, tags.get(i).getGuid());\r
932                                 conn.getTagTable().syncTag(tags.get(i), false);\r
933                         }\r
934                 }\r
935                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");\r
936         }\r
937         // Sync remote tags\r
938         private void syncRemoteSavedSearches(List<SavedSearch> searches) {\r
939                 logger.log(logger.EXTREME, "Entering SyncRunner.syncSavedSearches");\r
940                 if (searches != null) {\r
941                         for (int i=0; i<searches.size() && keepRunning; i++) {\r
942                                 String oldGuid;\r
943                                 oldGuid = conn.getSavedSearchTable().findSavedSearchByName(searches.get(i).getName());\r
944                                 if (oldGuid != null && !searches.get(i).getGuid().equalsIgnoreCase(oldGuid))\r
945                                         conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, searches.get(i).getGuid());\r
946                                 conn.getSavedSearchTable().syncSavedSearch(searches.get(i), false);\r
947                         }\r
948                 }\r
949                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncSavedSearches");\r
950         }\r
951         // Sync remote Notebooks 2\r
952         private void syncRemoteNotebooks(List<Notebook> notebooks) {\r
953                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");\r
954                 if (notebooks != null) {\r
955                         for (int i=0; i<notebooks.size() && keepRunning; i++) {\r
956                                 String oldGuid;\r
957                                 oldGuid = conn.getNotebookTable().findNotebookByName(notebooks.get(i).getName());\r
958                                 if (oldGuid != null && !conn.getNotebookTable().isNotebookLocal(oldGuid) && !notebooks.get(i).getGuid().equalsIgnoreCase(oldGuid))\r
959                                         conn.getNotebookTable().updateNotebookGuid(oldGuid, notebooks.get(i).getGuid());\r
960                                 conn.getNotebookTable().syncNotebook(notebooks.get(i), false); \r
961                         }\r
962                 }                       \r
963                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");\r
964         }\r
965         // Sync remote Resources\r
966         private void syncRemoteResources(List<Resource> resource) {\r
967                 // This is how the logic for this works.\r
968                 // 1.) If the resource is not in the local database, we add it.\r
969                 // 2.) If a copy of the resource is in the local database and the note isn't dirty, we update the local copy\r
970                 // 3.) If a copy of the resource is in the local databbase and it is dirty and the hash doesn't match, we ignore it because there\r
971                 //     is a conflict.  The note conflict should get a copy of the resource at that time.\r
972                 \r
973                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteResources");\r
974                 if (resource != null) {\r
975                         for (int i=0; i<resource.size() && keepRunning; i++) {\r
976                                 boolean saveNeeded = false;\r
977 /* #1 */                Resource r = getEvernoteResource(resource.get(i).getGuid(), true,true,true);\r
978                                 Resource l = conn.getNoteTable().noteResourceTable.getNoteResource(r.getGuid(), false);\r
979                                 if (l == null) {\r
980                                         saveNeeded = true;\r
981                                 } else {\r
982 /* #2 */                        boolean isNoteDirty = conn.getNoteTable().isNoteDirty(r.getNoteGuid());\r
983                                         if (!isNoteDirty)\r
984                                                 saveNeeded = true;\r
985                                         else {\r
986 /* #3 */                                String remoteHash = "";\r
987                                                 if (r != null && r.getData() != null && r.getData().getBodyHash() != null)\r
988                                                         remoteHash = byteArrayToHexString(r.getData().getBodyHash());\r
989                                                 String localHash = "";\r
990                                                 if (l != null && l.getData() != null && l.getData().getBodyHash() != null)\r
991                                                         remoteHash = byteArrayToHexString(l.getData().getBodyHash());\r
992                                 \r
993                                                 if (localHash.equalsIgnoreCase(remoteHash))\r
994                                                         saveNeeded = true;\r
995                                         }\r
996                                 }\r
997                                 \r
998                                 if (saveNeeded) \r
999                                         conn.getNoteTable().noteResourceTable.updateNoteResource(r, false);\r
1000 \r
1001                         }\r
1002                 }\r
1003                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteResources");\r
1004         }\r
1005         // Sync remote notebooks\r
1006         private void syncRemoteNotes(List<Note> note, boolean fullSync) {\r
1007                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotes");\r
1008                 if (note != null) {\r
1009                         for (int i=0; i<note.size() && keepRunning; i++) {\r
1010                                 Note n = getEvernoteNote(note.get(i).getGuid(), true, fullSync, true,true);\r
1011                                 if (n!=null) {\r
1012                                         \r
1013                                         // Basically, this is how the sync logic for a note works.\r
1014                                         // If the remote note has changed and the local has not, we\r
1015                                         // accept the change.\r
1016                                         // If both the local & remote have changed but the sequence\r
1017                                         // numbers are the same, we don't accept the change.  This\r
1018                                         // seems to happen when attachments are indexed by the server.\r
1019                                         // If both the local & remote have changed and the sequence numbers\r
1020                                         // are different we move the local copy to a local notebook (making sure\r
1021                                         // to copy all resources) and we accept the new one.                    \r
1022                                         boolean conflictingNote = true;\r
1023                                         logger.log(logger.EXTREME, "Checking for duplicate note " +n.getGuid());\r
1024                                         if (dirtyNoteGuids.contains(n.getGuid())) { \r
1025                                                 logger.log(logger.EXTREME, "Conflict check beginning");\r
1026                                                 conflictingNote = checkForConflict(n);\r
1027                                                 logger.log(logger.EXTREME, "Conflict check results " +conflictingNote);\r
1028                                                 if (conflictingNote)\r
1029                                                         moveConflictingNote(n.getGuid());\r
1030                                         }\r
1031                                         if (conflictingNote || fullSync) {\r
1032                                                 logger.log(logger.EXTREME, "Saving Note");\r
1033                                                 conn.getNoteTable().syncNote(n, false);\r
1034                                                 noteSignal.noteChanged.emit(n.getGuid(), null);   // Signal to ivalidate note cache\r
1035                                                 logger.log(logger.EXTREME, "Note Saved");\r
1036                                                 if (fullSync && n.getResources() != null) {\r
1037                                                         for (int q=0; q<n.getResources().size() && keepRunning; q++) {\r
1038                                                                 logger.log(logger.EXTREME, "Getting note resources.");\r
1039                                                                 conn.getNoteTable().noteResourceTable.updateNoteResource(n.getResources().get(q), false);\r
1040                                                         }\r
1041                                                 }\r
1042                                         }\r
1043                                 }\r
1044                         }\r
1045                 }\r
1046                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotes");\r
1047         }\r
1048         private Note getEvernoteNote(String guid, boolean withContent, boolean withResourceData, boolean withResourceRecognition, boolean withResourceAlternateData) { \r
1049                 Note n = null;\r
1050                 try {\r
1051                         logger.log(logger.EXTREME, "Retrieving note " +guid);\r
1052                         n = noteStore.getNote(authToken, guid, withContent, withResourceData, withResourceRecognition, withResourceAlternateData);\r
1053                         logger.log(logger.EXTREME, "Note " +guid +" has been retrieved.");\r
1054                 } catch (EDAMUserException e) {\r
1055                         logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");\r
1056                         logger.log(logger.LOW, e.toString());   \r
1057                         error = true;\r
1058                         e.printStackTrace();\r
1059                 } catch (EDAMSystemException e) {\r
1060                         logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");\r
1061                         logger.log(logger.LOW, e.toString());   \r
1062                         error = true;\r
1063                         e.printStackTrace();\r
1064                 } catch (EDAMNotFoundException e) {\r
1065                         logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");\r
1066                         logger.log(logger.LOW, e.toString());   \r
1067                         error = true;\r
1068                         e.printStackTrace();\r
1069                 } catch (TException e) {\r
1070                         logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");\r
1071                         logger.log(logger.LOW, e.toString());   \r
1072                         error = true;\r
1073                         e.printStackTrace();\r
1074                 }\r
1075                 return n;\r
1076         }\r
1077         private Resource getEvernoteResource(String guid, boolean withData, boolean withRecognition, boolean withAttributes) { \r
1078                 Resource n = null;\r
1079                 try {\r
1080                         logger.log(logger.EXTREME, "Retrieving resource " +guid);\r
1081                         n = noteStore.getResource(authToken, guid, withData, withRecognition, withAttributes, withAttributes);\r
1082                         logger.log(logger.EXTREME, "Resource " +guid +" has been retrieved.");\r
1083                 } catch (EDAMUserException e) {\r
1084                         logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");\r
1085                         logger.log(logger.LOW, e.toString());   \r
1086                         error = true;\r
1087                         e.printStackTrace();\r
1088                 } catch (EDAMSystemException e) {\r
1089                         logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");\r
1090                         logger.log(logger.LOW, e.toString());   \r
1091                         error = true;\r
1092                         e.printStackTrace();\r
1093                 } catch (EDAMNotFoundException e) {\r
1094                         logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");\r
1095                         logger.log(logger.LOW, e.toString());   \r
1096                         error = true;\r
1097                         e.printStackTrace();\r
1098                 } catch (TException e) {\r
1099                         logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");\r
1100                         logger.log(logger.LOW, e.toString());   \r
1101                         error = true;\r
1102                         e.printStackTrace();\r
1103                 }\r
1104                 return n;\r
1105         }\r
1106 \r
1107         \r
1108         private boolean checkForConflict(Note n) {\r
1109                 logger.log(logger.EXTREME, "Checking note sequence number  " +n.getGuid());\r
1110                 Note oldNote = conn.getNoteTable().getNote(n.getGuid(), false, false, false, false, false);\r
1111                 logger.log(logger.EXTREME, "Local/Remote sequence numbers: " +oldNote.getUpdateSequenceNum()+"/"+n.getUpdateSequenceNum());\r
1112                 if (oldNote.getUpdateSequenceNum() == n.getUpdateSequenceNum())\r
1113                         return false;\r
1114                 return true;\r
1115         }\r
1116         \r
1117         private void moveConflictingNote(String guid) {\r
1118                 logger.log(logger.EXTREME, "Conflicting change found for note " +guid);\r
1119                 List<Notebook> books = conn.getNotebookTable().getAllLocal();\r
1120                 String notebookGuid = null;\r
1121                 for (int i=0; i<books.size() && keepRunning; i++) {\r
1122                         if (books.get(i).getName().equalsIgnoreCase("Conflicting Changes (local)") ||\r
1123                                         books.get(i).getName().equalsIgnoreCase("Conflicting Changes")) {\r
1124                                 notebookGuid = books.get(i).getGuid();\r
1125                                 i=books.size();\r
1126                         }\r
1127                 }\r
1128                 \r
1129                 if (notebookGuid == null) {\r
1130                         logger.log(logger.EXTREME, "Building conflicting change notebook " +guid);\r
1131                         Calendar currentTime = new GregorianCalendar();\r
1132                         Long l = new Long(currentTime.getTimeInMillis());\r
1133                         long prevTime = l;\r
1134                         while (prevTime==l) {\r
1135                                 currentTime = new GregorianCalendar();\r
1136                                 l=currentTime.getTimeInMillis();\r
1137                         }\r
1138                         String randint = new String(Long.toString(l));\r
1139                 \r
1140                         Notebook newBook = new Notebook();\r
1141                         newBook.setUpdateSequenceNum(0);\r
1142                         newBook.setGuid(randint);\r
1143                         newBook.setName("Conflicting Changes");\r
1144                         newBook.setServiceCreated(new Date().getTime());\r
1145                         newBook.setServiceUpdated(new Date().getTime());\r
1146                         newBook.setDefaultNotebook(false);\r
1147                         newBook.setPublished(false);\r
1148                         \r
1149                         conn.getNotebookTable().addNotebook(newBook, false, true);\r
1150                         notebookGuid = newBook.getGuid();\r
1151                 }\r
1152                 \r
1153                 // Now that we have a good notebook guid, we need to move the conflicting note\r
1154                 // to the local notebook\r
1155                 logger.log(logger.EXTREME, "Moving conflicting note " +guid);\r
1156                 Calendar currentTime = new GregorianCalendar();\r
1157                 Long l = new Long(currentTime.getTimeInMillis());\r
1158                 long prevTime = l;\r
1159                 while (prevTime==l) {\r
1160                         currentTime = new GregorianCalendar();\r
1161                         l = currentTime.getTimeInMillis();\r
1162                 }\r
1163                 String newGuid = new String(Long.toString(l));\r
1164                                         \r
1165                 Note oldNote = conn.getNoteTable().getNote(guid, true, true, false, false, false);\r
1166                 for (int i=0; i<oldNote.getResources().size() && keepRunning; i++) {\r
1167                         l = new Long(currentTime.getTimeInMillis());\r
1168                         String newResG = new String(Long.toString(l));\r
1169                         String oldResG = oldNote.getResources().get(i).getGuid();\r
1170                         conn.getNoteTable().noteResourceTable.resetUpdateSequenceNumber(oldResG, true);\r
1171                         conn.getNoteTable().noteResourceTable.updateNoteResourceGuid(oldResG, newResG, true);\r
1172                 }\r
1173                 \r
1174                 conn.getNoteTable().resetSequenceNumber(guid);\r
1175                 conn.getNoteTable().updateNoteGuid(guid, newGuid);\r
1176                 conn.getNoteTable().updateNoteNotebook(newGuid, notebookGuid, true);\r
1177                 \r
1178                 \r
1179                 noteSignal.guidChanged.emit(guid,newGuid);\r
1180         }\r
1181         \r
1182         \r
1183 \r
1184         \r
1185         //******************************************************\r
1186         //******************************************************\r
1187         //** Utility Functions\r
1188         //******************************************************\r
1189         //******************************************************\r
1190         // Convert a byte array to a hex string\r
1191         private static String byteArrayToHexString(byte data[]) {\r
1192                 StringBuffer buf = new StringBuffer();\r
1193             for (byte element : data) {\r
1194                 int halfbyte = (element >>> 4) & 0x0F;\r
1195                 int two_halfs = 0;\r
1196                 do {\r
1197                         if ((0 <= halfbyte) && (halfbyte <= 9))\r
1198                                buf.append((char) ('0' + halfbyte));\r
1199                            else\r
1200                                 buf.append((char) ('a' + (halfbyte - 10)));\r
1201                         halfbyte = element & 0x0F;\r
1202                 } while(two_halfs++ < 1);\r
1203             }\r
1204             return buf.toString();              \r
1205         }\r
1206 \r
1207         \r
1208         \r
1209         //*******************************************************\r
1210         //* Find dirty tags, which do not have newly created parents\r
1211         //*******************************************************\r
1212         private Tag findNextTag() {\r
1213                 logger.log(logger.HIGH, "Entering SyncRunner.findNextTag");\r
1214                 Tag nextTag = null;\r
1215                 List<Tag> tags = conn.getTagTable().getDirty();\r
1216                 \r
1217                 // Find the parent.  If the parent has a sequence > 0 then it is a good\r
1218                 // parent.\r
1219                 for (int i=0; i<tags.size() && keepRunning; i++) {\r
1220                         if (tags.get(i).getParentGuid() == null) {\r
1221                                 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found without parent");\r
1222                                 return tags.get(i);\r
1223                         }\r
1224                         Tag parentTag = conn.getTagTable().getTag(tags.get(i).getParentGuid());\r
1225                         if (parentTag.getUpdateSequenceNum() > 0) {\r
1226                                 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found");\r
1227                                 return tags.get(i);\r
1228                         }\r
1229                 }\r
1230                 \r
1231                 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - no tags returned");\r
1232                 return nextTag;\r
1233         }\r
1234         \r
1235         \r
1236            // Connect to Evernote\r
1237     public boolean enConnect()  {\r
1238         try {\r
1239                         userStoreTrans = new THttpClient(userStoreUrl);\r
1240                 } catch (TTransportException e) {\r
1241                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());\r
1242                         mb.exec();\r
1243                         e.printStackTrace();\r
1244                 }\r
1245                 userStoreProt = new TBinaryProtocol(userStoreTrans);\r
1246             userStore = new UserStore.Client(userStoreProt, userStoreProt);\r
1247             syncSignal.saveUserStore.emit(userStore);\r
1248             try {\r
1249                         authResult = userStore.authenticate(username, password, consumerKey, consumerSecret);\r
1250                 } catch (EDAMUserException e) {\r
1251                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Error", "Incorrect username/password");\r
1252                         mb.exec();\r
1253                         isConnected = false;\r
1254                         return false;\r
1255                 } catch (EDAMSystemException e) {\r
1256                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "EDAM System Excepton", e.getLocalizedMessage());\r
1257                         mb.exec();\r
1258                         e.printStackTrace();\r
1259                         isConnected = false;\r
1260                 } catch (TException e) {\r
1261                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());\r
1262                         mb.exec();\r
1263                         e.printStackTrace();\r
1264                         isConnected = false;\r
1265                 }\r
1266                 \r
1267             boolean versionOk = false;\r
1268                 try {\r
1269 //                      versionOk = userStore.checkVersion("Dave's EDAMDemo (Java)", \r
1270                         versionOk = userStore.checkVersion("NeverNote", \r
1271                     com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR, \r
1272                       com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR);\r
1273                 } catch (TException e) {\r
1274                         e.printStackTrace();\r
1275                         isConnected = false;\r
1276                 } \r
1277             if (!versionOk) { \r
1278                 System.err.println("Incomatible EDAM client protocol version"); \r
1279                 isConnected = false;\r
1280             }\r
1281             if (authResult != null) {\r
1282                 user = authResult.getUser(); \r
1283                 authToken = authResult.getAuthenticationToken(); \r
1284                 noteStoreUrl = noteStoreUrlBase + user.getShardId();\r
1285                 syncSignal.saveAuthToken.emit(authToken);\r
1286                 syncSignal.saveNoteStore.emit(noteStore);\r
1287                 \r
1288          \r
1289                 try {\r
1290                         noteStoreTrans = new THttpClient(noteStoreUrl);\r
1291                 } catch (TTransportException e) {\r
1292                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());\r
1293                         mb.exec();\r
1294                         e.printStackTrace();\r
1295                         isConnected = false;\r
1296                 } \r
1297                 noteStoreProt = new TBinaryProtocol(noteStoreTrans);\r
1298                 noteStore = \r
1299                         new NoteStore.Client(noteStoreProt, noteStoreProt); \r
1300                 isConnected = true;\r
1301                 authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();\r
1302                 authRefreshTime = authTimeRemaining / 2;\r
1303             }\r
1304             \r
1305                 // Get user information\r
1306                 try {\r
1307                         User user = userStore.getUser(authToken);\r
1308                         syncSignal.saveUserInformation.emit(user);\r
1309                 } catch (EDAMUserException e1) {\r
1310                         e1.printStackTrace();\r
1311                 } catch (EDAMSystemException e1) {\r
1312                         e1.printStackTrace();\r
1313                 } catch (TException e1) {\r
1314                         e1.printStackTrace();\r
1315                 }\r
1316             \r
1317             return isConnected;\r
1318     }\r
1319         // Disconnect from the database                         \r
1320     public void enDisconnect() {\r
1321         isConnected = false;\r
1322     }\r
1323     // Refresh the connection\r
1324     private synchronized void refreshConnection() {\r
1325                 logger.log(logger.EXTREME, "Entering SyncRunner.refreshConnection()");\r
1326 //        Calendar cal = Calendar.getInstance();\r
1327                 \r
1328         // If we are not connected let's get out of here\r
1329         if (!isConnected)\r
1330                 return;\r
1331         \r
1332                 // If we fail too many times, then let's give up.\r
1333                 if (failedRefreshes >=5) {\r
1334                         logger.log(logger.EXTREME, "Refresh attempts have failed.  Disconnecting.");\r
1335                         isConnected = false;\r
1336                         return;\r
1337                 }\r
1338         \r
1339                 // If this is the first time through, then we need to set this\r
1340 //              if (authRefreshTime == 0 || cal.getTimeInMillis() > authRefreshTime) \r
1341 //                      authRefreshTime = cal.getTimeInMillis();\r
1342                 \r
1343  //             // Default to checking again in 5 min.  This in case we fail.\r
1344  //             authRefreshTime = authRefreshTime +(5*60*1000);     \r
1345 \r
1346                 // Try to get a new token\r
1347                 AuthenticationResult newAuth = null; \r
1348                 logger.log(logger.EXTREME, "Beginning to try authentication refresh");\r
1349         try {\r
1350                 if (userStore != null && authToken != null) \r
1351                         newAuth = userStore.refreshAuthentication(authToken); \r
1352                 else\r
1353                         return;\r
1354                 logger.log(logger.EXTREME, "UserStore.refreshAuthentication has succeeded.");\r
1355                 } catch (EDAMUserException e) {\r
1356                         e.printStackTrace();\r
1357                         syncSignal.authRefreshComplete.emit(false);\r
1358                         failedRefreshes++;\r
1359                         return;\r
1360                 } catch (EDAMSystemException e) {\r
1361                         e.printStackTrace();\r
1362                         syncSignal.authRefreshComplete.emit(false);\r
1363                         failedRefreshes++;\r
1364                         return;         \r
1365                 } catch (TException e) { \r
1366                         e.printStackTrace();\r
1367                         syncSignal.authRefreshComplete.emit(false);\r
1368                         failedRefreshes++;\r
1369                         return;\r
1370                 }\r
1371                 \r
1372                 // If we didn't get a good auth, then we've failed\r
1373                 if (newAuth == null) {\r
1374                         failedRefreshes++;\r
1375                         logger.log(logger.EXTREME, "Authentication failure #" +failedRefreshes);\r
1376                         syncSignal.authRefreshComplete.emit(false);\r
1377                         return;\r
1378                 }\r
1379                 \r
1380                 // We got a good token.  Now we need to setup the time to renew it.\r
1381                 logger.log(logger.EXTREME, "Saving authentication tokens");\r
1382                 authResult = newAuth;\r
1383                 authToken = new String(newAuth.getAuthenticationToken());\r
1384 //              authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();\r
1385 //              authRefreshTime = cal.getTimeInMillis() + (authTimeRemaining/4);        \r
1386                 failedRefreshes=0;\r
1387                 syncSignal.authRefreshComplete.emit(true);\r
1388                 authRefreshNeeded = false;\r
1389                         \r
1390                 // This should never happen, but if it does we consider this a faild attempt.\r
1391 //              if (authTimeRemaining <= 0) {\r
1392 //                      failedRefreshes++;\r
1393 //                      syncSignal.authRefreshComplete.emit(false);\r
1394 //              }\r
1395     }\r
1396         \r
1397         public synchronized boolean addWork(String request) {\r
1398                 if (workQueue.offer(request))\r
1399                         return true;\r
1400                 return false;\r
1401         }\r
1402     \r
1403     private Note getNoteContent(Note n) {\r
1404         n.setContent(conn.getNoteTable().getNoteContentBinary(n.getGuid()));\r
1405         return n;\r
1406     }\r
1407 }\r