OSDN Git Service

Merge branch 'master' of http://git.code.sf.net/p/nevernote/code into develop
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / threads / SyncRunner.java
1 /*
2  * This file is part of NixNote/NeighborNote 
3  * Copyright 2009 Randy Baumgarte
4  * Copyright 2013 Yuki Takahashi
5  * 
6  * This file may be licensed under the terms of of the
7  * GNU General Public License Version 2 (the ``GPL'').
8  *
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.
13  *
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.
18  *
19 */
20 package cx.fbn.nevernote.threads;
21
22 import java.io.BufferedOutputStream;
23 import java.io.File;
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;
37
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;
48
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;
78
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;
91
92
93 public class SyncRunner extends QObject implements Runnable {
94         
95         private final ApplicationLogger logger;
96         private DatabaseConnection              conn;
97         private boolean                                 idle;
98         public boolean                                  error;
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") +";";
108         
109         public volatile NoteStore.Client                localNoteStore;
110         private UserStore.Client                                userStore;
111         
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;
126         
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;
135     private User user; 
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;
147         String dbuid;
148         String dburl;
149         String indexUrl;
150         String resourceUrl;
151         String behaviorUrl;
152         
153         String dbpswd;
154         String dbcpswd;
155         private final TreeSet<String> ignoreTags;
156         private final TreeSet<String> ignoreNotebooks;
157         private final TreeSet<String> ignoreLinkedNotebooks;
158         private HashMap<String,String> badTagSync;
159
160         
161         public SyncRunner(String logname, String u, String i, String r, String b, String uid, String pswd, String cpswd) {
162                 logger = new ApplicationLogger(logname);
163                 
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();
174                 resourceUrl = r;
175                 indexUrl = i;
176                 behaviorUrl = b;
177                 
178                 dbuid = uid;
179                 dburl = u;
180                 dbpswd = pswd;
181                 dbcpswd = cpswd;
182 //              this.setAutoDelete(false);
183                 
184                 isConnected = false;
185                 syncNeeded = false;
186                 authRefreshNeeded = false;
187                 keepRunning = true;
188                 idle = true;
189                 disableUploads = false;
190                 ignoreTags = new TreeSet<String>();
191                 ignoreNotebooks = new TreeSet<String>();
192                 ignoreLinkedNotebooks = new TreeSet<String>();
193                 
194 //              setAutoDelete(false);
195                 workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);
196         }
197         @Override
198         public void run() {
199                 errorSharedNotebooks = new ArrayList<String>();
200                 errorSharedNotebooksIgnored = new HashMap<String,String>();
201                 try {
202                         logger.log(logger.EXTREME, "Starting thread");
203                         conn = new DatabaseConnection(logger, dburl, indexUrl, resourceUrl, behaviorUrl, dbuid, dbpswd, dbcpswd, 200);
204                         while(keepRunning) {
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")) {
210                                         idle=false;
211                                         return;
212                                 }
213                                 conn.getNoteTable().dumpDirtyNotes();  // Debugging statement
214                                 idle=false;
215                                 error=false;
216                                 if (syncNeeded) {
217                                         logger.log(logger.EXTREME, "SyncNeeded is true");
218                                         refreshNeeded=false;
219                                         sequenceDate = conn.getSyncTable().getLastSequenceDate();
220                                         updateSequenceNumber = conn.getSyncTable().getUpdateSequenceNumber();
221                                         try {
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());
227                                         }
228                                 }
229                                 idle=true;
230                                 logger.log(logger.EXTREME, "Signaling refresh finished.  refreshNeeded=" +refreshNeeded);
231                                 syncSignal.finished.emit(refreshNeeded);
232                                 if (error) {
233                                         syncSignal.errorDisconnect.emit();
234                                         status.message.emit(tr("Error synchronizing - see log for details."));
235                                 }
236                                 logger.log(logger.LOW, "Dirty Notes After Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
237                                 conn.getNoteTable().dumpDirtyNotes();
238                                 logger.log(logger.LOW, "---");
239                         }
240                 }       
241                 catch (InterruptedException e1) {
242                         e1.printStackTrace();
243                 }
244                 conn.dbShutdown();
245         }
246
247         
248         public DatabaseConnection getConnection() {
249                 return conn;
250         }
251
252         public boolean isIdle() {
253                 return idle;
254         }
255
256
257         public void setConnected(boolean c) {
258                 isConnected = c;
259         }
260         public void setKeepRunning(boolean r) {
261                 logger.log(logger.EXTREME, "Setting keepRunning=" +r);
262                 keepRunning = r;
263         }
264         public void setNoteStore(NoteStore.Client c) {
265                 logger.log(logger.EXTREME, "Setting NoteStore in sync thread");
266                 localNoteStore = c;
267         }
268         public void setUserStore(UserStore.Client c) {
269                 logger.log(logger.EXTREME, "Setting UserStore in sync thread");
270                 userStore = c;
271         }
272
273         public void setEvernoteUpdateCount(long s) {
274                 logger.log(logger.EXTREME, "Setting Update Count in sync thread");
275                 evernoteUpdateCount = s;
276         }
277         
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");
287                 
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));
293                 
294                 ignore.clear();
295                 ignore = conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK");
296                 for (int i=0; i<ignore.size(); i++) 
297                         ignoreLinkedNotebooks.add(ignore.get(i));
298                 
299                 ignoreTags.clear();
300                 ignore = conn.getSyncTable().getIgnoreRecords("TAG");
301                 for (int i=0; i<ignore.size(); i++) 
302                         ignoreTags.add(ignore.get(i));
303
304                 // Make sure we are connected & should keep running
305                 if (isConnected && keepRunning) {
306                         error = false;
307                         logger.log(logger.EXTREME, "Synchronizing with Evernote");
308                         status.message.emit(tr("Synchronizing with Evernote"));
309                         
310                         // Get user information
311                         try {
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();
320                                 error = true;
321                                 enDisconnect();
322                                 return;
323                         } catch (EDAMSystemException e1) {
324                                 if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
325                                         limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
326                                 }
327                                 
328                                 e1.printStackTrace();
329                                 status.message.emit(tr("System error user account information.  Aborting sync and disconnecting!"));
330                                 syncSignal.errorDisconnect.emit();
331                                 error = true;
332                                 enDisconnect();
333                                 return;
334                         } catch (TException e1) {
335                                 e1.printStackTrace();
336                                 syncSignal.errorDisconnect.emit();
337                                 error = true;
338                                 status.message.emit(tr("Transaction error getting user account information.  Aborting sync and disconnecting!"));
339                                 enDisconnect();
340                                 return;
341                         }
342                         
343                         // Get sync state
344                         SyncState syncState = null;
345                         try {   
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) {
352                                 e.printStackTrace();
353                                 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
354                                 syncSignal.errorDisconnect.emit();
355                                 enDisconnect();
356                                 return;
357                         } catch (EDAMSystemException e) {
358                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
359                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
360                                 }
361                                 e.printStackTrace();
362                                 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
363                                 syncSignal.errorDisconnect.emit();
364                                 enDisconnect();
365                                 return;
366                         } catch (TException e) {
367                                 e.printStackTrace();
368                                 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
369                                 syncSignal.errorDisconnect.emit();
370                                 enDisconnect();
371                                 return;
372                         }
373                         
374                         if (syncState == null) {
375                                 logger.log(logger.EXTREME, "Sync State is null");
376                                 status.message.emit(tr("Syncronization Error!"));
377                                 return;
378                         }
379
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");
388                                 sequenceDate = 0;
389                                 conn.getSyncTable().setLastSequenceDate(0);
390                                 updateSequenceNumber = 0;
391                                 conn.getSyncTable().setUpdateSequenceNumber(0);
392                         }
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);
400                         }
401                         if (syncShared != null) {
402                                 downloadAllSharedNotebooks(localNoteStore);
403                         }
404                         if (syncNotebooks != null) {
405                                 downloadAllNotebooks(localNoteStore);
406                         }
407                         
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);
412                                 }
413                                 conn.getSyncTable().deleteRecord("FullInkNoteImageSync");
414                         }
415                         
416                         // If there are remote changes
417                         logger.log(logger.LOW, "Update Count: " +syncState.getUpdateCount());
418                         logger.log(logger.LOW, "Last Update Count: " +updateSequenceNumber);
419                         
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);
425                         }
426                         
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
436                                 if (!error)
437                                         syncExpunged(localNoteStore);
438                                 if (!error)
439                                         syncLocalTags(localNoteStore);
440                                 if (!error)
441                                         syncLocalNotebooks(localNoteStore);
442                                 if (!error)
443                                         syncLocalLinkedNotebooks(localNoteStore);
444                                 if (!error) 
445                                         syncDeletedNotes(localNoteStore);
446                                 if (!error)
447                                         syncLocalNotes();
448                                 if (!error)
449                                         syncLocalSavedSearches(localNoteStore);
450                         }
451                         
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();
457                         
458                         //*****************************************
459                         //* End of synchronization
460                         //*****************************************
461                         if (refreshNeeded)
462                                 syncSignal.refreshLists.emit();
463                         
464                         if (!error) {
465                                 logger.log(logger.LOW, "Sync completed.  Errors=" +error);
466                                 if (!disableUploads) 
467                                         status.message.emit(tr("Synchronizing complete"));
468                                 else
469                                         status.message.emit(tr("Download syncronization complete.  Uploads have been disabled."));
470                                 
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);
478                         }
479                 }
480                 logger.log(logger.HIGH, "Leaving SyncRunner.evernoteSync");
481         }
482         
483         // Sync deleted items with Evernote
484         private void syncExpunged(Client noteStore) {
485                 logger.log(logger.HIGH, "Entering SyncRunner.syncExpunged");
486                 
487                 List<DeletedItemRecord> expunged = conn.getDeletedTable().getAllDeleted();
488                 boolean error = false;
489                 for (int i=0; i<expunged.size() && keepRunning; i++) {
490
491 //                      if (authRefreshNeeded)
492 //                              if (!refreshConnection())
493 //                                      return;
494
495                         try {
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);                              
503                                 }
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);
510                                 }
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);
518                                 }
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);
525                                 }
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());
531                                 }
532                                 logger.log(logger.LOW, "EDAM System Excepton in syncExpunged: "+expunged.get(i).guid);
533                                 logger.log(logger.LOW, e.getStackTrace());
534                                 error=true;
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());
540                                 error=true;
541                         }
542                 }
543                 if (!error)
544                         conn.getDeletedTable().expungeAllDeletedRecords();
545                 
546                 logger.log(logger.HIGH, "Leaving SyncRunner.syncExpunged");
547
548         }
549         private void syncDeletedNotes(Client noteStore) {
550                 if (syncDeletedContent)
551                         return;
552                 logger.log(logger.HIGH, "Entering SyncRunner.syncDeletedNotes");
553                 status.message.emit(tr("Synchronizing deleted notes."));
554
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++) {
558                         
559 //                      if (authRefreshNeeded)
560 //                              if (!refreshConnection())
561 //                                      return;
562                         
563                         Note enNote = notes.get(i);
564                         try {
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);
570                                         }
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());
578                                         } else {
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());                                                
583                                         }
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);
589                                 }                               
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()); 
594                                 //error = true;
595                         } catch (EDAMSystemException e) {
596                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
597                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
598                                 }
599                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
600                                 status.message.emit(tr("Error: ") +e);
601                                 logger.log(logger.LOW, e.toString());           
602                                 error = true;
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());         
607                                 //error = true;
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());   
612                                 error = true;
613                         }               
614                 }
615         }
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."));
621
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);
626                 }
627                 logger.log(logger.HIGH, "Leaving SyncRunner.syncNotes");
628
629         }
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."));
634                         
635                 if (enNote.isActive()) {
636                         try {
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);
643                                 } else { 
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());
653                                 }
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();
662                                         if (d!=null) {  
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());
669                                         }
670                                 }
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);
676
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());   
681                                 error = true;
682                         } catch (EDAMSystemException e) {
683                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
684                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
685                                 }
686                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
687                                 status.message.emit(tr("Error: ") +e);
688                                 logger.log(logger.LOW, e.toString());           
689                                 error = true;
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());   
694                                 error = true;
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());   
699                                 error = true;
700                         }
701                 }
702                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNote");
703
704         }
705
706         // Sync Notebooks with Evernote
707         private void syncLocalNotebooks(Client noteStore) {
708                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalNotebooks");
709                 
710                 status.message.emit(tr("Sending local notebooks."));
711                 List<Notebook> remoteList = new ArrayList<Notebook>();
712                 try {
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());          
719                         error = true;
720                 } catch (EDAMSystemException e1) {
721                         if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
722                                 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
723                         }
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());  
727                         error = true;
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());  
732                         error = true;
733                 }
734                 logger.log(logger.EXTREME, "Getting local dirty notebooks");
735                 List<Notebook> notebooks = conn.getNotebookTable().getDirty();
736                 int sequence;
737                 // Sync the local notebooks with Evernote's
738                 for (int i=0; i<notebooks.size() && keepRunning; i++) {
739                         
740 //                      if (authRefreshNeeded)
741 //                              if (!refreshConnection())
742 //                                      return;
743                         
744                         Notebook enNotebook = notebooks.get(i);
745                         try {
746                                 if (enNotebook.getUpdateSequenceNum() > 0) {
747                                         logger.log(logger.EXTREME, "Existing notebook is dirty");
748                                         sequence = noteStore.updateNotebook(authToken, enNotebook);
749                                 } else {
750                                         logger.log(logger.EXTREME, "New dirty notebook found");
751                                         String oldGuid = enNotebook.getGuid();
752                                         boolean found = false;
753                                         
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");
761                                                         found = true;
762                                                 }
763                                         }
764                                         if (!found)
765                                                 enNotebook = noteStore.createNotebook(authToken, enNotebook);
766                                         
767                                         logger.log(logger.EXTREME, "Updating notebook in database");
768                                         conn.getNotebookTable().updateNotebookGuid(oldGuid, enNotebook.getGuid());
769                                         sequence = enNotebook.getUpdateSequenceNum();
770                                 }
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());     
781                                 error = true;
782                         } catch (EDAMSystemException e) {
783                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
784                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
785                                 }
786                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks");
787                                 logger.log(logger.LOW, e.toString());           
788                                 error = true;
789                         } catch (EDAMNotFoundException e) {
790                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotebooks");
791                                 logger.log(logger.LOW, e.toString());           
792                                 error = true;
793                         } catch (TException e) {
794                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotebooks");
795                                 logger.log(logger.LOW, e.toString());   
796                                 error = true;
797                         }               
798                 }
799                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNotebooks");
800
801         }
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."));
807                 
808                 try {
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());  
815                         error = true;
816                 } catch (EDAMSystemException e1) {
817                         if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
818                                 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
819                         }
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());          
823                         error = true;
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());  
828                         error = true;
829                 }               
830                 
831                 int sequence;
832                 
833                 if (badTagSync == null)
834                         badTagSync = new HashMap<String,String>();
835                 else
836                         badTagSync.clear();
837                 
838                 Tag enTag = findNextTag();
839                 
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;
844                 int loopCount = 0;
845                 
846                 while(enTag!=null && loopCount < maxCount) {
847                         loopCount++;
848 //                      if (authRefreshNeeded)
849 //                              if (!refreshConnection())
850 //                                      return;
851
852                         try {
853                                 if (enTag.getUpdateSequenceNum() > 0) {
854                                         logger.log(logger.EXTREME, "Updating tag");
855                                         sequence = noteStore.updateTag(authToken, enTag);
856                                 } else {
857                                         
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");
868                                                         found = true;
869                                                 }
870                                         }
871                                         if (!found)
872                                                 enTag = noteStore.createTag(authToken, enTag);
873                                         else
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());
879                                         }
880                                 }
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);
892                                 error = true;
893                         } catch (EDAMSystemException e) {
894                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
895                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
896                                 }
897                                 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags: " +enTag.getName());
898                                 logger.log(logger.LOW, e.toString());   
899                                 badTagSync.put(enTag.getGuid(),null);
900                                 error = true;
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);
905                                 error = true;
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);
910                                 error = true;
911                         }       
912                         
913                         // Find the next tag
914                         logger.log(logger.EXTREME, "Finding next tag");
915                         enTag = findNextTag();
916                 }
917                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalTags");
918         }
919         private void syncLocalLinkedNotebooks(Client noteStore) {
920                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalLinkedNotebooks");
921                 
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));
925                         try {
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());           
931                                 error = true;
932                                 e.printStackTrace();
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());           
937                                 error = true;
938                                 e.printStackTrace();
939                         } catch (EDAMSystemException e) {
940                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
941                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
942                                 }
943                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalLinkedNotebooks");
944                                 status.message.emit(tr("Error: ") +e);
945                                 logger.log(logger.LOW, e.toString());           
946                                 error = true;
947                                 e.printStackTrace();
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());           
952                                 error = true;
953                                 e.printStackTrace();
954                         }
955                 }
956                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalLinkedNotebooks");
957         }
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."));
963         
964                 logger.log(logger.EXTREME, "Getting saved searches to compare with local");
965                 try {
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());  
971                         error = true;
972                 } catch (EDAMSystemException e1) {
973                         if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
974                                 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
975                         }
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());          
979                         error = true;
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());  
984                         error = true;
985                 }               
986                 
987                 List<SavedSearch> searches = conn.getSavedSearchTable().getDirty();
988                 int sequence;
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++) {
992                         
993 //                      if (authRefreshNeeded)
994 //                              if (!refreshConnection())
995 //                                      return;
996                         
997                         SavedSearch enSearch = searches.get(i);
998                         try {
999                                 if (enSearch.getUpdateSequenceNum() > 0) 
1000                                         sequence = noteStore.updateSearch(authToken, enSearch);
1001                                 else {
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);
1010                                                         found = true;
1011                                                         logger.log(logger.EXTREME, "Matching saved search found");
1012                                                         sequence = enSearch.getUpdateSequenceNum();
1013                                                 }
1014                                         }
1015
1016                                         String oldGuid = enSearch.getGuid();
1017                                         if (!found)
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());
1022                                 }
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());   
1033                                 error = true;
1034                         } catch (EDAMSystemException e) {
1035                                 if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1036                                         limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1037                                 }
1038                                 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags");
1039                                 logger.log(logger.LOW, e.toString());   
1040                                 error = true;
1041                         } catch (EDAMNotFoundException e) {
1042                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags");
1043                                 logger.log(logger.LOW, e.toString());   
1044                                 error = true;
1045                         } catch (TException e) {
1046                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags");
1047                                 logger.log(logger.LOW, e.toString());   
1048                                 error = true;
1049                         }               
1050                 }
1051
1052                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
1053         }       
1054
1055         // Sync evernote changes with local database
1056         private void syncRemoteToLocal(Client noteStore) {
1057                 logger.log(logger.HIGH, "Entering SyncRunner.syncRemoteToLocal");
1058
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());
1063                 }
1064                 
1065                 int chunkSize = 10;
1066                 SyncChunk chunk = null;
1067                 boolean fullSync = false;
1068                 boolean more = true;
1069                 
1070                 if (updateSequenceNumber == 0)
1071                         fullSync = true;
1072                 
1073                 status.message.emit(tr("Downloading 0% complete."));
1074                 
1075                 while(more &&  keepRunning) {
1076                         
1077 //                      if (authRefreshNeeded)
1078 //                              if (!refreshConnection())
1079 //                                      return;
1080                         
1081                         int sequence = updateSequenceNumber;
1082                         try {
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) {
1088                                 error = true;
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());
1094                                 }
1095                                 error = true;
1096                                 e.printStackTrace();
1097                                 status.message.emit(e.getMessage());
1098                         } catch (TException e) {
1099                                 error = true;
1100                                 e.printStackTrace();
1101                                 status.message.emit(e.getMessage());
1102                         } 
1103                         if (error || chunk == null) 
1104                                 return;
1105                                 
1106                 
1107                         
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());
1114                         
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);
1119                         
1120                         
1121                         // Check for more notes
1122                         if (chunk.getChunkHighUSN() <= updateSequenceNumber) 
1123                                 more = false;
1124                         if (error)
1125                                 more = false;
1126                         logger.log(logger.EXTREME, "More notes? " +more);
1127
1128                         
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();
1136                         }
1137                         
1138                         
1139                         if (more) {
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."));
1144                         }
1145 //                      conn.commitTransaction();
1146                 }
1147                 logger.log(logger.HIGH, "Leaving SyncRunner.syncRemoteToLocal");
1148         }
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();
1154                 if (guid != null) {
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();
1161                                 }
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) 
1164                                 // we delete it.
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);
1168                                 }
1169                         }
1170                 }
1171                 guid = chunk.getExpungedNotebooks();
1172                 if (guid != null)
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);
1176                         }
1177                 guid = chunk.getExpungedTags();
1178                 if (guid != null)
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);
1182                         }
1183                 guid = chunk.getExpungedSearches();
1184                 if (guid != null) 
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);
1188                         }
1189                 guid = chunk.getExpungedLinkedNotebooks();
1190                 if (guid != null) 
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);
1194                         }
1195
1196         }
1197         // Sync remote tags
1198         private void syncRemoteTags(List<Tag> tags) {
1199                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
1200                 if (tags != null) {
1201                         for (int i=0; i<tags.size() && keepRunning; i++) {
1202                                 String oldGuid;
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);
1207                         }
1208                 }
1209                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
1210         }
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++) {
1216                                 String oldGuid;
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);
1221                         }
1222                 }
1223                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncSavedSearches");
1224         }
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); 
1231                         }
1232                 }
1233                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncLinkedNotebooks");
1234         }
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++) {
1240                                 String oldGuid;
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); 
1245                                 
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);
1251 //                                      }
1252 //                              }
1253                         }
1254                 }                       
1255                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
1256         }
1257         // Sync remote shared notebook
1258 //      private void syncRemoteSharedNotebook(String guid, Long id, String token) {
1259 //              List<SharedNotebook> books = noteStore.getSharedNotebookByAuth(authToken);
1260 //      }
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);
1267                         }
1268                 }
1269                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteResources");
1270         }
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.
1278                 
1279                 Note n = conn.getNoteTable().getNote(resource.getNoteGuid(), false, false, false, false, false);
1280                 if (n!=null) {
1281                         logger.log(logger.HIGH, "Resource for note " +n.getGuid() +" : " +n.getTitle());
1282                 }
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);
1286                                                 if (l == null) {
1287                                                         logger.log(logger.HIGH, "Local resource not found");
1288                                                         saveNeeded = true;
1289                                                 } else {
1290                 /* #2 */                        boolean isNoteDirty = conn.getNoteTable().isNoteDirty(r.getNoteGuid());
1291                                                         if (!isNoteDirty) {
1292                                                                 logger.log(logger.HIGH, "Local resource found, but is not dirty");
1293                                                                 saveNeeded = true;
1294                                                         } else {
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());
1301                                                 
1302                                                                 if (localHash.equalsIgnoreCase(remoteHash))
1303                                                                         saveNeeded = true;
1304                                                         }
1305                                                 }
1306                                                 
1307                                                 logger.log(logger.HIGH, "Resource save needed: " +saveNeeded);
1308                                                 if (saveNeeded) 
1309                                                         conn.getNoteTable().noteResourceTable.updateNoteResource(r, false);
1310                                                 if (r.getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
1311                                                         downloadInkNoteImage(r.getGuid(), authToken);
1312                 
1313
1314         }
1315         // Sync remote notes
1316         private void syncRemoteNotes(Client noteStore, List<Note> note, boolean fullSync, String token) {
1317
1318                 if (note != null) {
1319                         
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() );
1325                         }
1326                         logger.log(logger.LOW, "---");
1327                         
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);
1331                         }
1332                 }
1333                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotes");
1334         }
1335         private void syncRemoteNote(Note n, boolean fullSync, String token) {
1336                 if (n!=null) {
1337                         
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());
1355                         }
1356                         boolean ignoreNote = false;
1357                         if (ignoreNotebooks.contains(n.getNotebookGuid()))
1358                                 ignoreNote = true;
1359                         for (int i=0; i<n.getTagGuidsSize(); i++) {
1360                                 if (ignoreTags.contains(n.getTagGuids().get(i))) {
1361                                         ignoreNote = true;
1362                                         i=n.getTagGuidsSize();
1363                                 }
1364                         }
1365                                 
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);
1380                                         }
1381                                 }
1382                         }
1383                 }
1384         }
1385         private Note getEvernoteNote(Client noteStore, String guid, boolean withContent, boolean withResourceData, boolean withResourceRecognition, boolean withResourceAlternateData, String token) { 
1386                 Note n = null;
1387                 try {
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());   
1394                         error = true;
1395                         e.printStackTrace();
1396                 } catch (EDAMSystemException e) {
1397                         if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1398                                 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1399                         }
1400                         logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
1401                         logger.log(logger.LOW, e.toString());   
1402                         error = true;
1403                         e.printStackTrace();
1404                 } catch (EDAMNotFoundException e) {
1405                         logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
1406                         logger.log(logger.LOW, e.toString());   
1407                         error = true;
1408                         e.printStackTrace();
1409                 } catch (TException e) {
1410                         logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
1411                         logger.log(logger.LOW, e.toString());   
1412                         error = true;
1413                         e.printStackTrace();
1414                 }
1415                 return n;
1416         }
1417         private Resource getEvernoteResource(Client noteStore, String guid, boolean withData, boolean withRecognition, boolean withAttributes, String token) { 
1418                 Resource n = null;
1419                 try {
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());   
1426                         error = true;
1427                         e.printStackTrace();
1428                 } catch (EDAMSystemException e) {
1429                         if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1430                                 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1431                         }
1432                         logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
1433                         logger.log(logger.LOW, e.toString());   
1434                         error = true;
1435                         e.printStackTrace();
1436                 } catch (EDAMNotFoundException e) {
1437                         logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
1438                         logger.log(logger.LOW, e.toString());   
1439                         error = true;
1440                         e.printStackTrace();
1441                 } catch (TException e) {
1442                         logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
1443                         logger.log(logger.LOW, e.toString());   
1444                         error = true;
1445                         e.printStackTrace();
1446                 }
1447                 return n;
1448         }
1449
1450         
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()) {
1458                         return false;
1459                 } 
1460                 boolean oldIsDirty = conn.getNoteTable().isNoteDirty(n.getGuid());
1461                 if (!oldIsDirty) 
1462                         return false;
1463                 return true;
1464         }
1465         
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();
1474                                 i=books.size();
1475                         }
1476                 }
1477                 
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());
1482                         long prevTime = l;
1483                         while (prevTime==l) {
1484                                 currentTime = new GregorianCalendar();
1485                                 l=currentTime.getTimeInMillis();
1486                         }
1487                         String randint = new String(Long.toString(l));
1488                 
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);
1497                         
1498                         conn.getNotebookTable().addNotebook(newBook, false, true);
1499                         notebookSignal.listChanged.emit();
1500                         notebookGuid = newBook.getGuid();
1501                         refreshNeeded = true;
1502                 }
1503                 
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());
1509                 long prevTime = l;
1510                 while (prevTime==l) {
1511                         currentTime = new GregorianCalendar();
1512                         l = currentTime.getTimeInMillis();
1513                 }
1514                 String newGuid = new String(Long.toString(l));
1515                                         
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);
1523                 }
1524                 
1525                 conn.getNoteTable().resetNoteSequence(guid);
1526                 conn.getNoteTable().updateNoteGuid(guid, newGuid);
1527                 conn.getNoteTable().updateNoteNotebook(newGuid, notebookGuid, true);
1528                 
1529                 noteSignal.notebookChanged.emit(newGuid, notebookGuid);
1530                 refreshNeeded = true;
1531                 noteSignal.guidChanged.emit(guid,newGuid);
1532         }
1533         
1534
1535
1536         
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;
1547                 int two_halfs = 0;
1548                 do {
1549                         if ((0 <= halfbyte) && (halfbyte <= 9))
1550                                buf.append((char) ('0' + halfbyte));
1551                            else
1552                                 buf.append((char) ('a' + (halfbyte - 10)));
1553                         halfbyte = element & 0x0F;
1554                 } while(two_halfs++ < 1);
1555             }
1556             return buf.toString();              
1557         }
1558
1559         
1560         
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");
1566                 Tag nextTag = null;
1567                 List<Tag> tags = conn.getTagTable().getDirty();
1568                 
1569                 // Find the parent.  If the parent has a sequence > 0 then it is a good
1570                 // parent.
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");
1575                                         return tags.get(i);
1576                                 }
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");
1580                                         return tags.get(i);
1581                                 }
1582                         }
1583                 }
1584                 
1585                 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - no tags returned");
1586                 return nextTag;
1587         }
1588         
1589         
1590            // Connect to Evernote
1591     public boolean enConnect()  {
1592         try {
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());
1597                         mb.exec();
1598                         e.printStackTrace();
1599                 }
1600                 userStoreProt = new TBinaryProtocol(userStoreTrans);
1601             userStore = new UserStore.Client(userStoreProt, userStoreProt);
1602             
1603             syncSignal.saveUserStore.emit(userStore);
1604             try {
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");
1609                         mb.exec();
1610                         isConnected = false;
1611                         return false;
1612                 } catch (EDAMSystemException e) {
1613                         if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1614                                 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
1615                         }
1616                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "EDAM System Excepton", e.getLocalizedMessage());
1617                         mb.exec();
1618                         e.printStackTrace();
1619                         isConnected = false;
1620                 } catch (TException e) {
1621                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1622                         mb.exec();
1623                         e.printStackTrace();
1624                         isConnected = false;
1625                 }
1626                 
1627             boolean versionOk = false;
1628                 try {
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;
1635                 } 
1636             if (!versionOk) { 
1637                 System.err.println("Incomatible EDAM client protocol version"); 
1638                 isConnected = false;
1639             }
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"));
1648                         mb.exec();
1649                         return false;
1650                 
1651             }
1652                 noteStoreUrl = noteStoreUrlBase + user.getShardId();
1653                 syncSignal.saveAuthToken.emit(authToken);
1654                 syncSignal.saveNoteStore.emit(localNoteStore);
1655                 
1656          
1657                 try {
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());
1662                         mb.exec();
1663                         e.printStackTrace();
1664                         isConnected = false;
1665                 } 
1666                 noteStoreProt = new TBinaryProtocol(noteStoreTrans);
1667                 localNoteStore = 
1668                         new NoteStore.Client(noteStoreProt, noteStoreProt); 
1669                 isConnected = true;
1670                 //authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
1671                 //authRefreshTime = authTimeRemaining / 2;
1672             //}
1673             
1674                 // Get user information
1675                 try {
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());
1683                         }
1684                         e1.printStackTrace();
1685                 } catch (TException e1) {
1686                         e1.printStackTrace();
1687                 }
1688             
1689             return isConnected;
1690     }
1691         // Disconnect from the database                         
1692     public void enDisconnect() {
1693         isConnected = false;
1694     }
1695     
1696     /*
1697     // Refresh the connection
1698     private synchronized boolean refreshConnection() {
1699         
1700                 logger.log(logger.EXTREME, "Entering SyncRunner.refreshConnection()");
1701 //        Calendar cal = Calendar.getInstance();
1702                 
1703         // If we are not connected let's get out of here
1704         if (!isConnected)
1705                 return false;
1706         
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"));
1712                         return false;
1713                 }
1714         
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();
1718                 
1719  //             // Default to checking again in 5 min.  This in case we fail.
1720  //             authRefreshTime = authRefreshTime +(5*60*1000);     
1721
1722                 // Try to get a new token
1723                 AuthenticationResult newAuth = null; 
1724                 logger.log(logger.EXTREME, "Beginning to try authentication refresh");
1725         try {
1726                 if (userStore != null && authToken != null) 
1727                         newAuth = userStore.refreshAuthentication(authToken); 
1728                 else
1729                         return false;
1730                 logger.log(logger.EXTREME, "UserStore.refreshAuthentication has succeeded.");
1731                 } catch (EDAMUserException e) {
1732                         e.printStackTrace();
1733                         syncSignal.authRefreshComplete.emit(false);
1734                         failedRefreshes++;
1735                         return false;
1736                 } catch (EDAMSystemException e) {
1737                         e.printStackTrace();
1738                         syncSignal.authRefreshComplete.emit(false);
1739                         failedRefreshes++;
1740                         return false;           
1741                 } catch (TException e) { 
1742                         e.printStackTrace();
1743                         syncSignal.authRefreshComplete.emit(false);
1744                         failedRefreshes++;
1745                         return false;
1746                 }
1747                 
1748                 // If we didn't get a good auth, then we've failed
1749                 if (newAuth == null) {
1750                         failedRefreshes++;
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);
1755                         return false;
1756                 }
1757                 
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);        
1764                 failedRefreshes=0;
1765                 syncSignal.authRefreshComplete.emit(true);
1766                 authRefreshNeeded = false;
1767                         
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);
1772 //              }
1773                 
1774                 return true;
1775     }
1776     
1777     */
1778     
1779         public synchronized boolean addWork(String request) {
1780                 if (workQueue.offer(request))
1781                         return true;
1782                 return false;
1783         }
1784     
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())));
1789         return n;
1790     }
1791
1792
1793
1794     //*********************************************************
1795     //* Special download instructions.  Used for DB upgrades
1796     //*********************************************************
1797     private void downloadAllSharedNotebooks(Client noteStore) {
1798         try {
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);
1803                         }
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());
1809                         return;
1810                 } catch (EDAMSystemException e1) {
1811                         if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1812                                 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1813                         }
1814                         e1.printStackTrace();
1815                         status.message.emit(tr("System exception Listing shared notebooks."));
1816                         logger.log(logger.LOW, e1.getMessage());
1817                         return;
1818                 } catch (TException e1) {
1819                         e1.printStackTrace();
1820                         status.message.emit(tr("Transaction exception Listing shared notebooks."));
1821                         logger.log(logger.LOW, e1.getMessage());
1822                         return;
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());
1827                 }
1828     }
1829     private void downloadAllNotebooks(Client noteStore) {
1830         try {
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);
1835                         }
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());
1841                         return;
1842                 } catch (EDAMSystemException e1) {
1843                         if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1844                                 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1845                         }
1846                         e1.printStackTrace();
1847                         status.message.emit(tr("System exception Listing notebooks."));
1848                         logger.log(logger.LOW, e1.getMessage());
1849                         return;
1850                 } catch (TException e1) {
1851                         e1.printStackTrace();
1852                         status.message.emit(tr("Transaction exception Listing notebooks."));
1853                         logger.log(logger.LOW, e1.getMessage());
1854                         return;
1855                 }
1856     }
1857     private void downloadAllLinkedNotebooks(Client noteStore) {
1858         try {
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);
1863                         }
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());
1869                         return;
1870                 } catch (EDAMSystemException e1) {
1871                         if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
1872                                 limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
1873                         }
1874                         e1.printStackTrace();
1875                         status.message.emit(tr("System exception Listing linked notebooks."));
1876                         logger.log(logger.LOW, e1.getMessage());
1877                         return;
1878                 } catch (TException e1) {
1879                         e1.printStackTrace();
1880                         status.message.emit(tr("Transaction exception Listing lineked notebooks."));
1881                         logger.log(logger.LOW, e1.getMessage());
1882                         return;
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());
1887                 }
1888     }
1889
1890     
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=";
1894                 Integer slice = 1;
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));
1905
1906             try {
1907                                 post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
1908                         } catch (UnsupportedEncodingException e1) {
1909                                 e1.printStackTrace();
1910                         }
1911                 try {
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();
1921                         }
1922
1923                         slice++;
1924         }
1925         http.getConnectionManager().shutdown(); 
1926                 noteSignal.noteChanged.emit(r.getNoteGuid(), null);   // Signal to ivalidate note cache
1927     }
1928     
1929     
1930     public QByteArray writeToFile(InputStream iStream) throws IOException {
1931
1932             File temp = File.createTempFile("nn-inknote-temp", ".png");
1933
1934             // Save InputStream to the file.
1935             BufferedOutputStream fOut = null;
1936             try {
1937               fOut = new BufferedOutputStream(new FileOutputStream(temp));
1938               byte[] buffer = new byte[32 * 1024];
1939               int bytesRead = 0;
1940               while ((bytesRead = iStream.read(buffer)) != -1) {
1941                 fOut.write(buffer, 0, bytesRead);
1942               }
1943             }
1944             finally {
1945                 iStream.close();
1946                 fOut.close();
1947             }
1948             QFile tempFile = new QFile(temp.getAbsoluteFile().toString());
1949             tempFile.open(OpenModeFlag.ReadOnly);
1950             QByteArray data = tempFile.readAll();
1951             tempFile.close();
1952             tempFile.remove();
1953             return data;
1954     }
1955     
1956     
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();
1964         
1965         errorSharedNotebooks.clear();
1966                 
1967         for (int i=0; i<books.size(); i++) {
1968                 if (errorSharedNotebooksIgnored.containsKey(books.get(i).getGuid()))
1969                         break;
1970                 try {
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());
1974
1975                                 logger.log(logger.EXTREME, "Last Sequence Number on file: " +lastSequenceNumber);
1976                                 
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);        
1983
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());
1989                                 } else {
1990                                         logger.log(logger.EXTREME, "Share key is null");
1991                                         linkedAuthResult = new AuthenticationResult();
1992                                         linkedAuthResult.setAuthenticationToken("");
1993                                 }
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;
2000                                         } 
2001                                         logger.log(logger.EXTREME, "Calling syncLinkedNotebook for " +books.get(i).getShareName());
2002                                         syncLinkedNotebook(linkedNoteStore, books.get(i), 
2003                                                         lastSequenceNumber, linkedSyncState.getUpdateCount(), authToken);
2004                                 }
2005                         
2006                         // Synchronize local changes
2007                         syncLocalLinkedNoteChanges(linkedNoteStore, books.get(i));
2008                                 
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());
2018                         error = true;
2019                         e.printStackTrace();
2020                 } catch (EDAMSystemException e) {
2021                         if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
2022                                 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
2023                         }
2024                         error = true;
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) {
2029                         error = true;
2030                         e.printStackTrace();
2031                 }
2032         }
2033         
2034         // Cleanup tags
2035         conn.getTagTable().removeUnusedLinkedTags();
2036         conn.getTagTable().cleanupTags();
2037         tagSignal.listChanged.emit();
2038         return;
2039         }
2040
2041     
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()))
2048                         return;
2049                 List<Note> dirtyNotes = conn.getNoteTable().getDirtyLinkedNotes();
2050                 if (dirtyNoteGuids == null) 
2051                         dirtyNoteGuids = new ArrayList<String>();
2052
2053                 for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
2054                         dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
2055                 }
2056                 boolean fullSync = false;
2057                 if (usn == 0)
2058                         fullSync = true;
2059                 boolean syncError = false;
2060                 while (usn < highSequence && !syncError) {
2061                         refreshNeeded = true;
2062                         try {
2063                                 SyncChunk chunk = 
2064                                         linkedNoteStore.getLinkedNotebookSyncChunk(token, book, usn, 10, fullSync);
2065                                 
2066                                 // Expunge notes
2067                                 syncExpungedNotes(chunk);
2068
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());
2073                                 // Sync resources
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());
2077                                 }
2078                                 logger.log(logger.EXTREME, "Synchronizing linked notebooks: " +chunk.getNotebooksSize());
2079                                 syncRemoteLinkedNotebooks(linkedNoteStore, chunk.getNotebooks(), false, book);
2080                                 syncLinkedTags(chunk.getTags(), book.getGuid());
2081                                 
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);
2085
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);
2090                                 }
2091                                 usn = chunk.getChunkHighUSN();
2092                                 conn.getLinkedNotebookTable().setLastSequenceDate(book.getGuid(),chunk.getCurrentTime());
2093                                 conn.getLinkedNotebookTable().setLastSequenceNumber(book.getGuid(),chunk.getChunkHighUSN());
2094                         } catch (EDAMUserException e) {
2095                                 syncError = true;
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());
2102                                 }
2103                                 syncError = true;
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) {
2108                                 syncError = true;
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) {
2114                                 syncError = true;
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());
2118                         }
2119                 }
2120                 logger.log(logger.EXTREME, "leaving syncLinkedNotebook");
2121         }
2122         // Sync remote tags
2123         private void syncLinkedTags(List<Tag> tags, String notebookGuid) {
2124                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
2125                 if (tags != null) {
2126                         for (int i=0; i<tags.size() && keepRunning; i++) {
2127                                 conn.getTagTable().syncLinkedTag(tags.get(i), notebookGuid, false);
2128                         }
2129                 }
2130                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
2131         }
2132         
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++) {
2138                                 try {
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();
2145                                         } else {
2146                                                 readOnly = true;
2147                                         }
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) {
2152                                         readOnly = true;
2153                                         e.printStackTrace();
2154                                 } catch (EDAMNotFoundException e) {
2155                                         readOnly = true;
2156                                         e.printStackTrace();
2157                                 } catch (EDAMSystemException e) {
2158                                         if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
2159                                                 limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
2160                                         }
2161                                         readOnly = true;
2162                                         e.printStackTrace();
2163                                 } catch (TException e) {
2164                                         readOnly = true;
2165                                         e.printStackTrace();
2166                                 }
2167
2168                         }
2169                 }                       
2170                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
2171         }
2172
2173         private void findNewLinkedTags(Client noteStore, List<Note> newNotes, String token) {
2174                 if (newNotes == null)
2175                         return;
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)) {
2181                                         Tag newTag;
2182                                         try {
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());
2190                                                 }
2191                                                 e.printStackTrace();
2192                                         } catch (EDAMNotFoundException e) {
2193                                                 e.printStackTrace();
2194                                         } catch (TException e) {
2195                                                 e.printStackTrace();
2196                                         }
2197                                         
2198                                 }
2199                         }
2200                 }
2201         }
2202
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());
2213                 }
2214                 logger.log(logger.EXTREME, "Leaving SyncRunner.synclocalLinkedNoteChanges");
2215         }
2216
2217 }