2 * This file is part of NixNote/NeighborNote
3 * Copyright 2009 Randy Baumgarte
4 * Copyright 2013 Yuki Takahashi
6 * This file may be licensed under the terms of of the
7 * GNU General Public License Version 2 (the ``GPL'').
9 * Software distributed under the License is distributed
10 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
11 * express or implied. See the GPL for the specific language
12 * governing rights and limitations.
14 * You should have received a copy of the GPL along with this
15 * program. If not, go to http://www.gnu.org/licenses/gpl.html
16 * or write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package cx.fbn.nevernote.threads;
22 import java.io.BufferedOutputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.UnsupportedEncodingException;
28 import java.net.UnknownHostException;
29 import java.util.ArrayList;
30 import java.util.Calendar;
31 import java.util.Date;
32 import java.util.GregorianCalendar;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.TreeSet;
36 import java.util.concurrent.LinkedBlockingQueue;
38 import org.apache.http.HttpEntity;
39 import org.apache.http.HttpResponse;
40 import org.apache.http.NameValuePair;
41 import org.apache.http.client.ClientProtocolException;
42 import org.apache.http.client.HttpClient;
43 import org.apache.http.client.entity.UrlEncodedFormEntity;
44 import org.apache.http.client.methods.HttpPost;
45 import org.apache.http.impl.client.DefaultHttpClient;
46 import org.apache.http.message.BasicNameValuePair;
47 import org.apache.http.protocol.HTTP;
49 import com.evernote.edam.error.EDAMErrorCode;
50 import com.evernote.edam.error.EDAMNotFoundException;
51 import com.evernote.edam.error.EDAMSystemException;
52 import com.evernote.edam.error.EDAMUserException;
53 import com.evernote.edam.notestore.NoteStore;
54 import com.evernote.edam.notestore.NoteStore.Client;
55 import com.evernote.edam.notestore.SyncChunk;
56 import com.evernote.edam.notestore.SyncState;
57 import com.evernote.edam.type.Data;
58 import com.evernote.edam.type.LinkedNotebook;
59 import com.evernote.edam.type.Note;
60 import com.evernote.edam.type.Notebook;
61 import com.evernote.edam.type.Resource;
62 import com.evernote.edam.type.SavedSearch;
63 import com.evernote.edam.type.SharedNotebook;
64 import com.evernote.edam.type.Tag;
65 import com.evernote.edam.type.User;
66 import com.evernote.edam.userstore.AuthenticationResult;
67 import com.evernote.edam.userstore.UserStore;
68 import com.evernote.thrift.TException;
69 import com.evernote.thrift.protocol.TBinaryProtocol;
70 import com.evernote.thrift.transport.THttpClient;
71 import com.evernote.thrift.transport.TTransportException;
72 import com.trolltech.qt.core.QByteArray;
73 import com.trolltech.qt.core.QFile;
74 import com.trolltech.qt.core.QIODevice.OpenModeFlag;
75 import com.trolltech.qt.core.QObject;
76 import com.trolltech.qt.core.QTextCodec;
77 import com.trolltech.qt.gui.QMessageBox;
79 import cx.fbn.nevernote.signals.LimitSignal;
80 import cx.fbn.nevernote.signals.NoteIndexSignal;
81 import cx.fbn.nevernote.signals.NoteResourceSignal;
82 import cx.fbn.nevernote.signals.NoteSignal;
83 import cx.fbn.nevernote.signals.NotebookSignal;
84 import cx.fbn.nevernote.signals.SavedSearchSignal;
85 import cx.fbn.nevernote.signals.StatusSignal;
86 import cx.fbn.nevernote.signals.SyncSignal;
87 import cx.fbn.nevernote.signals.TagSignal;
88 import cx.fbn.nevernote.sql.DatabaseConnection;
89 import cx.fbn.nevernote.sql.DeletedItemRecord;
90 import cx.fbn.nevernote.utilities.ApplicationLogger;
93 public class SyncRunner extends QObject implements Runnable {
95 private final ApplicationLogger logger;
96 private DatabaseConnection conn;
99 public volatile List<String> errorSharedNotebooks;
100 public volatile HashMap<String,String> errorSharedNotebooksIgnored;
101 public volatile boolean isConnected;
102 public volatile boolean keepRunning;
103 public volatile String authToken;
104 private long evernoteUpdateCount;
105 private final String userAgent = "NeighborNote/" + System.getProperty("os.name")
106 +"/"+System.getProperty("java.vendor") + "/"
107 + System.getProperty("java.version") +";";
109 public volatile NoteStore.Client localNoteStore;
110 private UserStore.Client userStore;
112 public volatile StatusSignal status;
113 public volatile TagSignal tagSignal;
114 public volatile NotebookSignal notebookSignal;
115 public volatile NoteIndexSignal noteIndexSignal;
116 public volatile NoteSignal noteSignal;
117 public volatile SavedSearchSignal searchSignal;
118 public volatile NoteResourceSignal resourceSignal;
119 public volatile SyncSignal syncSignal;
120 public volatile LimitSignal limitSignal;
121 public volatile boolean authRefreshNeeded;
122 public volatile boolean syncNeeded;
123 public volatile boolean disableUploads;
124 public volatile boolean syncDeletedContent;
125 private volatile List<String> dirtyNoteGuids;
127 public volatile String username = "";
128 public volatile String password = "";
129 public volatile String userStoreUrl;
130 public String noteStoreUrlBase;
131 private THttpClient userStoreTrans;
132 private TBinaryProtocol userStoreProt;
133 //private AuthenticationResult authResult;
134 private AuthenticationResult linkedAuthResult;
136 // private long authTimeRemaining;
137 public long authRefreshTime;
138 public long failedRefreshes = 0;
139 public THttpClient noteStoreTrans;
140 public TBinaryProtocol noteStoreProt;
141 public String noteStoreUrl;
142 public long sequenceDate;
143 public int updateSequenceNumber;
144 private boolean refreshNeeded;
145 private volatile LinkedBlockingQueue<String> workQueue;
146 private static int MAX_QUEUED_WAITING = 1000;
155 private final TreeSet<String> ignoreTags;
156 private final TreeSet<String> ignoreNotebooks;
157 private final TreeSet<String> ignoreLinkedNotebooks;
158 private HashMap<String,String> badTagSync;
161 public SyncRunner(String logname, String u, String i, String r, String b, String uid, String pswd, String cpswd) {
162 logger = new ApplicationLogger(logname);
164 noteSignal = new NoteSignal();
165 status = new StatusSignal();
166 tagSignal = new TagSignal();
167 notebookSignal = new NotebookSignal();
168 noteIndexSignal = new NoteIndexSignal();
169 noteSignal = new NoteSignal();
170 searchSignal = new SavedSearchSignal();
171 syncSignal = new SyncSignal();
172 resourceSignal = new NoteResourceSignal();
173 limitSignal = new LimitSignal();
182 // this.setAutoDelete(false);
186 authRefreshNeeded = false;
189 disableUploads = false;
190 ignoreTags = new TreeSet<String>();
191 ignoreNotebooks = new TreeSet<String>();
192 ignoreLinkedNotebooks = new TreeSet<String>();
194 // setAutoDelete(false);
195 workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);
199 errorSharedNotebooks = new ArrayList<String>();
200 errorSharedNotebooksIgnored = new HashMap<String,String>();
202 logger.log(logger.EXTREME, "Starting thread");
203 conn = new DatabaseConnection(logger, dburl, indexUrl, resourceUrl, behaviorUrl, dbuid, dbpswd, dbcpswd, 200);
205 logger.log(logger.EXTREME, "Blocking until work is found");
206 String work = workQueue.take();
207 logger.log(logger.LOW, "Dirty Notes Before Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
208 logger.log(logger.EXTREME, "Work found: " +work);
209 if (work.equalsIgnoreCase("stop")) {
213 conn.getNoteTable().dumpDirtyNotes(); // Debugging statement
217 logger.log(logger.EXTREME, "SyncNeeded is true");
219 sequenceDate = conn.getSyncTable().getLastSequenceDate();
220 updateSequenceNumber = conn.getSyncTable().getUpdateSequenceNumber();
222 logger.log(logger.EXTREME, "Beginning sync");
223 evernoteSync(localNoteStore);
224 logger.log(logger.EXTREME, "Sync finished");
225 } catch (UnknownHostException e) {
226 status.message.emit(e.getMessage());
230 logger.log(logger.EXTREME, "Signaling refresh finished. refreshNeeded=" +refreshNeeded);
231 syncSignal.finished.emit(refreshNeeded);
233 syncSignal.errorDisconnect.emit();
234 status.message.emit(tr("Error synchronizing - see log for details."));
236 logger.log(logger.LOW, "Dirty Notes After Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
237 conn.getNoteTable().dumpDirtyNotes();
238 logger.log(logger.LOW, "---");
241 catch (InterruptedException e1) {
242 e1.printStackTrace();
248 public DatabaseConnection getConnection() {
252 public boolean isIdle() {
257 public void setConnected(boolean c) {
260 public void setKeepRunning(boolean r) {
261 logger.log(logger.EXTREME, "Setting keepRunning=" +r);
264 public void setNoteStore(NoteStore.Client c) {
265 logger.log(logger.EXTREME, "Setting NoteStore in sync thread");
268 public void setUserStore(UserStore.Client c) {
269 logger.log(logger.EXTREME, "Setting UserStore in sync thread");
273 public void setEvernoteUpdateCount(long s) {
274 logger.log(logger.EXTREME, "Setting Update Count in sync thread");
275 evernoteUpdateCount = s;
278 //***************************************************************
279 //***************************************************************
280 //** These functions deal with Evernote communications
281 //***************************************************************
282 //***************************************************************
283 // Synchronize changes with Evernote
284 @SuppressWarnings("unused")
285 private void evernoteSync(Client noteStore) throws java.net.UnknownHostException {
286 logger.log(logger.HIGH, "Entering SyncRunner.evernoteSync");
288 // Rebuild list of tags & notebooks to ignore
289 ignoreNotebooks.clear();
290 List<String> ignore = conn.getSyncTable().getIgnoreRecords("NOTEBOOK");
291 for (int i=0; i<ignore.size(); i++)
292 ignoreNotebooks.add(ignore.get(i));
295 ignore = conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK");
296 for (int i=0; i<ignore.size(); i++)
297 ignoreLinkedNotebooks.add(ignore.get(i));
300 ignore = conn.getSyncTable().getIgnoreRecords("TAG");
301 for (int i=0; i<ignore.size(); i++)
302 ignoreTags.add(ignore.get(i));
304 // Make sure we are connected & should keep running
305 if (isConnected && keepRunning) {
307 logger.log(logger.EXTREME, "Synchronizing with Evernote");
308 status.message.emit(tr("Synchronizing with Evernote"));
310 // Get user information
312 logger.log(logger.EXTREME, "getting user from userstore");
313 User user = userStore.getUser(authToken);
314 logger.log(logger.EXTREME, "Saving user information");
315 syncSignal.saveUserInformation.emit(user);
316 } catch (EDAMUserException e1) {
317 e1.printStackTrace();
318 status.message.emit(tr("User exception getting user account information. Aborting sync and disconnecting"));
319 syncSignal.errorDisconnect.emit();
323 } catch (EDAMSystemException e1) {
324 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
325 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
328 e1.printStackTrace();
329 status.message.emit(tr("System error user account information. Aborting sync and disconnecting!"));
330 syncSignal.errorDisconnect.emit();
334 } catch (TException e1) {
335 e1.printStackTrace();
336 syncSignal.errorDisconnect.emit();
338 status.message.emit(tr("Transaction error getting user account information. Aborting sync and disconnecting!"));
344 SyncState syncState = null;
346 logger.log(logger.EXTREME, "Getting sync state");
347 syncState = noteStore.getSyncState(authToken);
348 syncSignal.saveUploadAmount.emit(syncState.getUploaded());
349 syncSignal.saveEvernoteUpdateCount.emit(syncState.getUpdateCount());
350 evernoteUpdateCount = syncState.getUpdateCount();
351 } catch (EDAMUserException e) {
353 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
354 syncSignal.errorDisconnect.emit();
357 } catch (EDAMSystemException e) {
358 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
359 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
362 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
363 syncSignal.errorDisconnect.emit();
366 } catch (TException e) {
368 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
369 syncSignal.errorDisconnect.emit();
374 if (syncState == null) {
375 logger.log(logger.EXTREME, "Sync State is null");
376 status.message.emit(tr("Syncronization Error!"));
380 // Determine what to do.
381 // If we need to do a full sync.
382 logger.log(logger.LOW, "Full Sequence Before: " +syncState.getFullSyncBefore());
383 logger.log(logger.LOW, "Last Sequence Date: " +sequenceDate);
384 logger.log(logger.LOW, "Var Last Sequence Number: " +updateSequenceNumber);
385 logger.log(logger.LOW, "DB Last Sequence Number: " + conn.getSyncTable().getUpdateSequenceNumber());
386 if (syncState.getFullSyncBefore() > sequenceDate) {
387 logger.log(logger.EXTREME, "Full sequence date has expired");
389 conn.getSyncTable().setLastSequenceDate(0);
390 updateSequenceNumber = 0;
391 conn.getSyncTable().setUpdateSequenceNumber(0);
393 // Check for "special" sync instructions
394 String syncLinked = conn.getSyncTable().getRecord("FullLinkedNotebookSync");
395 String syncShared = conn.getSyncTable().getRecord("FullSharedNotebookSync");
396 String syncNotebooks = conn.getSyncTable().getRecord("FullNotebookSync");
397 String syncInkNoteImages = conn.getSyncTable().getRecord("FullInkNoteImageSync");
398 if (syncLinked != null) {
399 downloadAllLinkedNotebooks(localNoteStore);
401 if (syncShared != null) {
402 downloadAllSharedNotebooks(localNoteStore);
404 if (syncNotebooks != null) {
405 downloadAllNotebooks(localNoteStore);
408 if (syncInkNoteImages != null) {
409 List<String> guids = conn.getNoteTable().noteResourceTable.findInkNotes();
410 for (int i=0; i<guids.size(); i++) {
411 downloadInkNoteImage(guids.get(i), authToken);
413 conn.getSyncTable().deleteRecord("FullInkNoteImageSync");
416 // If there are remote changes
417 logger.log(logger.LOW, "Update Count: " +syncState.getUpdateCount());
418 logger.log(logger.LOW, "Last Update Count: " +updateSequenceNumber);
420 if (syncState.getUpdateCount() > updateSequenceNumber) {
421 logger.log(logger.EXTREME, "Refresh needed is true");
422 refreshNeeded = true;
423 logger.log(logger.EXTREME, "Downloading changes");
424 syncRemoteToLocal(localNoteStore);
427 //*****************************************
428 //* Sync linked/shared notebooks
429 //*****************************************
430 syncLinkedNotebooks();
431 //conn.getNoteTable().getDirty();
432 //disableUploads = true; /// DELETE THIS LINE!!!!
433 if (!disableUploads) {
434 logger.log(logger.EXTREME, "Uploading changes");
435 // Synchronize remote changes
437 syncExpunged(localNoteStore);
439 syncLocalTags(localNoteStore);
441 syncLocalNotebooks(localNoteStore);
443 syncLocalLinkedNotebooks(localNoteStore);
445 syncDeletedNotes(localNoteStore);
449 syncLocalSavedSearches(localNoteStore);
452 status.message.emit(tr("Cleaning up"));
453 List<String> notes = conn.getNoteTable().expungeIgnoreSynchronizedNotes(conn.getSyncTable().getIgnoreRecords("NOTEBOOK"),
454 conn.getSyncTable().getIgnoreRecords("TAG"), conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK"));
455 if (notes.size() > 0)
456 syncSignal.refreshLists.emit();
458 //*****************************************
459 //* End of synchronization
460 //*****************************************
462 syncSignal.refreshLists.emit();
465 logger.log(logger.LOW, "Sync completed. Errors=" +error);
467 status.message.emit(tr("Synchronizing complete"));
469 status.message.emit(tr("Download syncronization complete. Uploads have been disabled."));
471 logger.log(logger.EXTREME, "Saving sync time");
472 if (syncState.getCurrentTime() > sequenceDate)
473 sequenceDate = syncState.getCurrentTime();
474 if (syncState.getUpdateCount() > updateSequenceNumber)
475 updateSequenceNumber = syncState.getUpdateCount();
476 conn.getSyncTable().setLastSequenceDate(sequenceDate);
477 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
480 logger.log(logger.HIGH, "Leaving SyncRunner.evernoteSync");
483 // Sync deleted items with Evernote
484 private void syncExpunged(Client noteStore) {
485 logger.log(logger.HIGH, "Entering SyncRunner.syncExpunged");
487 List<DeletedItemRecord> expunged = conn.getDeletedTable().getAllDeleted();
488 boolean error = false;
489 for (int i=0; i<expunged.size() && keepRunning; i++) {
491 // if (authRefreshNeeded)
492 // if (!refreshConnection())
496 if (expunged.get(i).type.equalsIgnoreCase("TAG")) {
497 logger.log(logger.EXTREME, "Tag expunged");
498 conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "TAG");
499 updateSequenceNumber = noteStore.expungeTag(authToken, expunged.get(i).guid);
500 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
501 conn.getSyncTable().setLastSequenceDate(sequenceDate);
502 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
504 if (expunged.get(i).type.equalsIgnoreCase("NOTEBOOK")) {
505 logger.log(logger.EXTREME, "Notebook expunged");
506 conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "NOTEBOOK");
507 updateSequenceNumber = noteStore.expungeNotebook(authToken, expunged.get(i).guid);
508 conn.getSyncTable().setLastSequenceDate(sequenceDate);
509 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
511 if (expunged.get(i).type.equalsIgnoreCase("NOTE")) {
512 logger.log(logger.EXTREME, "Note expunged");
513 conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "NOTE");
514 updateSequenceNumber = noteStore.deleteNote(authToken, expunged.get(i).guid);
515 refreshNeeded = true;
516 conn.getSyncTable().setLastSequenceDate(sequenceDate);
517 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
519 if (expunged.get(i).type.equalsIgnoreCase("SAVEDSEARCH")) {
520 logger.log(logger.EXTREME, "saved search expunged");
521 conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "SAVEDSEARCH");
522 updateSequenceNumber = noteStore.expungeSearch(authToken, expunged.get(i).guid);
523 conn.getSyncTable().setLastSequenceDate(sequenceDate);
524 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
526 } catch (EDAMUserException e) {
527 logger.log(logger.LOW, "EDAM User Excepton in syncExpunged: " +expunged.get(i).guid); // This can happen if we try to delete a deleted note
528 } catch (EDAMSystemException e) {
529 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
530 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
532 logger.log(logger.LOW, "EDAM System Excepton in syncExpunged: "+expunged.get(i).guid);
533 logger.log(logger.LOW, e.getStackTrace());
535 } catch (EDAMNotFoundException e) {
536 logger.log(logger.LOW, "EDAM Not Found Excepton in syncExpunged: "+expunged.get(i).guid);
537 } catch (TException e) {
538 logger.log(logger.LOW, "EDAM TExcepton in syncExpunged: "+expunged.get(i).guid);
539 logger.log(logger.LOW, e.getStackTrace());
544 conn.getDeletedTable().expungeAllDeletedRecords();
546 logger.log(logger.HIGH, "Leaving SyncRunner.syncExpunged");
549 private void syncDeletedNotes(Client noteStore) {
550 if (syncDeletedContent)
552 logger.log(logger.HIGH, "Entering SyncRunner.syncDeletedNotes");
553 status.message.emit(tr("Synchronizing deleted notes."));
555 List<Note> notes = conn.getNoteTable().getDirty();
556 // Sync the local notebooks with Evernote's
557 for (int i=0; i<notes.size() && keepRunning; i++) {
559 // if (authRefreshNeeded)
560 // if (!refreshConnection())
563 Note enNote = notes.get(i);
565 if (enNote.getUpdateSequenceNum() > 0 && (enNote.isActive() == false || enNote.getDeleted() > 0)) {
566 // Check that the note is valid.
567 if (enNote.isActive() == true || enNote.getDeleted() == 0) {
568 conn.getNoteTable().deleteNote(enNote.getGuid());
569 enNote = conn.getNoteTable().getNote(enNote.getGuid(), false, false, false, false, false);
571 if (syncDeletedContent) {
572 logger.log(logger.EXTREME, "Deleted note found & synch content selected");
573 Note delNote = conn.getNoteTable().getNote(enNote.getGuid(), true, true, true, true, true);
574 delNote = getNoteContent(delNote);
575 delNote = noteStore.updateNote(authToken, delNote);
576 enNote.setUpdateSequenceNum(delNote.getUpdateSequenceNum());
577 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
579 logger.log(logger.EXTREME, "Deleted note found & sync content not selected");
580 int usn = noteStore.deleteNote(authToken, enNote.getGuid());
581 enNote.setUpdateSequenceNum(usn);
582 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
584 logger.log(logger.EXTREME, "Resetting deleted dirty flag");
585 conn.getNoteTable().resetDirtyFlag(enNote.getGuid());
586 updateSequenceNumber = enNote.getUpdateSequenceNum();
587 logger.log(logger.EXTREME, "Saving sequence number");
588 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
590 } catch (EDAMUserException e) {
591 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);
592 //status.message.emit("Error sending local note: " +e.getParameter());
593 //logger.log(logger.LOW, e.toString());
595 } catch (EDAMSystemException e) {
596 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
597 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
599 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
600 status.message.emit(tr("Error: ") +e);
601 logger.log(logger.LOW, e.toString());
603 } catch (EDAMNotFoundException e) {
604 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);
605 //status.message.emit("Error deleting local note: " +e +" - Continuing");
606 //logger.log(logger.LOW, e.toString());
608 } catch (TException e) {
609 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);
610 status.message.emit(tr("Error sending local note: ") +e);
611 logger.log(logger.LOW, e.toString());
616 // Sync notes with Evernote
617 private void syncLocalNotes() {
618 logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");
619 logger.log(logger.LOW, "Dirty local notes found: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
620 status.message.emit(tr("Sending local notes."));
622 List<Note> notes = conn.getNoteTable().getDirty();
623 // Sync the local notebooks with Evernote's
624 for (int i=0; i<notes.size() && keepRunning; i++) {
625 syncLocalNote(localNoteStore, notes.get(i), authToken);
627 logger.log(logger.HIGH, "Leaving SyncRunner.syncNotes");
630 // Sync notes with Evernote
631 private void syncLocalNote(Client noteStore, Note enNote, String token) {
632 logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");
633 status.message.emit(tr("Sending local notes."));
635 if (enNote.isActive()) {
637 if (enNote.getUpdateSequenceNum() > 0) {
638 logger.log(logger.EXTREME, "Active dirty note found - non new - " +enNote.getGuid());
639 logger.log(logger.EXTREME, "Fetching note content");
640 enNote = getNoteContent(enNote);
641 logger.log(logger.MEDIUM, "Updating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
642 enNote = noteStore.updateNote(token, enNote);
644 logger.log(logger.EXTREME, "Active dirty found - new note " +enNote.getGuid());
645 String oldGuid = enNote.getGuid();
646 logger.log(logger.MEDIUM, "Fetching note content");
647 enNote = getNoteContent(enNote);
648 logger.log(logger.MEDIUM, "Creating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
649 enNote = noteStore.createNote(token, enNote);
650 logger.log(logger.MEDIUM, "New note Guid : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
651 noteSignal.guidChanged.emit(oldGuid, enNote.getGuid());
652 conn.getNoteTable().updateNoteGuid(oldGuid, enNote.getGuid());
654 updateSequenceNumber = enNote.getUpdateSequenceNum();
655 logger.log(logger.EXTREME, "Saving note");
656 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
657 List<Resource> rl = enNote.getResources();
658 logger.log(logger.EXTREME, "Getting note resources");
659 for (int j=0; j<enNote.getResourcesSize() && keepRunning; j++) {
660 Resource newRes = rl.get(j);
661 Data d = newRes.getData();
663 logger.log(logger.EXTREME, "Calculating resource hash");
664 String hash = byteArrayToHexString(d.getBodyHash());
665 logger.log(logger.EXTREME, "updating resources by hash");
666 String oldGuid = conn.getNoteTable().noteResourceTable.getNoteResourceGuidByHashHex(enNote.getGuid(), hash);
667 conn.getNoteTable().updateNoteResourceGuidbyHash(enNote.getGuid(), newRes.getGuid(), hash);
668 resourceSignal.resourceGuidChanged.emit(enNote.getGuid(), oldGuid, newRes.getGuid());
671 logger.log(logger.EXTREME, "Resetting note dirty flag");
672 conn.getNoteTable().resetDirtyFlag(enNote.getGuid());
673 updateSequenceNumber = enNote.getUpdateSequenceNum();
674 logger.log(logger.EXTREME, "Emitting note sequence number change");
675 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
677 } catch (EDAMUserException e) {
678 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);
679 status.message.emit(tr("Error sending local note: ") +e.getParameter());
680 logger.log(logger.LOW, e.toString());
682 } catch (EDAMSystemException e) {
683 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
684 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
686 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
687 status.message.emit(tr("Error: ") +e);
688 logger.log(logger.LOW, e.toString());
690 } catch (EDAMNotFoundException e) {
691 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);
692 status.message.emit(tr("Error sending local note: ") +e);
693 logger.log(logger.LOW, e.toString());
695 } catch (TException e) {
696 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);
697 status.message.emit(tr("Error sending local note: ") +e);
698 logger.log(logger.LOW, e.toString());
702 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNote");
706 // Sync Notebooks with Evernote
707 private void syncLocalNotebooks(Client noteStore) {
708 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalNotebooks");
710 status.message.emit(tr("Sending local notebooks."));
711 List<Notebook> remoteList = new ArrayList<Notebook>();
713 logger.log(logger.EXTREME, "Getting remote notebooks to compare with local");
714 remoteList = noteStore.listNotebooks(authToken);
715 } catch (EDAMUserException e1) {
716 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks getting remote Notebook List");
717 status.message.emit(tr("Error: ") +e1);
718 logger.log(logger.LOW, e1.toString());
720 } catch (EDAMSystemException e1) {
721 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
722 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
724 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks getting remote Notebook List");
725 status.message.emit(tr("Error: ") +e1);
726 logger.log(logger.LOW, e1.toString());
728 } catch (TException e1) {
729 logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalNotebooks getting remote Notebook List");
730 status.message.emit(tr("Error: ") +e1);
731 logger.log(logger.LOW, e1.toString());
734 logger.log(logger.EXTREME, "Getting local dirty notebooks");
735 List<Notebook> notebooks = conn.getNotebookTable().getDirty();
737 // Sync the local notebooks with Evernote's
738 for (int i=0; i<notebooks.size() && keepRunning; i++) {
740 // if (authRefreshNeeded)
741 // if (!refreshConnection())
744 Notebook enNotebook = notebooks.get(i);
746 if (enNotebook.getUpdateSequenceNum() > 0) {
747 logger.log(logger.EXTREME, "Existing notebook is dirty");
748 sequence = noteStore.updateNotebook(authToken, enNotebook);
750 logger.log(logger.EXTREME, "New dirty notebook found");
751 String oldGuid = enNotebook.getGuid();
752 boolean found = false;
754 // Look for a notebook with the same name. If one is found, we don't need
755 // to create another one
756 logger.log(logger.EXTREME, "Looking for matching notebook name");
757 for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
758 if (remoteList.get(k).getName().equalsIgnoreCase(enNotebook.getName())) {
759 enNotebook = remoteList.get(k);
760 logger.log(logger.EXTREME, "Matching notebook found");
765 enNotebook = noteStore.createNotebook(authToken, enNotebook);
767 logger.log(logger.EXTREME, "Updating notebook in database");
768 conn.getNotebookTable().updateNotebookGuid(oldGuid, enNotebook.getGuid());
769 sequence = enNotebook.getUpdateSequenceNum();
771 logger.log(logger.EXTREME, "Updating notebook sequence in database");
772 conn.getNotebookTable().updateNotebookSequence(enNotebook.getGuid(), sequence);
773 logger.log(logger.EXTREME, "Resetting dirty flag in notebook");
774 conn.getNotebookTable().resetDirtyFlag(enNotebook.getGuid());
775 updateSequenceNumber = sequence;
776 logger.log(logger.EXTREME, "Emitting sequence number to main thread");
777 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
778 } catch (EDAMUserException e) {
779 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks");
780 logger.log(logger.LOW, e.toString() + ": Stack : " +enNotebook.getStack());
782 } catch (EDAMSystemException e) {
783 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
784 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
786 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks");
787 logger.log(logger.LOW, e.toString());
789 } catch (EDAMNotFoundException e) {
790 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotebooks");
791 logger.log(logger.LOW, e.toString());
793 } catch (TException e) {
794 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotebooks");
795 logger.log(logger.LOW, e.toString());
799 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNotebooks");
802 // Sync Tags with Evernote
803 private void syncLocalTags(Client noteStore) {
804 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalTags");
805 List<Tag> remoteList = new ArrayList<Tag>();
806 status.message.emit(tr("Sending local tags."));
809 logger.log(logger.EXTREME, "Getting remote tags to compare names with the local tags");
810 remoteList = noteStore.listTags(authToken);
811 } catch (EDAMUserException e1) {
812 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote Tag List");
813 status.message.emit(tr("Error: ") +e1);
814 logger.log(logger.LOW, e1.toString());
816 } catch (EDAMSystemException e1) {
817 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
818 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
820 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote Tag List");
821 status.message.emit(tr("Error: ") +e1);
822 logger.log(logger.LOW, e1.toString());
824 } catch (TException e1) {
825 logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote Tag List");
826 status.message.emit(tr("Error: ") +e1);
827 logger.log(logger.LOW, e1.toString());
833 if (badTagSync == null)
834 badTagSync = new HashMap<String,String>();
838 Tag enTag = findNextTag();
840 // This is a hack. Sometimes this function goes flookey and goes into a
841 // perpetual loop. This causes NeverNote to flood Evernote's servers.
842 // This is a safety valve to prevent unlimited loops.
843 int maxCount = conn.getTagTable().getDirty().size()+10;
846 while(enTag!=null && loopCount < maxCount) {
848 // if (authRefreshNeeded)
849 // if (!refreshConnection())
853 if (enTag.getUpdateSequenceNum() > 0) {
854 logger.log(logger.EXTREME, "Updating tag");
855 sequence = noteStore.updateTag(authToken, enTag);
858 // Look for a tag with the same name. If one is found, we don't need
859 // to create another one
860 logger.log(logger.EXTREME, "New tag. Comparing with remote names");
861 boolean found = false;
862 String oldGuid = enTag.getGuid();
863 for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
864 if (remoteList.get(k).getName().equalsIgnoreCase(enTag.getName())) {
865 conn.getTagTable().updateTagGuid(enTag.getGuid(), remoteList.get(k).getGuid());
866 enTag = remoteList.get(k);
867 logger.log(logger.EXTREME, "Matching tag name found");
872 enTag = noteStore.createTag(authToken, enTag);
874 enTag.setUpdateSequenceNum(noteStore.updateTag(authToken,enTag));
875 sequence = enTag.getUpdateSequenceNum();
876 if (!oldGuid.equals(enTag.getGuid())) {
877 logger.log(logger.EXTREME, "Updating tag guid");
878 conn.getTagTable().updateTagGuid(oldGuid, enTag.getGuid());
881 logger.log(logger.EXTREME, "Updating tag sequence number");
882 conn.getTagTable().updateTagSequence(enTag.getGuid(), sequence);
883 logger.log(logger.EXTREME, "Resetting tag dirty flag");
884 conn.getTagTable().resetDirtyFlag(enTag.getGuid());
885 logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");
886 updateSequenceNumber = sequence;
887 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
888 } catch (EDAMUserException e) {
889 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags: " +enTag.getName());
890 logger.log(logger.LOW, e.toString());
891 badTagSync.put(enTag.getGuid(),null);
893 } catch (EDAMSystemException e) {
894 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
895 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
897 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags: " +enTag.getName());
898 logger.log(logger.LOW, e.toString());
899 badTagSync.put(enTag.getGuid(),null);
901 } catch (EDAMNotFoundException e) {
902 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags: " +enTag.getName());
903 logger.log(logger.LOW, e.toString());
904 badTagSync.put(enTag.getGuid(),null);
906 } catch (TException e) {
907 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags: " +enTag.getName());
908 logger.log(logger.LOW, e.toString());
909 badTagSync.put(enTag.getGuid(),null);
914 logger.log(logger.EXTREME, "Finding next tag");
915 enTag = findNextTag();
917 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalTags");
919 private void syncLocalLinkedNotebooks(Client noteStore) {
920 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalLinkedNotebooks");
922 List<String> list = conn.getLinkedNotebookTable().getDirtyGuids();
923 for (int i=0; i<list.size(); i++) {
924 LinkedNotebook book = conn.getLinkedNotebookTable().getNotebook(list.get(i));
926 noteStore.updateLinkedNotebook(authToken, book);
927 } catch (EDAMUserException e) {
928 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalLinkedNotebooks");
929 status.message.emit(tr("Error: ") +e);
930 logger.log(logger.LOW, e.toString());
933 } catch (EDAMNotFoundException e) {
934 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalLinkedNotebooks");
935 status.message.emit(tr("Error: ") +e);
936 logger.log(logger.LOW, e.toString());
939 } catch (EDAMSystemException e) {
940 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
941 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
943 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalLinkedNotebooks");
944 status.message.emit(tr("Error: ") +e);
945 logger.log(logger.LOW, e.toString());
948 } catch (TException e) {
949 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalLinkedNotebooks");
950 status.message.emit(tr("Error: ") +e);
951 logger.log(logger.LOW, e.toString());
956 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalLinkedNotebooks");
958 // Sync Saved Searches with Evernote
959 private void syncLocalSavedSearches(Client noteStore) {
960 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
961 List<SavedSearch> remoteList = new ArrayList<SavedSearch>();
962 status.message.emit(tr("Sending saved searches."));
964 logger.log(logger.EXTREME, "Getting saved searches to compare with local");
966 remoteList = noteStore.listSearches(authToken);
967 } catch (EDAMUserException e1) {
968 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote saved search List");
969 status.message.emit(tr("Error: ") +e1);
970 logger.log(logger.LOW, e1.toString());
972 } catch (EDAMSystemException e1) {
973 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
974 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
976 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote saved search List");
977 status.message.emit(tr("Error: ") +e1);
978 logger.log(logger.LOW, e1.toString());
980 } catch (TException e1) {
981 logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote saved search List");
982 status.message.emit(tr("Error: ") +e1);
983 logger.log(logger.LOW, e1.toString());
987 List<SavedSearch> searches = conn.getSavedSearchTable().getDirty();
989 // Sync the local notebooks with Evernote's
990 logger.log(logger.EXTREME, "Beginning to send saved searches");
991 for (int i=0; i<searches.size() && keepRunning; i++) {
993 // if (authRefreshNeeded)
994 // if (!refreshConnection())
997 SavedSearch enSearch = searches.get(i);
999 if (enSearch.getUpdateSequenceNum() > 0)
1000 sequence = noteStore.updateSearch(authToken, enSearch);
1002 logger.log(logger.EXTREME, "New saved search found.");
1003 // Look for a tag with the same name. If one is found, we don't need
1004 // to create another one
1005 boolean found = false;
1006 logger.log(logger.EXTREME, "Matching remote saved search names with local");
1007 for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
1008 if (remoteList.get(k).getName().equalsIgnoreCase(enSearch.getName())) {
1009 enSearch = remoteList.get(k);
1011 logger.log(logger.EXTREME, "Matching saved search found");
1012 sequence = enSearch.getUpdateSequenceNum();
1016 String oldGuid = enSearch.getGuid();
1018 enSearch = noteStore.createSearch(authToken, enSearch);
1019 sequence = enSearch.getUpdateSequenceNum();
1020 logger.log(logger.EXTREME, "Updating tag guid in local database");
1021 conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, enSearch.getGuid());
1023 logger.log(logger.EXTREME, "Updating tag sequence in local database");
1024 conn.getSavedSearchTable().updateSavedSearchSequence(enSearch.getGuid(), sequence);
1025 logger.log(logger.EXTREME, "Resetting tag dirty flag");
1026 conn.getSavedSearchTable().resetDirtyFlag(enSearch.getGuid());
1027 logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");
1028 updateSequenceNumber = sequence;
1029 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
1030 } catch (EDAMUserException e) {
1031 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags");
1032 logger.log(logger.LOW, e.toString());
1034 } catch (EDAMSystemException e) {
1035 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1036 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1038 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags");
1039 logger.log(logger.LOW, e.toString());
1041 } catch (EDAMNotFoundException e) {
1042 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags");
1043 logger.log(logger.LOW, e.toString());
1045 } catch (TException e) {
1046 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags");
1047 logger.log(logger.LOW, e.toString());
1052 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
1055 // Sync evernote changes with local database
1056 private void syncRemoteToLocal(Client noteStore) {
1057 logger.log(logger.HIGH, "Entering SyncRunner.syncRemoteToLocal");
1059 List<Note> dirtyNotes = conn.getNoteTable().getDirty();
1060 dirtyNoteGuids = new ArrayList<String>();
1061 for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
1062 dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
1066 SyncChunk chunk = null;
1067 boolean fullSync = false;
1068 boolean more = true;
1070 if (updateSequenceNumber == 0)
1073 status.message.emit(tr("Downloading 0% complete."));
1075 while(more && keepRunning) {
1077 // if (authRefreshNeeded)
1078 // if (!refreshConnection())
1081 int sequence = updateSequenceNumber;
1083 // conn.beginTransaction();
1084 logger.log(logger.EXTREME, "Getting chunk from Evernote");
1085 chunk = noteStore.getSyncChunk(authToken, sequence, chunkSize, fullSync);
1086 logger.log(logger.LOW, "Chunk High Sequence: " +chunk.getChunkHighUSN());
1087 } catch (EDAMUserException e) {
1089 e.printStackTrace();
1090 status.message.emit(e.getMessage());
1091 } catch (EDAMSystemException e) {
1092 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1093 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1096 e.printStackTrace();
1097 status.message.emit(e.getMessage());
1098 } catch (TException e) {
1100 e.printStackTrace();
1101 status.message.emit(e.getMessage());
1103 if (error || chunk == null)
1108 syncRemoteTags(chunk.getTags());
1109 syncRemoteSavedSearches(chunk.getSearches());
1110 syncRemoteNotebooks(chunk.getNotebooks());
1111 syncRemoteNotes(noteStore, chunk.getNotes(), fullSync, authToken);
1112 syncRemoteResources(noteStore, chunk.getResources());
1113 syncRemoteLinkedNotebooks(chunk.getLinkedNotebooks());
1115 // Signal about any updated notes to invalidate the cache
1116 for (int i=0; i<chunk.getNotesSize(); i++)
1117 noteSignal.noteChanged.emit(chunk.getNotes().get(i).getGuid(), null);
1118 syncExpungedNotes(chunk);
1121 // Check for more notes
1122 if (chunk.getChunkHighUSN() <= updateSequenceNumber)
1126 logger.log(logger.EXTREME, "More notes? " +more);
1129 // Save the chunk sequence number
1130 if (!error && chunk.getChunkHighUSN() > 0 && keepRunning) {
1131 logger.log(logger.EXTREME, "emitting sequence number to main thread");
1132 updateSequenceNumber = chunk.getChunkHighUSN();
1133 conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());
1134 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
1135 // conn.commitTransaction();
1140 long pct = chunk.getChunkHighUSN() * 100;
1141 conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());
1142 pct = pct/evernoteUpdateCount;
1143 status.message.emit(tr("Downloading ") +new Long(pct).toString()+tr("% complete."));
1145 // conn.commitTransaction();
1147 logger.log(logger.HIGH, "Leaving SyncRunner.syncRemoteToLocal");
1149 // Sync expunged notes
1150 private void syncExpungedNotes(SyncChunk chunk) {
1151 // Do the local deletes
1152 logger.log(logger.EXTREME, "Doing local deletes");
1153 List<String> guid = chunk.getExpungedNotes();
1155 for (int i=0; i<guid.size() && keepRunning; i++) {
1156 String notebookGuid = "";
1157 Note localNote = conn.getNoteTable().getNote(guid.get(i), false, false, false, false, false);
1158 if (localNote != null) {
1159 conn.getNoteTable().updateNoteSequence(guid.get(i), 0);
1160 notebookGuid = localNote.getNotebookGuid();
1162 // If the note is in a local notebook (which means we moved it) or if the
1163 // note returned is null (which means it is already deleted or flagged expunged)
1165 if (!conn.getNotebookTable().isNotebookLocal(notebookGuid) || localNote == null) {
1166 logger.log(logger.EXTREME, "Expunging local note from database");
1167 conn.getNoteTable().expungeNote(guid.get(i), true, false);
1171 guid = chunk.getExpungedNotebooks();
1173 for (int i=0; i<guid.size() && keepRunning; i++) {
1174 logger.log(logger.EXTREME, "Expunging local notebook from database");
1175 conn.getNotebookTable().expungeNotebook(guid.get(i), false);
1177 guid = chunk.getExpungedTags();
1179 for (int i=0; i<guid.size() && keepRunning; i++) {
1180 logger.log(logger.EXTREME, "Expunging tags from local database");
1181 conn.getTagTable().expungeTag(guid.get(i), false);
1183 guid = chunk.getExpungedSearches();
1185 for (int i=0; i<guid.size() && keepRunning; i++) {
1186 logger.log(logger.EXTREME, "Expunging saved search from local database");
1187 conn.getSavedSearchTable().expungeSavedSearch(guid.get(i), false);
1189 guid = chunk.getExpungedLinkedNotebooks();
1191 for (int i=0; i<guid.size() && keepRunning; i++) {
1192 logger.log(logger.EXTREME, "Expunging linked notebook from local database");
1193 conn.getLinkedNotebookTable().expungeNotebook(guid.get(i), false);
1198 private void syncRemoteTags(List<Tag> tags) {
1199 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
1201 for (int i=0; i<tags.size() && keepRunning; i++) {
1203 oldGuid = conn.getTagTable().findTagByName(tags.get(i).getName());
1204 if (oldGuid != null && !tags.get(i).getGuid().equalsIgnoreCase(oldGuid))
1205 conn.getTagTable().updateTagGuid(oldGuid, tags.get(i).getGuid());
1206 conn.getTagTable().syncTag(tags.get(i), false);
1209 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
1211 // Sync remote saved searches
1212 private void syncRemoteSavedSearches(List<SavedSearch> searches) {
1213 logger.log(logger.EXTREME, "Entering SyncRunner.syncSavedSearches");
1214 if (searches != null) {
1215 for (int i=0; i<searches.size() && keepRunning; i++) {
1217 oldGuid = conn.getSavedSearchTable().findSavedSearchByName(searches.get(i).getName());
1218 if (oldGuid != null && !searches.get(i).getGuid().equalsIgnoreCase(oldGuid))
1219 conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, searches.get(i).getGuid());
1220 conn.getSavedSearchTable().syncSavedSearch(searches.get(i), false);
1223 logger.log(logger.EXTREME, "Leaving SyncRunner.syncSavedSearches");
1225 // Sync remote linked notebooks
1226 private void syncRemoteLinkedNotebooks(List<LinkedNotebook> books) {
1227 logger.log(logger.EXTREME, "Entering SyncRunner.syncLinkedNotebooks");
1228 if (books != null) {
1229 for (int i=0; i<books.size() && keepRunning; i++) {
1230 conn.getLinkedNotebookTable().updateNotebook(books.get(i), false);
1233 logger.log(logger.EXTREME, "Leaving SyncRunner.syncLinkedNotebooks");
1235 // Sync remote Notebooks 2
1236 private void syncRemoteNotebooks(List<Notebook> notebooks) {
1237 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");
1238 if (notebooks != null) {
1239 for (int i=0; i<notebooks.size() && keepRunning; i++) {
1241 oldGuid = conn.getNotebookTable().findNotebookByName(notebooks.get(i).getName());
1242 if (oldGuid != null && !conn.getNotebookTable().isNotebookLocal(oldGuid) && !notebooks.get(i).getGuid().equalsIgnoreCase(oldGuid))
1243 conn.getNotebookTable().updateNotebookGuid(oldGuid, notebooks.get(i).getGuid());
1244 conn.getNotebookTable().syncNotebook(notebooks.get(i), false);
1246 // Synchronize shared notebook information
1247 // if (notebooks.get(i).getSharedNotebookIdsSize() > 0) {
1248 // conn.getSharedNotebookTable().expungeNotebookByGuid(notebooks.get(i).getGuid(), false);
1249 // for (int j=0; j<notebooks.get(i).getSharedNotebookIdsSize(); j++) {
1250 // syncRemoteSharedNotebook(notebooks.get(i).getGuid(), notebooks.get(i).getSharedNotebookIds().get(j), authToken);
1255 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
1257 // Sync remote shared notebook
1258 // private void syncRemoteSharedNotebook(String guid, Long id, String token) {
1259 // List<SharedNotebook> books = noteStore.getSharedNotebookByAuth(authToken);
1261 // Sync remote Resources
1262 private void syncRemoteResources(Client noteStore, List<Resource> resource) {
1263 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteResources");
1264 if (resource != null) {
1265 for (int i=0; i<resource.size() && keepRunning; i++) {
1266 syncRemoteResource(noteStore, resource.get(i), authToken);
1269 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteResources");
1271 // Sync remote resource
1272 private void syncRemoteResource(Client noteStore, Resource resource, String authToken) {
1273 // This is how the logic for this works.
1274 // 1.) If the resource is not in the local database, we add it.
1275 // 2.) If a copy of the resource is in the local database and the note isn't dirty, we update the local copy
1276 // 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
1277 // is a conflict. The note conflict should get a copy of the resource at that time.
1279 Note n = conn.getNoteTable().getNote(resource.getNoteGuid(), false, false, false, false, false);
1281 logger.log(logger.HIGH, "Resource for note " +n.getGuid() +" : " +n.getTitle());
1283 boolean saveNeeded = false;
1284 /* #1 */ Resource r = getEvernoteResource(noteStore, resource.getGuid(), true,true,true, authToken);
1285 Resource l = conn.getNoteTable().noteResourceTable.getNoteResource(r.getGuid(), false);
1287 logger.log(logger.HIGH, "Local resource not found");
1290 /* #2 */ boolean isNoteDirty = conn.getNoteTable().isNoteDirty(r.getNoteGuid());
1292 logger.log(logger.HIGH, "Local resource found, but is not dirty");
1295 /* #3 */ String remoteHash = "";
1296 if (r != null && r.getData() != null && r.getData().getBodyHash() != null)
1297 remoteHash = byteArrayToHexString(r.getData().getBodyHash());
1298 String localHash = "";
1299 if (l != null && l.getData() != null && l.getData().getBodyHash() != null)
1300 remoteHash = byteArrayToHexString(l.getData().getBodyHash());
1302 if (localHash.equalsIgnoreCase(remoteHash))
1307 logger.log(logger.HIGH, "Resource save needed: " +saveNeeded);
1309 conn.getNoteTable().noteResourceTable.updateNoteResource(r, false);
1310 if (r.getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
1311 downloadInkNoteImage(r.getGuid(), authToken);
1315 // Sync remote notes
1316 private void syncRemoteNotes(Client noteStore, List<Note> note, boolean fullSync, String token) {
1320 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotes");
1321 logger.log(logger.LOW, "Local Dirty Notes: ");
1322 logger.log(logger.LOW, "Remote Dirty Notes:");
1323 for (int i=0; i<note.size();i++) {
1324 logger.log(logger.LOW, i +" : " +note.get(i).getGuid() + " : " +note.get(i).getTitle() );
1326 logger.log(logger.LOW, "---");
1328 for (int i=0; i<note.size() && keepRunning; i++) {
1329 Note n = getEvernoteNote(noteStore, note.get(i).getGuid(), true, fullSync, true,true, token);
1330 syncRemoteNote(n, fullSync, token);
1333 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotes");
1335 private void syncRemoteNote(Note n, boolean fullSync, String token) {
1338 // Basically, this is how the sync logic for a note works.
1339 // If the remote note has changed and the local has not, we
1340 // accept the change.
1341 // If both the local & remote have changed but the sequence
1342 // numbers are the same, we don't accept the change. This
1343 // seems to happen when attachments are indexed by the server.
1344 // If both the local & remote have changed and the sequence numbers
1345 // are different we move the local copy to a local notebook (making sure
1346 // to copy all resources) and we accept the new one.
1347 boolean conflictingNote = true;
1348 logger.log(logger.EXTREME, "Checking for duplicate note " +n.getGuid() +" : " +n.getTitle());
1349 if (dirtyNoteGuids != null && dirtyNoteGuids.contains(n.getGuid())) {
1350 logger.log(logger.EXTREME, "Conflict check beginning");
1351 conflictingNote = checkForConflict(n);
1352 logger.log(logger.EXTREME, "Conflict check results " +conflictingNote);
1353 if (conflictingNote)
1354 moveConflictingNote(n.getGuid());
1356 boolean ignoreNote = false;
1357 if (ignoreNotebooks.contains(n.getNotebookGuid()))
1359 for (int i=0; i<n.getTagGuidsSize(); i++) {
1360 if (ignoreTags.contains(n.getTagGuids().get(i))) {
1362 i=n.getTagGuidsSize();
1366 if ((conflictingNote || fullSync) && !ignoreNote) {
1367 logger.log(logger.EXTREME, "Saving Note");
1368 conn.getNoteTable().syncNote(n);
1369 // The following was commented out because it caused a race condition on the database where resources
1370 // may be lost. We do the same thing elsewhere;.
1371 // noteSignal.noteChanged.emit(n.getGuid(), null); // Signal to ivalidate note cache
1372 noteSignal.noteDownloaded.emit(n, true); // Signal to add note to index
1373 logger.log(logger.EXTREME, "Note Saved");
1374 if (fullSync && n.getResources() != null) {
1375 for (int q=0; q<n.getResources().size() && keepRunning; q++) {
1376 logger.log(logger.EXTREME, "Getting note resources.");
1377 conn.getNoteTable().noteResourceTable.updateNoteResource(n.getResources().get(q), false);
1378 if (n.getResources().get(q).getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
1379 downloadInkNoteImage(n.getResources().get(q).getGuid(), token);
1385 private Note getEvernoteNote(Client noteStore, String guid, boolean withContent, boolean withResourceData, boolean withResourceRecognition, boolean withResourceAlternateData, String token) {
1388 logger.log(logger.EXTREME, "Retrieving note " +guid);
1389 n = noteStore.getNote(token, guid, withContent, withResourceData, withResourceRecognition, withResourceAlternateData);
1390 logger.log(logger.EXTREME, "Note " +guid +" has been retrieved.");
1391 } catch (EDAMUserException e) {
1392 logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");
1393 logger.log(logger.LOW, e.toString());
1395 e.printStackTrace();
1396 } catch (EDAMSystemException e) {
1397 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1398 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1400 logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
1401 logger.log(logger.LOW, e.toString());
1403 e.printStackTrace();
1404 } catch (EDAMNotFoundException e) {
1405 logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
1406 logger.log(logger.LOW, e.toString());
1408 e.printStackTrace();
1409 } catch (TException e) {
1410 logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
1411 logger.log(logger.LOW, e.toString());
1413 e.printStackTrace();
1417 private Resource getEvernoteResource(Client noteStore, String guid, boolean withData, boolean withRecognition, boolean withAttributes, String token) {
1420 logger.log(logger.EXTREME, "Retrieving resource " +guid);
1421 n = noteStore.getResource(token, guid, withData, withRecognition, withAttributes, withAttributes);
1422 logger.log(logger.EXTREME, "Resource " +guid +" has been retrieved.");
1423 } catch (EDAMUserException e) {
1424 logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");
1425 logger.log(logger.LOW, e.toString());
1427 e.printStackTrace();
1428 } catch (EDAMSystemException e) {
1429 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1430 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1432 logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
1433 logger.log(logger.LOW, e.toString());
1435 e.printStackTrace();
1436 } catch (EDAMNotFoundException e) {
1437 logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
1438 logger.log(logger.LOW, e.toString());
1440 e.printStackTrace();
1441 } catch (TException e) {
1442 logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
1443 logger.log(logger.LOW, e.toString());
1445 e.printStackTrace();
1451 private boolean checkForConflict(Note n) {
1452 logger.log(logger.EXTREME, "Checking note sequence number " +n.getGuid());
1453 Note oldNote = conn.getNoteTable().getNote(n.getGuid(), false, false, false, false, false);
1454 logger.log(logger.EXTREME, "Local/Remote sequence numbers: " +oldNote.getUpdateSequenceNum()+"/"+n.getUpdateSequenceNum());
1455 logger.log(logger.LOW, "Remote Note Title:" +n.getTitle());
1456 logger.log(logger.LOW, "Local Note Title:" +oldNote.getTitle());
1457 if (oldNote.getUpdateSequenceNum() == n.getUpdateSequenceNum()) {
1460 boolean oldIsDirty = conn.getNoteTable().isNoteDirty(n.getGuid());
1466 private void moveConflictingNote(String guid) {
1467 logger.log(logger.EXTREME, "Conflicting change found for note " +guid);
1468 List<Notebook> books = conn.getNotebookTable().getAllLocal();
1469 String notebookGuid = null;
1470 for (int i=0; i<books.size() && keepRunning; i++) {
1471 if (books.get(i).getName().equalsIgnoreCase("Conflicting Changes (local)") ||
1472 books.get(i).getName().equalsIgnoreCase("Conflicting Changes")) {
1473 notebookGuid = books.get(i).getGuid();
1478 if (notebookGuid == null) {
1479 logger.log(logger.EXTREME, "Building conflicting change notebook " +guid);
1480 Calendar currentTime = new GregorianCalendar();
1481 Long l = new Long(currentTime.getTimeInMillis());
1483 while (prevTime==l) {
1484 currentTime = new GregorianCalendar();
1485 l=currentTime.getTimeInMillis();
1487 String randint = new String(Long.toString(l));
1489 Notebook newBook = new Notebook();
1490 newBook.setUpdateSequenceNum(0);
1491 newBook.setGuid(randint);
1492 newBook.setName("Conflicting Changes");
1493 newBook.setServiceCreated(new Date().getTime());
1494 newBook.setServiceUpdated(new Date().getTime());
1495 newBook.setDefaultNotebook(false);
1496 newBook.setPublished(false);
1498 conn.getNotebookTable().addNotebook(newBook, false, true);
1499 notebookSignal.listChanged.emit();
1500 notebookGuid = newBook.getGuid();
1501 refreshNeeded = true;
1504 // Now that we have a good notebook guid, we need to move the conflicting note
1505 // to the local notebook
1506 logger.log(logger.EXTREME, "Moving conflicting note " +guid);
1507 Calendar currentTime = new GregorianCalendar();
1508 Long l = new Long(currentTime.getTimeInMillis());
1510 while (prevTime==l) {
1511 currentTime = new GregorianCalendar();
1512 l = currentTime.getTimeInMillis();
1514 String newGuid = new String(Long.toString(l));
1516 Note oldNote = conn.getNoteTable().getNote(guid, true, true, false, false, false);
1517 for (int i=0; i<oldNote.getResources().size() && keepRunning; i++) {
1518 l = new Long(currentTime.getTimeInMillis());
1519 String newResG = new String(Long.toString(l));
1520 String oldResG = oldNote.getResources().get(i).getGuid();
1521 conn.getNoteTable().noteResourceTable.resetUpdateSequenceNumber(oldResG, true);
1522 conn.getNoteTable().noteResourceTable.updateNoteResourceGuid(oldResG, newResG, true);
1525 conn.getNoteTable().resetNoteSequence(guid);
1526 conn.getNoteTable().updateNoteGuid(guid, newGuid);
1527 conn.getNoteTable().updateNoteNotebook(newGuid, notebookGuid, true);
1529 noteSignal.notebookChanged.emit(newGuid, notebookGuid);
1530 refreshNeeded = true;
1531 noteSignal.guidChanged.emit(guid,newGuid);
1537 //******************************************************
1538 //******************************************************
1539 //** Utility Functions
1540 //******************************************************
1541 //******************************************************
1542 // Convert a byte array to a hex string
1543 private static String byteArrayToHexString(byte data[]) {
1544 StringBuffer buf = new StringBuffer();
1545 for (byte element : data) {
1546 int halfbyte = (element >>> 4) & 0x0F;
1549 if ((0 <= halfbyte) && (halfbyte <= 9))
1550 buf.append((char) ('0' + halfbyte));
1552 buf.append((char) ('a' + (halfbyte - 10)));
1553 halfbyte = element & 0x0F;
1554 } while(two_halfs++ < 1);
1556 return buf.toString();
1561 //*******************************************************
1562 //* Find dirty tags, which do not have newly created parents
1563 //*******************************************************
1564 private Tag findNextTag() {
1565 logger.log(logger.HIGH, "Entering SyncRunner.findNextTag");
1567 List<Tag> tags = conn.getTagTable().getDirty();
1569 // Find the parent. If the parent has a sequence > 0 then it is a good
1571 for (int i=0; i<tags.size() && keepRunning; i++) {
1572 if (!badTagSync.containsKey(tags.get(i).getGuid())) {
1573 if (tags.get(i).getParentGuid() == null) {
1574 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found without parent");
1577 Tag parentTag = conn.getTagTable().getTag(tags.get(i).getParentGuid());
1578 if (parentTag.getUpdateSequenceNum() > 0) {
1579 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found");
1585 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - no tags returned");
1590 // Connect to Evernote
1591 public boolean enConnect() {
1593 userStoreTrans = new THttpClient(userStoreUrl);
1594 userStoreTrans.setCustomHeader("User-Agent", userAgent);
1595 } catch (TTransportException e) {
1596 QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1598 e.printStackTrace();
1600 userStoreProt = new TBinaryProtocol(userStoreTrans);
1601 userStore = new UserStore.Client(userStoreProt, userStoreProt);
1603 syncSignal.saveUserStore.emit(userStore);
1605 //authResult = userStore.authenticate(username, password, consumerKey, consumerSecret);
1606 user = userStore.getUser(authToken);
1607 } catch (EDAMUserException e) {
1608 QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Error", "Invalid Authorization");
1610 isConnected = false;
1612 } catch (EDAMSystemException e) {
1613 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1614 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1616 QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "EDAM System Excepton", e.getLocalizedMessage());
1618 e.printStackTrace();
1619 isConnected = false;
1620 } catch (TException e) {
1621 QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1623 e.printStackTrace();
1624 isConnected = false;
1627 boolean versionOk = false;
1629 versionOk = userStore.checkVersion("NeighborNote",
1630 com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR,
1631 com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR);
1632 } catch (TException e) {
1633 e.printStackTrace();
1634 isConnected = false;
1637 System.err.println("Incomatible EDAM client protocol version");
1638 isConnected = false;
1640 //if (authResult != null) {
1641 //user = authResult.getUser();
1642 //authToken = authResult.getAuthenticationToken();
1643 if (user == null || noteStoreUrlBase == null) {
1644 logger.log(logger.LOW, "Error retrieving user information. Aborting.");
1645 System.err.println("Error retrieving user information.");
1646 isConnected = false;
1647 QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, tr("Connection Error"), tr("Error retrieving user information. Synchronization not complete"));
1652 noteStoreUrl = noteStoreUrlBase + user.getShardId();
1653 syncSignal.saveAuthToken.emit(authToken);
1654 syncSignal.saveNoteStore.emit(localNoteStore);
1658 noteStoreTrans = new THttpClient(noteStoreUrl);
1659 noteStoreTrans.setCustomHeader("User-Agent", userAgent);
1660 } catch (TTransportException e) {
1661 QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1663 e.printStackTrace();
1664 isConnected = false;
1666 noteStoreProt = new TBinaryProtocol(noteStoreTrans);
1668 new NoteStore.Client(noteStoreProt, noteStoreProt);
1670 //authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
1671 //authRefreshTime = authTimeRemaining / 2;
1674 // Get user information
1676 User user = userStore.getUser(authToken);
1677 syncSignal.saveUserInformation.emit(user);
1678 } catch (EDAMUserException e1) {
1679 e1.printStackTrace();
1680 } catch (EDAMSystemException e1) {
1681 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1682 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1684 e1.printStackTrace();
1685 } catch (TException e1) {
1686 e1.printStackTrace();
1691 // Disconnect from the database
1692 public void enDisconnect() {
1693 isConnected = false;
1697 // Refresh the connection
1698 private synchronized boolean refreshConnection() {
1700 logger.log(logger.EXTREME, "Entering SyncRunner.refreshConnection()");
1701 // Calendar cal = Calendar.getInstance();
1703 // If we are not connected let's get out of here
1707 // If we fail too many times, then let's give up.
1708 if (failedRefreshes >=5) {
1709 logger.log(logger.EXTREME, "Refresh attempts have failed. Disconnecting.");
1710 isConnected = false;
1711 status.message.emit(tr("Unable to synchronize - Authentication failed"));
1715 // If this is the first time through, then we need to set this
1716 // if (authRefreshTime == 0 || cal.getTimeInMillis() > authRefreshTime)
1717 // authRefreshTime = cal.getTimeInMillis();
1719 // // Default to checking again in 5 min. This in case we fail.
1720 // authRefreshTime = authRefreshTime +(5*60*1000);
1722 // Try to get a new token
1723 AuthenticationResult newAuth = null;
1724 logger.log(logger.EXTREME, "Beginning to try authentication refresh");
1726 if (userStore != null && authToken != null)
1727 newAuth = userStore.refreshAuthentication(authToken);
1730 logger.log(logger.EXTREME, "UserStore.refreshAuthentication has succeeded.");
1731 } catch (EDAMUserException e) {
1732 e.printStackTrace();
1733 syncSignal.authRefreshComplete.emit(false);
1736 } catch (EDAMSystemException e) {
1737 e.printStackTrace();
1738 syncSignal.authRefreshComplete.emit(false);
1741 } catch (TException e) {
1742 e.printStackTrace();
1743 syncSignal.authRefreshComplete.emit(false);
1748 // If we didn't get a good auth, then we've failed
1749 if (newAuth == null) {
1751 status.message.emit(tr("Unable to synchronize - Authentication failed"));
1752 logger.log(logger.EXTREME, "Authentication failure #" +failedRefreshes);
1753 status.message.emit(tr("Unable to synchronize - Authentication failed"));
1754 syncSignal.authRefreshComplete.emit(false);
1758 // We got a good token. Now we need to setup the time to renew it.
1759 logger.log(logger.EXTREME, "Saving authentication tokens");
1760 authResult = newAuth;
1761 authToken = new String(newAuth.getAuthenticationToken());
1762 // authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
1763 // authRefreshTime = cal.getTimeInMillis() + (authTimeRemaining/4);
1765 syncSignal.authRefreshComplete.emit(true);
1766 authRefreshNeeded = false;
1768 // This should never happen, but if it does we consider this a faild attempt.
1769 // if (authTimeRemaining <= 0) {
1770 // failedRefreshes++;
1771 // syncSignal.authRefreshComplete.emit(false);
1779 public synchronized boolean addWork(String request) {
1780 if (workQueue.offer(request))
1785 private Note getNoteContent(Note n) {
1786 QTextCodec codec = QTextCodec.codecForLocale();
1787 codec = QTextCodec.codecForName("UTF-8");
1788 n.setContent(codec.toUnicode(new QByteArray(n.getContent())));
1794 //*********************************************************
1795 //* Special download instructions. Used for DB upgrades
1796 //*********************************************************
1797 private void downloadAllSharedNotebooks(Client noteStore) {
1799 List<SharedNotebook> books = noteStore.listSharedNotebooks(authToken);
1800 logger.log(logger.LOW, "Shared notebooks found = " +books.size());
1801 for (int i=0; i<books.size(); i++) {
1802 conn.getSharedNotebookTable().updateNotebook(books.get(i), false);
1804 conn.getSyncTable().deleteRecord("FullSharedNotebookSync");
1805 } catch (EDAMUserException e1) {
1806 e1.printStackTrace();
1807 status.message.emit(tr("User exception Listing shared notebooks."));
1808 logger.log(logger.LOW, e1.getMessage());
1810 } catch (EDAMSystemException e1) {
1811 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1812 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1814 e1.printStackTrace();
1815 status.message.emit(tr("System exception Listing shared notebooks."));
1816 logger.log(logger.LOW, e1.getMessage());
1818 } catch (TException e1) {
1819 e1.printStackTrace();
1820 status.message.emit(tr("Transaction exception Listing shared notebooks."));
1821 logger.log(logger.LOW, e1.getMessage());
1823 } catch (EDAMNotFoundException e1) {
1824 e1.printStackTrace();
1825 status.message.emit(tr("EDAM Not Found exception Listing shared notebooks."));
1826 logger.log(logger.LOW, e1.getMessage());
1829 private void downloadAllNotebooks(Client noteStore) {
1831 List<Notebook> books = noteStore.listNotebooks(authToken);
1832 logger.log(logger.LOW, "Shared notebooks found = " +books.size());
1833 for (int i=0; i<books.size(); i++) {
1834 conn.getNotebookTable().updateNotebook(books.get(i), false);
1836 conn.getSyncTable().deleteRecord("FullNotebookSync");
1837 } catch (EDAMUserException e1) {
1838 e1.printStackTrace();
1839 status.message.emit(tr("User exception Listing notebooks."));
1840 logger.log(logger.LOW, e1.getMessage());
1842 } catch (EDAMSystemException e1) {
1843 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1844 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1846 e1.printStackTrace();
1847 status.message.emit(tr("System exception Listing notebooks."));
1848 logger.log(logger.LOW, e1.getMessage());
1850 } catch (TException e1) {
1851 e1.printStackTrace();
1852 status.message.emit(tr("Transaction exception Listing notebooks."));
1853 logger.log(logger.LOW, e1.getMessage());
1857 private void downloadAllLinkedNotebooks(Client noteStore) {
1859 List<LinkedNotebook> books = noteStore.listLinkedNotebooks(authToken);
1860 logger.log(logger.LOW, "Linked notebooks found = " +books.size());
1861 for (int i=0; i<books.size(); i++) {
1862 conn.getLinkedNotebookTable().updateNotebook(books.get(i), false);
1864 conn.getSyncTable().deleteRecord("FullLinkedNotebookSync");
1865 } catch (EDAMUserException e1) {
1866 e1.printStackTrace();
1867 status.message.emit(tr("User exception Listing linked notebooks."));
1868 logger.log(logger.LOW, e1.getMessage());
1870 } catch (EDAMSystemException e1) {
1871 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1872 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1874 e1.printStackTrace();
1875 status.message.emit(tr("System exception Listing linked notebooks."));
1876 logger.log(logger.LOW, e1.getMessage());
1878 } catch (TException e1) {
1879 e1.printStackTrace();
1880 status.message.emit(tr("Transaction exception Listing lineked notebooks."));
1881 logger.log(logger.LOW, e1.getMessage());
1883 } catch (EDAMNotFoundException e1) {
1884 e1.printStackTrace();
1885 status.message.emit(tr("EDAM Not Found exception Listing linked notebooks."));
1886 logger.log(logger.LOW, e1.getMessage());
1891 private void downloadInkNoteImage(String guid, String authToken) {
1892 String urlBase = noteStoreUrl.replace("/edam/note/", "/shard/") + "/res/"+guid+".ink?slice=";
1893 // urlBase = "https://www.evernote.com/shard/s1/res/52b567a9-54ae-4a08-afc5-d5bae275b2a8.ink?slice=";
1895 Resource r = conn.getNoteTable().noteResourceTable.getNoteResource(guid, false);
1896 conn.getInkImagesTable().expungeImage(r.getGuid());
1897 int sliceCount = 1+((r.getHeight()-1)/480);
1898 HttpClient http = new DefaultHttpClient();
1899 for (int i=0; i<sliceCount; i++) {
1900 String url = urlBase + slice.toString();
1901 HttpPost post = new HttpPost(url);
1902 post.getParams().setParameter("auth", authToken);
1903 List <NameValuePair> nvps = new ArrayList <NameValuePair>();
1904 nvps.add(new BasicNameValuePair("auth", authToken));
1907 post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
1908 } catch (UnsupportedEncodingException e1) {
1909 e1.printStackTrace();
1912 HttpResponse response = http.execute(post);
1913 HttpEntity resEntity = response.getEntity();
1914 InputStream is = resEntity.getContent();
1915 QByteArray data = writeToFile(is);
1916 conn.getInkImagesTable().saveImage(guid, slice, data);
1917 } catch (ClientProtocolException e) {
1918 e.printStackTrace();
1919 } catch (IOException e) {
1920 e.printStackTrace();
1925 http.getConnectionManager().shutdown();
1926 noteSignal.noteChanged.emit(r.getNoteGuid(), null); // Signal to ivalidate note cache
1930 public QByteArray writeToFile(InputStream iStream) throws IOException {
1932 File temp = File.createTempFile("nn-inknote-temp", ".png");
1934 // Save InputStream to the file.
1935 BufferedOutputStream fOut = null;
1937 fOut = new BufferedOutputStream(new FileOutputStream(temp));
1938 byte[] buffer = new byte[32 * 1024];
1940 while ((bytesRead = iStream.read(buffer)) != -1) {
1941 fOut.write(buffer, 0, bytesRead);
1948 QFile tempFile = new QFile(temp.getAbsoluteFile().toString());
1949 tempFile.open(OpenModeFlag.ReadOnly);
1950 QByteArray data = tempFile.readAll();
1957 //******************************************
1958 //* Begin syncing shared notebooks
1959 //******************************************
1960 private void syncLinkedNotebooks() {
1961 logger.log(logger.MEDIUM, "Authenticating linked Notebooks");
1962 status.message.emit(tr("Synchronizing shared notebooks."));
1963 List<LinkedNotebook> books = conn.getLinkedNotebookTable().getAll();
1965 errorSharedNotebooks.clear();
1967 for (int i=0; i<books.size(); i++) {
1968 if (errorSharedNotebooksIgnored.containsKey(books.get(i).getGuid()))
1971 logger.log(logger.EXTREME, "Checking notebook: " +books.get(i).getShareName());
1972 long lastSyncDate = conn.getLinkedNotebookTable().getLastSequenceDate(books.get(i).getGuid());
1973 int lastSequenceNumber = conn.getLinkedNotebookTable().getLastSequenceNumber(books.get(i).getGuid());
1975 logger.log(logger.EXTREME, "Last Sequence Number on file: " +lastSequenceNumber);
1977 // Authenticate to the owner's shard
1978 String linkedNoteStoreUrl = noteStoreUrlBase + books.get(i).getShardId();
1979 logger.log(logger.EXTREME, "linkedNoteStoreURL: " +linkedNoteStoreUrl);
1980 THttpClient linkedNoteStoreTrans = new THttpClient(linkedNoteStoreUrl);
1981 TBinaryProtocol linkedNoteStoreProt = new TBinaryProtocol(linkedNoteStoreTrans);
1982 Client linkedNoteStore = new NoteStore.Client(linkedNoteStoreProt, linkedNoteStoreProt);
1984 linkedAuthResult = null;
1985 if (books.get(i).getShareKey() != null) {
1986 logger.log(logger.EXTREME, "Share Key Not Null: " +books.get(i).getShareKey());
1987 linkedAuthResult = linkedNoteStore.authenticateToSharedNotebook(books.get(i).getShareKey(), authToken);
1988 logger.log(logger.EXTREME, "Authentication Token" +linkedAuthResult.getAuthenticationToken());
1990 logger.log(logger.EXTREME, "Share key is null");
1991 linkedAuthResult = new AuthenticationResult();
1992 linkedAuthResult.setAuthenticationToken("");
1994 SyncState linkedSyncState =
1995 linkedNoteStore.getLinkedNotebookSyncState(linkedAuthResult.getAuthenticationToken(), books.get(i));
1996 if (linkedSyncState.getUpdateCount() > lastSequenceNumber) {
1997 logger.log(logger.EXTREME, "Remote changes found");
1998 if (lastSyncDate < linkedSyncState.getFullSyncBefore()) {
1999 lastSequenceNumber = 0;
2001 logger.log(logger.EXTREME, "Calling syncLinkedNotebook for " +books.get(i).getShareName());
2002 syncLinkedNotebook(linkedNoteStore, books.get(i),
2003 lastSequenceNumber, linkedSyncState.getUpdateCount(), authToken);
2006 // Synchronize local changes
2007 syncLocalLinkedNoteChanges(linkedNoteStore, books.get(i));
2009 } catch (EDAMUserException e) {
2010 e.printStackTrace();
2011 } catch (EDAMNotFoundException e) {
2012 status.message.emit(tr("Error synchronizing \" " +
2013 books.get(i).getShareName()+"\". Please verify you still have access to that shared notebook."));
2014 errorSharedNotebooks.add(books.get(i).getGuid());
2015 errorSharedNotebooksIgnored.put(books.get(i).getGuid(), books.get(i).getGuid());
2016 logger.log(logger.LOW, "Error synchronizing shared notebook. EDAMNotFound: "+e.getMessage());
2017 logger.log(logger.LOW, e.getStackTrace());
2019 e.printStackTrace();
2020 } catch (EDAMSystemException e) {
2021 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
2022 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
2025 logger.log(logger.LOW, "System error authenticating against shared notebook. "+
2026 "Key: "+books.get(i).getShareKey() +" Error:" +e.getMessage());
2027 e.printStackTrace();
2028 } catch (TException e) {
2030 e.printStackTrace();
2035 conn.getTagTable().removeUnusedLinkedTags();
2036 conn.getTagTable().cleanupTags();
2037 tagSignal.listChanged.emit();
2042 //**************************************************************
2043 //* Linked notebook contents (from someone else's account)
2044 //*************************************************************
2045 private void syncLinkedNotebook(Client linkedNoteStore, LinkedNotebook book, int usn, int highSequence, String token) {
2046 logger.log(logger.EXTREME, "Entering syncLinkedNotebook");
2047 if (ignoreLinkedNotebooks.contains(book.getGuid()))
2049 List<Note> dirtyNotes = conn.getNoteTable().getDirtyLinkedNotes();
2050 if (dirtyNoteGuids == null)
2051 dirtyNoteGuids = new ArrayList<String>();
2053 for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
2054 dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
2056 boolean fullSync = false;
2059 boolean syncError = false;
2060 while (usn < highSequence && !syncError) {
2061 refreshNeeded = true;
2064 linkedNoteStore.getLinkedNotebookSyncChunk(token, book, usn, 10, fullSync);
2067 syncExpungedNotes(chunk);
2069 logger.log(logger.EXTREME, "Syncing remote notes: " +chunk.getNotesSize());
2070 syncRemoteNotes(linkedNoteStore, chunk.getNotes(), fullSync, linkedAuthResult.getAuthenticationToken());
2071 logger.log(logger.EXTREME, "Finding new linked tags");
2072 findNewLinkedTags(linkedNoteStore, chunk.getNotes(), linkedAuthResult.getAuthenticationToken());
2074 logger.log(logger.EXTREME, "Synchronizing tags: " +chunk.getTagsSize());
2075 for (int i=0; i<chunk.getResourcesSize(); i++) {
2076 syncRemoteResource(linkedNoteStore, chunk.getResources().get(i), linkedAuthResult.getAuthenticationToken());
2078 logger.log(logger.EXTREME, "Synchronizing linked notebooks: " +chunk.getNotebooksSize());
2079 syncRemoteLinkedNotebooks(linkedNoteStore, chunk.getNotebooks(), false, book);
2080 syncLinkedTags(chunk.getTags(), book.getGuid());
2082 // Go through & signal any notes that have changed so we can refresh the user's view
2083 for (int i=0; i<chunk.getNotesSize(); i++)
2084 noteSignal.noteChanged.emit(chunk.getNotes().get(i).getGuid(), null);
2086 // Expunge Notebook records
2087 logger.log(logger.EXTREME, "Expunging linked notebooks: " +chunk.getExpungedLinkedNotebooksSize());
2088 for (int i=0; i<chunk.getExpungedLinkedNotebooksSize(); i++) {
2089 conn.getLinkedNotebookTable().expungeNotebook(chunk.getExpungedLinkedNotebooks().get(i), false);
2091 usn = chunk.getChunkHighUSN();
2092 conn.getLinkedNotebookTable().setLastSequenceDate(book.getGuid(),chunk.getCurrentTime());
2093 conn.getLinkedNotebookTable().setLastSequenceNumber(book.getGuid(),chunk.getChunkHighUSN());
2094 } catch (EDAMUserException e) {
2096 status.message.emit(tr("EDAM UserException synchronizing linked notbook. See the log for datails."));
2097 e.printStackTrace();
2098 logger.log(logger.LOW, tr("EDAM UserException synchronizing linked notbook ")+ e.getMessage());
2099 } catch (EDAMSystemException e) {
2100 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
2101 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
2104 status.message.emit(tr("EDAM SystemException synchronizing linked notbook. See the log for datails."));
2105 e.printStackTrace();
2106 logger.log(logger.LOW, tr("EDAM SystemException synchronizing linked notbook. See the log for datails") +e.getMessage());
2107 } catch (EDAMNotFoundException e) {
2109 status.message.emit(tr("Notebook URL not found. Removing notobook ") +book.getShareName());
2110 conn.getNotebookTable().deleteLinkedTags(book.getGuid());
2111 conn.getLinkedNotebookTable().expungeNotebook(book.getGuid(), false);
2112 logger.log(logger.LOW, tr("Notebook URL not found. Removing notobook ") +e.getMessage());
2113 } catch (TException e) {
2115 status.message.emit(tr("EDAM TException synchronizing linked notbook. See the log for datails."));
2116 e.printStackTrace();
2117 logger.log(logger.LOW, tr("EDAM TException synchronizing linked notbook. See the log for datails." )+e.getMessage());
2120 logger.log(logger.EXTREME, "leaving syncLinkedNotebook");
2123 private void syncLinkedTags(List<Tag> tags, String notebookGuid) {
2124 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
2126 for (int i=0; i<tags.size() && keepRunning; i++) {
2127 conn.getTagTable().syncLinkedTag(tags.get(i), notebookGuid, false);
2130 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
2133 // Sync notebooks from a linked notebook
2134 private void syncRemoteLinkedNotebooks(Client noteStore, List<Notebook> notebooks, boolean readOnly, LinkedNotebook linked) {
2135 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");
2136 if (notebooks != null) {
2137 for (int i=0; i<notebooks.size() && keepRunning; i++) {
2139 logger.log(logger.EXTREME, "auth token:" +linkedAuthResult.getAuthenticationToken());
2140 if (!linkedAuthResult.getAuthenticationToken().equals("")) {
2141 SharedNotebook s = noteStore.getSharedNotebookByAuth(linkedAuthResult.getAuthenticationToken());
2142 logger.log(logger.EXTREME, "share key:"+s.getShareKey() +" notebookGuid" +s.getNotebookGuid());
2143 conn.getLinkedNotebookTable().setNotebookGuid(s.getShareKey(), s.getNotebookGuid());
2144 readOnly = !s.isNotebookModifiable();
2148 notebooks.get(i).setName(linked.getShareName());
2149 notebooks.get(i).setDefaultNotebook(false);
2150 conn.getNotebookTable().syncLinkedNotebook(notebooks.get(i), false, readOnly);
2151 } catch (EDAMUserException e) {
2153 e.printStackTrace();
2154 } catch (EDAMNotFoundException e) {
2156 e.printStackTrace();
2157 } catch (EDAMSystemException e) {
2158 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
2159 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
2162 e.printStackTrace();
2163 } catch (TException e) {
2165 e.printStackTrace();
2170 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
2173 private void findNewLinkedTags(Client noteStore, List<Note> newNotes, String token) {
2174 if (newNotes == null)
2176 for (int i=0; i<newNotes.size(); i++) {
2177 Note n = newNotes.get(i);
2178 for (int j=0; j<n.getTagGuidsSize(); j++) {
2179 String tag = n.getTagGuids().get(j);
2180 if (!conn.getTagTable().exists(tag)) {
2183 newTag = noteStore.getTag(token, tag);
2184 conn.getTagTable().addTag(newTag, false);
2185 } catch (EDAMUserException e) {
2186 e.printStackTrace();
2187 } catch (EDAMSystemException e) {
2188 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
2189 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
2191 e.printStackTrace();
2192 } catch (EDAMNotFoundException e) {
2193 e.printStackTrace();
2194 } catch (TException e) {
2195 e.printStackTrace();
2203 // Synchronize changes locally done to linked notes
2204 private void syncLocalLinkedNoteChanges(Client noteStore, LinkedNotebook book) {
2205 logger.log(logger.EXTREME, "Entering SyncRunner.synclocalLinkedNoteChanges");
2206 String notebookGuid = conn.getLinkedNotebookTable().getNotebookGuid(book.getGuid());
2207 logger.log(logger.EXTREME, "Finding changes for " +book.getShareName() +":" +book.getGuid() + ":" +notebookGuid);
2208 List<Note> notes = conn.getNoteTable().getDirtyLinked(notebookGuid);
2209 logger.log(logger.EXTREME, "Number of changes found: " +notes.size());
2210 for (int i=0; i<notes.size(); i++) {
2211 logger.log(logger.EXTREME, "Calling syncLocalNote with key " +linkedAuthResult.getAuthenticationToken());
2212 syncLocalNote(noteStore, notes.get(i), linkedAuthResult.getAuthenticationToken());
2214 logger.log(logger.EXTREME, "Leaving SyncRunner.synclocalLinkedNoteChanges");