OSDN Git Service

コピーライトの表示を修正。
[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 import org.apache.thrift.TException;
49 import org.apache.thrift.protocol.TBinaryProtocol;
50 import org.apache.thrift.transport.THttpClient;
51 import org.apache.thrift.transport.TTransportException;
52
53 import com.evernote.edam.error.EDAMNotFoundException;
54 import com.evernote.edam.error.EDAMSystemException;
55 import com.evernote.edam.error.EDAMUserException;
56 import com.evernote.edam.notestore.NoteStore;
57 import com.evernote.edam.notestore.NoteStore.Client;
58 import com.evernote.edam.notestore.SyncChunk;
59 import com.evernote.edam.notestore.SyncState;
60 import com.evernote.edam.type.Data;
61 import com.evernote.edam.type.LinkedNotebook;
62 import com.evernote.edam.type.Note;
63 import com.evernote.edam.type.Notebook;
64 import com.evernote.edam.type.Resource;
65 import com.evernote.edam.type.SavedSearch;
66 import com.evernote.edam.type.SharedNotebook;
67 import com.evernote.edam.type.Tag;
68 import com.evernote.edam.type.User;
69 import com.evernote.edam.userstore.AuthenticationResult;
70 import com.evernote.edam.userstore.UserStore;
71 import com.trolltech.qt.core.QByteArray;
72 import com.trolltech.qt.core.QFile;
73 import com.trolltech.qt.core.QIODevice.OpenModeFlag;
74 import com.trolltech.qt.core.QObject;
75 import com.trolltech.qt.core.QTextCodec;
76 import com.trolltech.qt.gui.QMessageBox;
77
78 import cx.fbn.nevernote.signals.NoteIndexSignal;
79 import cx.fbn.nevernote.signals.NoteResourceSignal;
80 import cx.fbn.nevernote.signals.NoteSignal;
81 import cx.fbn.nevernote.signals.NotebookSignal;
82 import cx.fbn.nevernote.signals.SavedSearchSignal;
83 import cx.fbn.nevernote.signals.StatusSignal;
84 import cx.fbn.nevernote.signals.SyncSignal;
85 import cx.fbn.nevernote.signals.TagSignal;
86 import cx.fbn.nevernote.sql.DatabaseConnection;
87 import cx.fbn.nevernote.sql.DeletedItemRecord;
88 import cx.fbn.nevernote.utilities.ApplicationLogger;
89
90 public class SyncRunner extends QObject implements Runnable {
91         
92         private final ApplicationLogger logger;
93         private DatabaseConnection              conn;
94         private boolean                                 idle;
95         public boolean                                  error;
96         public volatile List<String>    errorSharedNotebooks;
97         public volatile HashMap<String,String>  errorSharedNotebooksIgnored;
98         public volatile boolean                 isConnected;
99         public volatile boolean                 keepRunning;
100         public volatile String                  authToken;
101         private long                                    evernoteUpdateCount;
102         private final String userAgent = "NeighborNote/" + System.getProperty("os.name")
103                                                         +"/"+System.getProperty("java.vendor") + "/"
104                                                         + System.getProperty("java.version") +";";
105         
106         public volatile NoteStore.Client                localNoteStore;
107         private UserStore.Client                                userStore;
108         
109         public volatile StatusSignal                    status;
110         public volatile TagSignal                               tagSignal;
111         public volatile NotebookSignal                  notebookSignal;
112         public volatile NoteIndexSignal                 noteIndexSignal;
113         public volatile NoteSignal                              noteSignal;
114         public volatile SavedSearchSignal               searchSignal;
115         public volatile NoteResourceSignal              resourceSignal;
116         public volatile SyncSignal                              syncSignal;
117         public volatile boolean                                 authRefreshNeeded;
118         public volatile boolean                                 syncNeeded;
119         public volatile boolean                                 disableUploads;
120         public volatile boolean                                 syncDeletedContent;
121         private volatile List<String>                   dirtyNoteGuids;
122         
123     public volatile String username = ""; 
124     public volatile String password = ""; 
125         public volatile String userStoreUrl;
126 //          private final static String consumerKey = "baumgarte"; 
127 //          private final static String consumerSecret = "eb8b5740e17cb55f";
128     public String noteStoreUrlBase;
129     private THttpClient userStoreTrans;
130     private TBinaryProtocol userStoreProt;
131     //private AuthenticationResult authResult;
132     private AuthenticationResult linkedAuthResult;
133     private User user; 
134 //          private long authTimeRemaining;
135     public long authRefreshTime;
136     public long failedRefreshes = 0;
137     public  THttpClient noteStoreTrans;
138     public TBinaryProtocol noteStoreProt;
139     public String noteStoreUrl;
140     public long sequenceDate;
141     public int updateSequenceNumber;
142     private boolean refreshNeeded;
143     private volatile LinkedBlockingQueue<String> workQueue;
144         private static int MAX_QUEUED_WAITING = 1000;
145         String dbuid;
146         String dburl;
147         String indexUrl;
148         String resourceUrl;
149         // ICHANGED
150         String behaviorUrl;
151         
152         String dbpswd;
153         String dbcpswd;
154         private final TreeSet<String> ignoreTags;
155         private final TreeSet<String> ignoreNotebooks;
156         private final TreeSet<String> ignoreLinkedNotebooks;
157         private HashMap<String,String> badTagSync;
158
159         
160         // ICHANGED String bを追加   
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                 resourceUrl = r;
174                 indexUrl = i;
175                 // ICHANGED
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                         // ICHANGED behaviorUrlを追加
204                         conn = new DatabaseConnection(logger, dburl, indexUrl, resourceUrl, behaviorUrl, dbuid, dbpswd, dbcpswd, 200);
205                         while(keepRunning) {
206                                 logger.log(logger.EXTREME, "Blocking until work is found");
207                                 String work = workQueue.take();
208                                 logger.log(logger.LOW, "Dirty Notes Before Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
209                                 logger.log(logger.EXTREME, "Work found: " +work);
210                                 if (work.equalsIgnoreCase("stop")) {
211                                         idle=false;
212                                         return;
213                                 }
214                                 conn.getNoteTable().dumpDirtyNotes();  // Debugging statement
215                                 idle=false;
216                                 error=false;
217                                 if (syncNeeded) {
218                                         logger.log(logger.EXTREME, "SyncNeeded is true");
219                                         refreshNeeded=false;
220                                         sequenceDate = conn.getSyncTable().getLastSequenceDate();
221                                         updateSequenceNumber = conn.getSyncTable().getUpdateSequenceNumber();
222                                         try {
223                                                 logger.log(logger.EXTREME, "Beginning sync");
224                                                 evernoteSync(localNoteStore);
225                                                 logger.log(logger.EXTREME, "Sync finished");
226                                         } catch (UnknownHostException e) {
227                                                 status.message.emit(e.getMessage());
228                                         }
229                                 }
230                                 idle=true;
231                                 logger.log(logger.EXTREME, "Signaling refresh finished.  refreshNeeded=" +refreshNeeded);
232                                 syncSignal.finished.emit(refreshNeeded);
233                                 if (error) {
234                                         syncSignal.errorDisconnect.emit();
235                                         status.message.emit(tr("Error synchronizing - see log for details."));
236                                 }
237                                 logger.log(logger.LOW, "Dirty Notes After Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
238                                 conn.getNoteTable().dumpDirtyNotes();
239                                 logger.log(logger.LOW, "---");
240                         }
241                 }       
242                 catch (InterruptedException e1) {
243                         e1.printStackTrace();
244                 }
245                 conn.dbShutdown();
246         }
247
248         
249         public DatabaseConnection getConnection() {
250                 return conn;
251         }
252
253         public boolean isIdle() {
254                 return idle;
255         }
256
257
258         public void setConnected(boolean c) {
259                 isConnected = c;
260         }
261         public void setKeepRunning(boolean r) {
262                 logger.log(logger.EXTREME, "Setting keepRunning=" +r);
263                 keepRunning = r;
264         }
265         public void setNoteStore(NoteStore.Client c) {
266                 logger.log(logger.EXTREME, "Setting NoteStore in sync thread");
267                 localNoteStore = c;
268         }
269         public void setUserStore(UserStore.Client c) {
270                 logger.log(logger.EXTREME, "Setting UserStore in sync thread");
271                 userStore = c;
272         }
273
274         public void setEvernoteUpdateCount(long s) {
275                 logger.log(logger.EXTREME, "Setting Update Count in sync thread");
276                 evernoteUpdateCount = s;
277         }
278         
279         //***************************************************************
280     //***************************************************************
281     //** These functions deal with Evernote communications
282     //***************************************************************
283     //***************************************************************
284         // Synchronize changes with Evernote
285         @SuppressWarnings("unused")
286         private void evernoteSync(Client noteStore) throws java.net.UnknownHostException {
287                 logger.log(logger.HIGH, "Entering SyncRunner.evernoteSync");
288                 
289                 // Rebuild list of tags & notebooks to ignore
290                 ignoreNotebooks.clear();
291                 List<String> ignore = conn.getSyncTable().getIgnoreRecords("NOTEBOOK");
292                 for (int i=0; i<ignore.size(); i++) 
293                         ignoreNotebooks.add(ignore.get(i));
294                 
295                 ignore.clear();
296                 ignore = conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK");
297                 for (int i=0; i<ignore.size(); i++) 
298                         ignoreLinkedNotebooks.add(ignore.get(i));
299                 
300                 ignoreTags.clear();
301                 ignore = conn.getSyncTable().getIgnoreRecords("TAG");
302                 for (int i=0; i<ignore.size(); i++) 
303                         ignoreTags.add(ignore.get(i));
304
305                 // Make sure we are connected & should keep running
306                 if (isConnected && keepRunning) {
307                         error = false;
308                         logger.log(logger.EXTREME, "Synchronizing with Evernote");
309                         status.message.emit(tr("Synchronizing with Evernote"));
310                         
311                         // Get user information
312                         try {
313                                 logger.log(logger.EXTREME, "getting user from userstore");
314                                 User user = userStore.getUser(authToken);
315                                 logger.log(logger.EXTREME, "Saving user information");
316                                 syncSignal.saveUserInformation.emit(user);
317                         } catch (EDAMUserException e1) {
318                                 e1.printStackTrace();
319                                 status.message.emit(tr("User exception getting user account information.  Aborting sync and disconnecting"));
320                                 syncSignal.errorDisconnect.emit();
321                                 error = true;
322                                 enDisconnect();
323                                 return;
324                         } catch (EDAMSystemException e1) {
325                                 e1.printStackTrace();
326                                 status.message.emit(tr("System error user account information.  Aborting sync and disconnecting!"));
327                                 syncSignal.errorDisconnect.emit();
328                                 error = true;
329                                 enDisconnect();
330                                 return;
331                         } catch (TException e1) {
332                                 e1.printStackTrace();
333                                 syncSignal.errorDisconnect.emit();
334                                 error = true;
335                                 status.message.emit(tr("Transaction error getting user account information.  Aborting sync and disconnecting!"));
336                                 enDisconnect();
337                                 return;
338                         }
339                         
340                         // Get sync state
341                         SyncState syncState = null;
342                         try {   
343                                 logger.log(logger.EXTREME, "Getting sync state");
344                                 syncState = noteStore.getSyncState(authToken);  
345                                 syncSignal.saveUploadAmount.emit(syncState.getUploaded());
346                                 syncSignal.saveEvernoteUpdateCount.emit(syncState.getUpdateCount());
347                                 evernoteUpdateCount = syncState.getUpdateCount();
348                         } catch (EDAMUserException e) {
349                                 e.printStackTrace();
350                                 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
351                                 syncSignal.errorDisconnect.emit();
352                                 enDisconnect();
353                                 return;
354                         } catch (EDAMSystemException e) {
355                                 e.printStackTrace();
356                                 status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
357                                 syncSignal.errorDisconnect.emit();
358                                 enDisconnect();
359                                 return;
360                         } catch (TException e) {
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                         }
367                         
368                         if (syncState == null) {
369                                 logger.log(logger.EXTREME, "Sync State is null");
370                                 status.message.emit(tr("Syncronization Error!"));
371                                 return;
372                         }
373
374                         // Determine what to do. 
375                         // If we need to do a full sync.
376                         logger.log(logger.LOW, "Full Sequence Before: " +syncState.getFullSyncBefore());
377                         logger.log(logger.LOW, "Last Sequence Date: " +sequenceDate);
378                         logger.log(logger.LOW, "Var Last Sequence Number: " +updateSequenceNumber);
379                         logger.log(logger.LOW, "DB Last Sequence Number: " + conn.getSyncTable().getUpdateSequenceNumber());
380                         if (syncState.getFullSyncBefore() > sequenceDate) {
381                                 logger.log(logger.EXTREME, "Full sequence date has expired");
382                                 sequenceDate = 0;
383                                 conn.getSyncTable().setLastSequenceDate(0);
384                                 updateSequenceNumber = 0;
385                                 conn.getSyncTable().setUpdateSequenceNumber(0);
386                         }
387                         // Check for "special" sync instructions
388                         String syncLinked = conn.getSyncTable().getRecord("FullLinkedNotebookSync");
389                         String syncShared = conn.getSyncTable().getRecord("FullSharedNotebookSync");
390                         String syncNotebooks = conn.getSyncTable().getRecord("FullNotebookSync");
391                         String syncInkNoteImages = conn.getSyncTable().getRecord("FullInkNoteImageSync");
392                         if (syncLinked != null) {
393                                 downloadAllLinkedNotebooks(localNoteStore);
394                         }
395                         if (syncShared != null) {
396                                 downloadAllSharedNotebooks(localNoteStore);
397                         }
398                         if (syncNotebooks != null) {
399                                 downloadAllNotebooks(localNoteStore);
400                         }
401                         
402                         if (syncInkNoteImages != null) {
403                                 List<String> guids = conn.getNoteTable().noteResourceTable.findInkNotes();
404                                 for (int i=0; i<guids.size(); i++) {
405                                         downloadInkNoteImage(guids.get(i), authToken);
406                                 }
407                                 conn.getSyncTable().deleteRecord("FullInkNoteImageSync");
408                         }
409                         
410                         // If there are remote changes
411                         logger.log(logger.LOW, "Update Count: " +syncState.getUpdateCount());
412                         logger.log(logger.LOW, "Last Update Count: " +updateSequenceNumber);
413                         
414                         if (syncState.getUpdateCount() > updateSequenceNumber) {
415                                 logger.log(logger.EXTREME, "Refresh needed is true");
416                                 refreshNeeded = true;
417                                 logger.log(logger.EXTREME, "Downloading changes");
418                                 syncRemoteToLocal(localNoteStore);
419                         }
420                         
421                         //*****************************************
422                         //* Sync linked/shared notebooks 
423                         //*****************************************
424                         //syncLinkedNotebooks();
425                         //conn.getNoteTable().getDirty();
426                         //disableUploads = true;   /// DELETE THIS LINE!!!!
427                         if (!disableUploads) {
428                                 logger.log(logger.EXTREME, "Uploading changes");
429                                 // Synchronize remote changes
430                                 if (!error)
431                                         syncExpunged(localNoteStore);
432                                 if (!error)
433                                         syncLocalTags(localNoteStore);
434                                 if (!error)
435                                         syncLocalNotebooks(localNoteStore);
436                                 if (!error)
437                                         syncLocalLinkedNotebooks(localNoteStore);
438                                 if (!error) 
439                                         syncDeletedNotes(localNoteStore);
440                                 if (!error)
441                                         syncLocalNotes();
442                                 if (!error)
443                                         syncLocalSavedSearches(localNoteStore);
444                         }
445                         
446                         status.message.emit(tr("Cleaning up"));
447                         List<String> notes = conn.getNoteTable().expungeIgnoreSynchronizedNotes(conn.getSyncTable().getIgnoreRecords("NOTEBOOK"), 
448                                         conn.getSyncTable().getIgnoreRecords("TAG"), conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK"));
449                         if (notes.size() > 0)
450                                 syncSignal.refreshLists.emit();
451                         
452                         //*****************************************
453                         //* End of synchronization
454                         //*****************************************
455                         if (refreshNeeded)
456                                 syncSignal.refreshLists.emit();
457                         
458                         if (!error) {
459                                 logger.log(logger.LOW, "Sync completed.  Errors=" +error);
460                                 if (!disableUploads) 
461                                         status.message.emit(tr("Synchronizing complete"));
462                                 else
463                                         status.message.emit(tr("Download syncronization complete.  Uploads have been disabled."));
464                                 
465                                 logger.log(logger.EXTREME, "Saving sync time");
466                                 if (syncState.getCurrentTime() > sequenceDate)
467                                         sequenceDate = syncState.getCurrentTime();
468                                 if (syncState.getUpdateCount() > updateSequenceNumber)
469                                         updateSequenceNumber = syncState.getUpdateCount();
470                                 conn.getSyncTable().setLastSequenceDate(sequenceDate);
471                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
472                         }
473                 }
474                 logger.log(logger.HIGH, "Leaving SyncRunner.evernoteSync");
475         }
476         
477         // Sync deleted items with Evernote
478         private void syncExpunged(Client noteStore) {
479                 logger.log(logger.HIGH, "Entering SyncRunner.syncExpunged");
480                 
481                 List<DeletedItemRecord> expunged = conn.getDeletedTable().getAllDeleted();
482                 boolean error = false;
483                 for (int i=0; i<expunged.size() && keepRunning; i++) {
484
485 //                      if (authRefreshNeeded)
486 //                              if (!refreshConnection())
487 //                                      return;
488
489                         try {
490                                 if (expunged.get(i).type.equalsIgnoreCase("TAG")) {
491                                         logger.log(logger.EXTREME, "Tag expunged");
492                                         conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "TAG"); \r
493                                         updateSequenceNumber = noteStore.expungeTag(authToken, expunged.get(i).guid);
494                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
495                                         conn.getSyncTable().setLastSequenceDate(sequenceDate);
496                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);                              \r
497                                 }
498                                 if      (expunged.get(i).type.equalsIgnoreCase("NOTEBOOK")) {
499                                         logger.log(logger.EXTREME, "Notebook expunged");
500                                         conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "NOTEBOOK");\r
501                                         updateSequenceNumber = noteStore.expungeNotebook(authToken, expunged.get(i).guid);
502                                         conn.getSyncTable().setLastSequenceDate(sequenceDate);
503                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
504                                 }
505                                 if (expunged.get(i).type.equalsIgnoreCase("NOTE")) {
506                                         logger.log(logger.EXTREME, "Note expunged");
507                                         conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "NOTE");\r
508                                         updateSequenceNumber = noteStore.deleteNote(authToken, expunged.get(i).guid);
509                                         refreshNeeded = true;
510                                         conn.getSyncTable().setLastSequenceDate(sequenceDate);
511                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
512                                 }
513                                 if (expunged.get(i).type.equalsIgnoreCase("SAVEDSEARCH")) {
514                                         logger.log(logger.EXTREME, "saved search expunged");
515                                         conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "SAVEDSEARCH");\r
516                                         updateSequenceNumber = noteStore.expungeSearch(authToken, expunged.get(i).guid);
517                                         conn.getSyncTable().setLastSequenceDate(sequenceDate);
518                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
519                                 }
520                         } catch (EDAMUserException e) {
521                                 logger.log(logger.LOW, "EDAM User Excepton in syncExpunged: " +expunged.get(i).guid);   // This can happen if we try to delete a deleted note
522                         } catch (EDAMSystemException e) {
523                                 logger.log(logger.LOW, "EDAM System Excepton in syncExpunged: "+expunged.get(i).guid);
524                                 logger.log(logger.LOW, e.getStackTrace());
525                                 error=true;
526                         } catch (EDAMNotFoundException e) {
527                                 logger.log(logger.LOW, "EDAM Not Found Excepton in syncExpunged: "+expunged.get(i).guid);
528                         } catch (TException e) {
529                                 logger.log(logger.LOW, "EDAM TExcepton in syncExpunged: "+expunged.get(i).guid);
530                                 logger.log(logger.LOW, e.getStackTrace());
531                                 error=true;
532                         }
533                 }
534                 if (!error)
535                         conn.getDeletedTable().expungeAllDeletedRecords();
536                 
537                 logger.log(logger.HIGH, "Leaving SyncRunner.syncExpunged");
538
539         }
540         private void syncDeletedNotes(Client noteStore) {
541                 if (syncDeletedContent)
542                         return;
543                 logger.log(logger.HIGH, "Entering SyncRunner.syncDeletedNotes");
544                 status.message.emit(tr("Synchronizing deleted notes."));
545
546                 List<Note> notes = conn.getNoteTable().getDirty();
547                 // Sync the local notebooks with Evernote's
548                 for (int i=0; i<notes.size() && keepRunning; i++) {
549                         
550 //                      if (authRefreshNeeded)
551 //                              if (!refreshConnection())
552 //                                      return;
553                         
554                         Note enNote = notes.get(i);
555                         try {
556                                 if (enNote.getUpdateSequenceNum() > 0 && (enNote.isActive() == false || enNote.getDeleted() > 0)) {
557                                         // Check that the note is valid.  \r
558                                         if (enNote.isActive() == true || enNote.getDeleted() == 0) {\r
559                                                 conn.getNoteTable().deleteNote(enNote.getGuid());\r
560                                                 enNote = conn.getNoteTable().getNote(enNote.getGuid(), false, false, false, false, false);\r
561                                         }\r
562                                         if (syncDeletedContent) {
563                                                 logger.log(logger.EXTREME, "Deleted note found & synch content selected");
564                                                 Note delNote = conn.getNoteTable().getNote(enNote.getGuid(), true, true, true, true, true);
565                                                 delNote = getNoteContent(delNote);
566                                                 delNote = noteStore.updateNote(authToken, delNote);
567                                                 enNote.setUpdateSequenceNum(delNote.getUpdateSequenceNum());
568                                                 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
569                                         } else {
570                                                 logger.log(logger.EXTREME, "Deleted note found & sync content not selected");
571                                                 int usn = noteStore.deleteNote(authToken, enNote.getGuid());
572                                                 enNote.setUpdateSequenceNum(usn);
573                                                 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());                                                
574                                         }
575                                         logger.log(logger.EXTREME, "Resetting deleted dirty flag");
576                                         conn.getNoteTable().resetDirtyFlag(enNote.getGuid());
577                                         updateSequenceNumber = enNote.getUpdateSequenceNum();
578                                         logger.log(logger.EXTREME, "Saving sequence number");
579                                         conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
580                                 }                               
581                         } catch (EDAMUserException e) {
582                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);\r
583                                 //status.message.emit("Error sending local note: " +e.getParameter());
584                                 //logger.log(logger.LOW, e.toString()); 
585                                 //error = true;
586                         } catch (EDAMSystemException e) {
587                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
588                                 status.message.emit(tr("Error: ") +e);
589                                 logger.log(logger.LOW, e.toString());           
590                                 error = true;
591                         } catch (EDAMNotFoundException e) {
592                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);\r
593                                 //status.message.emit("Error deleting local note: " +e +" - Continuing");
594                                 //logger.log(logger.LOW, e.toString());         
595                                 //error = true;
596                         } catch (TException e) {
597                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);
598                                 status.message.emit(tr("Error sending local note: ") +e);
599                                 logger.log(logger.LOW, e.toString());   
600                                 error = true;
601                         }               
602                 }
603         }
604         // Sync notes with Evernote
605         private void syncLocalNotes() {
606                 logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");
607                 logger.log(logger.LOW, "Dirty local notes found: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
608                 status.message.emit(tr("Sending local notes."));
609
610                 List<Note> notes = conn.getNoteTable().getDirty();
611                 // Sync the local notebooks with Evernote's
612                 for (int i=0; i<notes.size() && keepRunning; i++) {
613                         syncLocalNote(localNoteStore, notes.get(i), authToken);
614                 }
615                 logger.log(logger.HIGH, "Leaving SyncRunner.syncNotes");
616
617         }
618         // Sync notes with Evernote
619         private void syncLocalNote(Client noteStore, Note enNote, String token) {
620                 logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");
621                 status.message.emit(tr("Sending local notes."));
622                         
623                 if (enNote.isActive()) {
624                         try {
625                                 if (enNote.getUpdateSequenceNum() > 0) {
626                                         logger.log(logger.EXTREME, "Active dirty note found - non new - " +enNote.getGuid());
627                                         logger.log(logger.EXTREME, "Fetching note content");
628                                         enNote = getNoteContent(enNote);
629                                         logger.log(logger.MEDIUM, "Updating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
630                                         enNote = noteStore.updateNote(token, enNote);
631                                 } else { 
632                                         logger.log(logger.EXTREME, "Active dirty found - new note " +enNote.getGuid());
633                                         String oldGuid = enNote.getGuid();
634                                         logger.log(logger.MEDIUM, "Fetching note content");
635                                         enNote = getNoteContent(enNote);
636                                         logger.log(logger.MEDIUM, "Creating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
637                                         enNote = noteStore.createNote(token, enNote);
638                                         logger.log(logger.MEDIUM, "New note Guid : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
639                                         noteSignal.guidChanged.emit(oldGuid, enNote.getGuid());
640                                         conn.getNoteTable().updateNoteGuid(oldGuid, enNote.getGuid());
641                                 }
642                                 updateSequenceNumber = enNote.getUpdateSequenceNum();
643                                 logger.log(logger.EXTREME, "Saving note");
644                                 conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
645                                 List<Resource> rl = enNote.getResources();
646                                 logger.log(logger.EXTREME, "Getting note resources");
647                                 for (int j=0; j<enNote.getResourcesSize() && keepRunning; j++) {
648                                         Resource newRes = rl.get(j);
649                                         Data d = newRes.getData();
650                                         if (d!=null) {  
651                                                 logger.log(logger.EXTREME, "Calculating resource hash");
652                                                 String hash = byteArrayToHexString(d.getBodyHash());
653                                                 logger.log(logger.EXTREME, "updating resources by hash");
654                                                 String oldGuid = conn.getNoteTable().noteResourceTable.getNoteResourceGuidByHashHex(enNote.getGuid(), hash);
655                                                 conn.getNoteTable().updateNoteResourceGuidbyHash(enNote.getGuid(), newRes.getGuid(), hash);
656                                                 resourceSignal.resourceGuidChanged.emit(enNote.getGuid(), oldGuid, newRes.getGuid());
657                                         }
658                                 }
659                                 logger.log(logger.EXTREME, "Resetting note dirty flag");
660                                 conn.getNoteTable().resetDirtyFlag(enNote.getGuid());
661                                 updateSequenceNumber = enNote.getUpdateSequenceNum();
662                                 logger.log(logger.EXTREME, "Emitting note sequence number change");
663                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
664
665                         } catch (EDAMUserException e) {
666                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);
667                                 status.message.emit(tr("Error sending local note: ")     +e.getParameter());
668                                 logger.log(logger.LOW, e.toString());   
669                                 error = true;
670                         } catch (EDAMSystemException e) {
671                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
672                                 status.message.emit(tr("Error: ") +e);
673                                 logger.log(logger.LOW, e.toString());           
674                                 error = true;
675                         } catch (EDAMNotFoundException e) {
676                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);
677                                 status.message.emit(tr("Error sending local note: ") +e);
678                                 logger.log(logger.LOW, e.toString());   
679                                 error = true;
680                         } catch (TException e) {
681                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);
682                                 status.message.emit(tr("Error sending local note: ") +e);
683                                 logger.log(logger.LOW, e.toString());   
684                                 error = true;
685                         }
686                 }
687                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNote");
688
689         }
690
691         // Sync Notebooks with Evernote
692         private void syncLocalNotebooks(Client noteStore) {
693                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalNotebooks");
694                 
695                 status.message.emit(tr("Sending local notebooks."));
696                 List<Notebook> remoteList = new ArrayList<Notebook>();
697                 try {
698                         logger.log(logger.EXTREME, "Getting remote notebooks to compare with local");
699                         remoteList = noteStore.listNotebooks(authToken);
700                 } catch (EDAMUserException e1) {
701                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks getting remote Notebook List");
702                         status.message.emit(tr("Error: ") +e1);
703                         logger.log(logger.LOW, e1.toString());          
704                         error = true;
705                 } catch (EDAMSystemException e1) {
706                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks getting remote Notebook List");
707                         status.message.emit(tr("Error: ") +e1);
708                         logger.log(logger.LOW, e1.toString());  
709                         error = true;
710                 } catch (TException e1) {
711                         logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalNotebooks getting remote Notebook List");
712                         status.message.emit(tr("Error: ") +e1);
713                         logger.log(logger.LOW, e1.toString());  
714                         error = true;
715                 }
716                 logger.log(logger.EXTREME, "Getting local dirty notebooks");
717                 List<Notebook> notebooks = conn.getNotebookTable().getDirty();
718                 int sequence;
719                 // Sync the local notebooks with Evernote's
720                 for (int i=0; i<notebooks.size() && keepRunning; i++) {
721                         
722 //                      if (authRefreshNeeded)
723 //                              if (!refreshConnection())
724 //                                      return;
725                         
726                         Notebook enNotebook = notebooks.get(i);
727                         try {
728                                 if (enNotebook.getUpdateSequenceNum() > 0) {
729                                         logger.log(logger.EXTREME, "Existing notebook is dirty");
730                                         sequence = noteStore.updateNotebook(authToken, enNotebook);
731                                 } else {
732                                         logger.log(logger.EXTREME, "New dirty notebook found");
733                                         String oldGuid = enNotebook.getGuid();
734                                         boolean found = false;
735                                         
736                                         // Look for a notebook with the same name.  If one is found, we don't need 
737                                         // to create another one
738                                         logger.log(logger.EXTREME, "Looking for matching notebook name");
739                                         for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
740                                                 if (remoteList.get(k).getName().equalsIgnoreCase(enNotebook.getName())) {
741                                                         enNotebook = remoteList.get(k);
742                                                         logger.log(logger.EXTREME, "Matching notebook found");
743                                                         found = true;
744                                                 }
745                                         }
746                                         if (!found)
747                                                 enNotebook = noteStore.createNotebook(authToken, enNotebook);
748                                         
749                                         logger.log(logger.EXTREME, "Updating notebook in database");
750                                         conn.getNotebookTable().updateNotebookGuid(oldGuid, enNotebook.getGuid());
751                                         sequence = enNotebook.getUpdateSequenceNum();
752                                 }
753                                 logger.log(logger.EXTREME, "Updating notebook sequence in database");
754                                 conn.getNotebookTable().updateNotebookSequence(enNotebook.getGuid(), sequence);
755                                 logger.log(logger.EXTREME, "Resetting dirty flag in notebook");
756                                 conn.getNotebookTable().resetDirtyFlag(enNotebook.getGuid());
757                                 updateSequenceNumber = sequence;
758                                 logger.log(logger.EXTREME, "Emitting sequence number to main thread");
759                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
760                         } catch (EDAMUserException e) {
761                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks");
762                                 logger.log(logger.LOW, e.toString() + ": Stack : " +enNotebook.getStack());     
763                                 error = true;
764                         } catch (EDAMSystemException e) {
765                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks");
766                                 logger.log(logger.LOW, e.toString());           
767                                 error = true;
768                         } catch (EDAMNotFoundException e) {
769                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotebooks");
770                                 logger.log(logger.LOW, e.toString());           
771                                 error = true;
772                         } catch (TException e) {
773                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotebooks");
774                                 logger.log(logger.LOW, e.toString());   
775                                 error = true;
776                         }               
777                 }
778                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNotebooks");
779
780         }
781         // Sync Tags with Evernote
782         private void syncLocalTags(Client noteStore) {
783                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalTags");
784                 List<Tag> remoteList = new ArrayList<Tag>();
785                 status.message.emit(tr("Sending local tags."));
786                 
787                 try {
788                         logger.log(logger.EXTREME, "Getting remote tags to compare names with the local tags");
789                         remoteList = noteStore.listTags(authToken);
790                 } catch (EDAMUserException e1) {
791                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote Tag List");
792                         status.message.emit(tr("Error: ") +e1);
793                         logger.log(logger.LOW, e1.toString());  
794                         error = true;
795                 } catch (EDAMSystemException e1) {
796                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote Tag List");
797                         status.message.emit(tr("Error: ") +e1);
798                         logger.log(logger.LOW, e1.toString());          
799                         error = true;
800                 } catch (TException e1) {
801                         logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote Tag List");
802                         status.message.emit(tr("Error: ") +e1);
803                         logger.log(logger.LOW, e1.toString());  
804                         error = true;
805                 }               
806                 
807                 int sequence;
808                 
809                 if (badTagSync == null)
810                         badTagSync = new HashMap<String,String>();
811                 else
812                         badTagSync.clear();
813                 
814                 Tag enTag = findNextTag();
815                 
816                 // This is a hack.  Sometimes this function goes flookey and goes into a 
817                 // perpetual loop.  This causes  NeverNote to flood Evernote's servers.
818                 // This is a safety valve to prevent unlimited loops.
819                 int maxCount = conn.getTagTable().getDirty().size()+10;
820                 int loopCount = 0;
821                 
822                 while(enTag!=null && loopCount < maxCount) {
823                         loopCount++;
824 //                      if (authRefreshNeeded)
825 //                              if (!refreshConnection())
826 //                                      return;
827
828                         try {
829                                 if (enTag.getUpdateSequenceNum() > 0) {
830                                         logger.log(logger.EXTREME, "Updating tag");
831                                         sequence = noteStore.updateTag(authToken, enTag);
832                                 } else {
833                                         
834                                         // Look for a tag with the same name.  If one is found, we don't need 
835                                         // to create another one
836                                         logger.log(logger.EXTREME, "New tag.  Comparing with remote names");
837                                         boolean found = false;
838                                         String oldGuid = enTag.getGuid();
839                                         for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
840                                                 if (remoteList.get(k).getName().equalsIgnoreCase(enTag.getName())) {
841                                                         conn.getTagTable().updateTagGuid(enTag.getGuid(), remoteList.get(k).getGuid());
842                                                         enTag = remoteList.get(k);
843                                                         logger.log(logger.EXTREME, "Matching tag name found");
844                                                         found = true;
845                                                 }
846                                         }
847                                         if (!found)
848                                                 enTag = noteStore.createTag(authToken, enTag);
849                                         else
850                                                 enTag.setUpdateSequenceNum(noteStore.updateTag(authToken,enTag));
851                                         sequence = enTag.getUpdateSequenceNum();
852                                         if (!oldGuid.equals(enTag.getGuid())) {
853                                                 logger.log(logger.EXTREME, "Updating tag guid");
854                                                 conn.getTagTable().updateTagGuid(oldGuid, enTag.getGuid());
855                                         }
856                                 }
857                                 logger.log(logger.EXTREME, "Updating tag sequence number");
858                                 conn.getTagTable().updateTagSequence(enTag.getGuid(), sequence);
859                                 logger.log(logger.EXTREME, "Resetting tag dirty flag");
860                                 conn.getTagTable().resetDirtyFlag(enTag.getGuid());
861                                 logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");
862                                 updateSequenceNumber = sequence;
863                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
864                         } catch (EDAMUserException e) {
865                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags: " +enTag.getName());
866                                 logger.log(logger.LOW, e.toString());
867                                 badTagSync.put(enTag.getGuid(),null);
868                                 error = true;
869                         } catch (EDAMSystemException e) {
870                                 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags: " +enTag.getName());
871                                 logger.log(logger.LOW, e.toString());   
872                                 badTagSync.put(enTag.getGuid(),null);
873                                 error = true;
874                         } catch (EDAMNotFoundException e) {
875                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags: " +enTag.getName());
876                                 logger.log(logger.LOW, e.toString());
877                                 badTagSync.put(enTag.getGuid(),null);
878                                 error = true;
879                         } catch (TException e) {
880                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags: " +enTag.getName());
881                                 logger.log(logger.LOW, e.toString());
882                                 badTagSync.put(enTag.getGuid(),null);
883                                 error = true;
884                         }       
885                         
886                         // Find the next tag
887                         logger.log(logger.EXTREME, "Finding next tag");
888                         enTag = findNextTag();
889                 }
890                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalTags");
891         }
892         private void syncLocalLinkedNotebooks(Client noteStore) {
893                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalLinkedNotebooks");
894                 
895                 List<String> list = conn.getLinkedNotebookTable().getDirtyGuids();
896                 for (int i=0; i<list.size(); i++) {
897                         LinkedNotebook book = conn.getLinkedNotebookTable().getNotebook(list.get(i));
898                         try {
899                                 noteStore.updateLinkedNotebook(authToken, book);
900                         } catch (EDAMUserException e) {
901                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalLinkedNotebooks");
902                                 status.message.emit(tr("Error: ") +e);
903                                 logger.log(logger.LOW, e.toString());           
904                                 error = true;
905                                 e.printStackTrace();
906                         } catch (EDAMNotFoundException e) {
907                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalLinkedNotebooks");
908                                 status.message.emit(tr("Error: ") +e);
909                                 logger.log(logger.LOW, e.toString());           
910                                 error = true;
911                                 e.printStackTrace();
912                         } catch (EDAMSystemException e) {
913                                 logger.log(logger.LOW, "*** EDAM System Excepton syncLocalLinkedNotebooks");
914                                 status.message.emit(tr("Error: ") +e);
915                                 logger.log(logger.LOW, e.toString());           
916                                 error = true;
917                                 e.printStackTrace();
918                         } catch (TException e) {
919                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalLinkedNotebooks");
920                                 status.message.emit(tr("Error: ") +e);
921                                 logger.log(logger.LOW, e.toString());           
922                                 error = true;
923                                 e.printStackTrace();
924                         }
925                 }
926                 logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalLinkedNotebooks");
927         }
928         // Sync Saved Searches with Evernote
929         private void syncLocalSavedSearches(Client noteStore) {
930                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
931                 List<SavedSearch> remoteList = new ArrayList<SavedSearch>();
932                 status.message.emit(tr("Sending saved searches."));
933         
934                 logger.log(logger.EXTREME, "Getting saved searches to compare with local");
935                 try {
936                         remoteList = noteStore.listSearches(authToken);
937                 } catch (EDAMUserException e1) {
938                         logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote saved search List");
939                         status.message.emit(tr("Error: ") +e1);
940                         logger.log(logger.LOW, e1.toString());  
941                         error = true;
942                 } catch (EDAMSystemException e1) {
943                         logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote saved search List");
944                         status.message.emit(tr("Error: ") +e1);
945                         logger.log(logger.LOW, e1.toString());          
946                         error = true;
947                 } catch (TException e1) {
948                         logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote saved search List");
949                         status.message.emit(tr("Error: ") +e1);
950                         logger.log(logger.LOW, e1.toString());  
951                         error = true;
952                 }               
953                 
954                 List<SavedSearch> searches = conn.getSavedSearchTable().getDirty();
955                 int sequence;
956                 // Sync the local notebooks with Evernote's
957                 logger.log(logger.EXTREME, "Beginning to send saved searches");
958                 for (int i=0; i<searches.size() &&  keepRunning; i++) {
959                         
960 //                      if (authRefreshNeeded)
961 //                              if (!refreshConnection())
962 //                                      return;
963                         
964                         SavedSearch enSearch = searches.get(i);
965                         try {
966                                 if (enSearch.getUpdateSequenceNum() > 0) 
967                                         sequence = noteStore.updateSearch(authToken, enSearch);
968                                 else {
969                                         logger.log(logger.EXTREME, "New saved search found.");
970                                         // Look for a tag with the same name.  If one is found, we don't need 
971                                         // to create another one
972                                         boolean found = false;
973                                         logger.log(logger.EXTREME, "Matching remote saved search names with local");
974                                         for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
975                                                 if (remoteList.get(k).getName().equalsIgnoreCase(enSearch.getName())) {
976                                                         enSearch = remoteList.get(k);
977                                                         found = true;
978                                                         logger.log(logger.EXTREME, "Matching saved search found");
979                                                         sequence = enSearch.getUpdateSequenceNum();
980                                                 }
981                                         }
982
983                                         String oldGuid = enSearch.getGuid();
984                                         if (!found)
985                                                 enSearch = noteStore.createSearch(authToken, enSearch);
986                                         sequence = enSearch.getUpdateSequenceNum();
987                                         logger.log(logger.EXTREME, "Updating tag guid in local database");
988                                         conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, enSearch.getGuid());
989                                 }
990                                 logger.log(logger.EXTREME, "Updating tag sequence in local database");
991                                 conn.getSavedSearchTable().updateSavedSearchSequence(enSearch.getGuid(), sequence);
992                                 logger.log(logger.EXTREME, "Resetting tag dirty flag");
993                                 conn.getSavedSearchTable().resetDirtyFlag(enSearch.getGuid());
994                                 logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");
995                                 updateSequenceNumber = sequence;
996                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
997                         } catch (EDAMUserException e) {
998                                 logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags");
999                                 logger.log(logger.LOW, e.toString());   
1000                                 error = true;
1001                         } catch (EDAMSystemException e) {
1002                                 logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags");
1003                                 logger.log(logger.LOW, e.toString());   
1004                                 error = true;
1005                         } catch (EDAMNotFoundException e) {
1006                                 logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags");
1007                                 logger.log(logger.LOW, e.toString());   
1008                                 error = true;
1009                         } catch (TException e) {
1010                                 logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags");
1011                                 logger.log(logger.LOW, e.toString());   
1012                                 error = true;
1013                         }               
1014                 }
1015
1016                 logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
1017         }       
1018
1019         // Sync evernote changes with local database
1020         private void syncRemoteToLocal(Client noteStore) {
1021                 logger.log(logger.HIGH, "Entering SyncRunner.syncRemoteToLocal");
1022
1023                 List<Note> dirtyNotes = conn.getNoteTable().getDirty();
1024                 dirtyNoteGuids = new ArrayList<String>();\r
1025                 for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
1026                         dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
1027                 }
1028                 
1029                 int chunkSize = 10;
1030                 SyncChunk chunk = null;
1031                 boolean fullSync = false;
1032                 boolean more = true;
1033                 
1034                 if (updateSequenceNumber == 0)
1035                         fullSync = true;
1036                 
1037                 status.message.emit(tr("Downloading 0% complete."));
1038                 
1039                 while(more &&  keepRunning) {
1040                         
1041 //                      if (authRefreshNeeded)
1042 //                              if (!refreshConnection())
1043 //                                      return;
1044                         
1045                         int sequence = updateSequenceNumber;
1046                         try {
1047 //                              conn.beginTransaction();
1048                                 logger.log(logger.EXTREME, "Getting chunk from Evernote");
1049                                 chunk = noteStore.getSyncChunk(authToken, sequence, chunkSize, fullSync);
1050                                 logger.log(logger.LOW, "Chunk High Sequence: " +chunk.getChunkHighUSN());
1051                         } catch (EDAMUserException e) {
1052                                 error = true;
1053                                 e.printStackTrace();
1054                                 status.message.emit(e.getMessage());
1055                         } catch (EDAMSystemException e) {
1056                                 error = true;
1057                                 e.printStackTrace();
1058                                 status.message.emit(e.getMessage());
1059                         } catch (TException e) {
1060                                 error = true;
1061                                 e.printStackTrace();
1062                                 status.message.emit(e.getMessage());
1063                         } 
1064                         if (error || chunk == null) 
1065                                 return;
1066                                 
1067                 
1068                         
1069                         syncRemoteTags(chunk.getTags());
1070                         syncRemoteSavedSearches(chunk.getSearches());
1071                         syncRemoteNotebooks(chunk.getNotebooks());
1072                         syncRemoteNotes(noteStore, chunk.getNotes(), fullSync, authToken);
1073                         syncRemoteResources(noteStore, chunk.getResources());
1074                         syncRemoteLinkedNotebooks(chunk.getLinkedNotebooks());
1075                         
1076                         // Signal about any updated notes to invalidate the cache
1077                         for (int i=0; i<chunk.getNotesSize(); i++) 
1078                                 noteSignal.noteChanged.emit(chunk.getNotes().get(i).getGuid(), null); 
1079                         syncExpungedNotes(chunk);
1080                         
1081                         
1082                         // Check for more notes
1083                         if (chunk.getChunkHighUSN() <= updateSequenceNumber) 
1084                                 more = false;
1085                         if (error)
1086                                 more = false;
1087                         logger.log(logger.EXTREME, "More notes? " +more);
1088
1089                         
1090                         // Save the chunk sequence number
1091                         if (!error && chunk.getChunkHighUSN() > 0 && keepRunning) {
1092                                 logger.log(logger.EXTREME, "emitting sequence number to main thread");
1093                                 updateSequenceNumber = chunk.getChunkHighUSN();
1094                                 conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());
1095                                 conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
1096 //                              conn.commitTransaction();
1097                         }
1098                         
1099                         
1100                         if (more) {
1101                                 long pct = chunk.getChunkHighUSN() * 100;
1102                                 conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());
1103                                 pct = pct/evernoteUpdateCount;
1104                                 status.message.emit(tr("Downloading ") +new Long(pct).toString()+tr("% complete."));
1105                         }
1106 //                      conn.commitTransaction();
1107                 }
1108                 logger.log(logger.HIGH, "Leaving SyncRunner.syncRemoteToLocal");
1109         }
1110         // Sync expunged notes
1111         private void syncExpungedNotes(SyncChunk chunk) {
1112                 // Do the local deletes
1113                 logger.log(logger.EXTREME, "Doing local deletes");
1114                 List<String> guid = chunk.getExpungedNotes();
1115                 if (guid != null) {
1116                         for (int i=0; i<guid.size() && keepRunning; i++) {
1117                                 String notebookGuid = "";
1118                                 Note localNote = conn.getNoteTable().getNote(guid.get(i), false, false, false, false, false);
1119                                 if (localNote != null) {
1120                                         conn.getNoteTable().updateNoteSequence(guid.get(i), 0);
1121                                         notebookGuid = localNote.getNotebookGuid();
1122                                 }
1123                                 // If the note is in a local notebook (which means we moved it) or if the \r
1124                                 // note returned is null (which means it is already deleted or flagged expunged) \r
1125                                 // we delete it.\r
1126                                 if (!conn.getNotebookTable().isNotebookLocal(notebookGuid) || localNote == null) {\r
1127                                         logger.log(logger.EXTREME, "Expunging local note from database");
1128                                         conn.getNoteTable().expungeNote(guid.get(i), true, false);
1129                                 }
1130                         }
1131                 }
1132                 guid = chunk.getExpungedNotebooks();
1133                 if (guid != null)
1134                         for (int i=0; i<guid.size() && keepRunning; i++) {
1135                                 logger.log(logger.EXTREME, "Expunging local notebook from database");
1136                                 conn.getNotebookTable().expungeNotebook(guid.get(i), false);
1137                         }
1138                 guid = chunk.getExpungedTags();
1139                 if (guid != null)
1140                         for (int i=0; i<guid.size() && keepRunning; i++) {
1141                                 logger.log(logger.EXTREME, "Expunging tags from local database");
1142                                 conn.getTagTable().expungeTag(guid.get(i), false);
1143                         }
1144                 guid = chunk.getExpungedSearches();
1145                 if (guid != null) 
1146                         for (int i=0; i<guid.size() && keepRunning; i++) {
1147                                 logger.log(logger.EXTREME, "Expunging saved search from local database");
1148                                 conn.getSavedSearchTable().expungeSavedSearch(guid.get(i), false);
1149                         }
1150                 guid = chunk.getExpungedLinkedNotebooks();
1151                 if (guid != null) 
1152                         for (int i=0; i<guid.size() && keepRunning; i++) {
1153                                 logger.log(logger.EXTREME, "Expunging linked notebook from local database");
1154                                 conn.getLinkedNotebookTable().expungeNotebook(guid.get(i), false);
1155                         }
1156
1157         }
1158         // Sync remote tags
1159         private void syncRemoteTags(List<Tag> tags) {
1160                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
1161                 if (tags != null) {
1162                         for (int i=0; i<tags.size() && keepRunning; i++) {
1163                                 String oldGuid;
1164                                 oldGuid = conn.getTagTable().findTagByName(tags.get(i).getName());
1165                                 if (oldGuid != null && !tags.get(i).getGuid().equalsIgnoreCase(oldGuid))
1166                                         conn.getTagTable().updateTagGuid(oldGuid, tags.get(i).getGuid());
1167                                 conn.getTagTable().syncTag(tags.get(i), false);
1168                         }
1169                 }
1170                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
1171         }
1172         // Sync remote saved searches
1173         private void syncRemoteSavedSearches(List<SavedSearch> searches) {
1174                 logger.log(logger.EXTREME, "Entering SyncRunner.syncSavedSearches");
1175                 if (searches != null) {
1176                         for (int i=0; i<searches.size() && keepRunning; i++) {
1177                                 String oldGuid;
1178                                 oldGuid = conn.getSavedSearchTable().findSavedSearchByName(searches.get(i).getName());
1179                                 if (oldGuid != null && !searches.get(i).getGuid().equalsIgnoreCase(oldGuid))
1180                                         conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, searches.get(i).getGuid());
1181                                 conn.getSavedSearchTable().syncSavedSearch(searches.get(i), false);
1182                         }
1183                 }
1184                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncSavedSearches");
1185         }
1186         // Sync remote linked notebooks
1187         private void syncRemoteLinkedNotebooks(List<LinkedNotebook> books) {
1188                 logger.log(logger.EXTREME, "Entering SyncRunner.syncLinkedNotebooks");
1189                 if (books != null) {
1190                         for (int i=0; i<books.size() && keepRunning; i++) {
1191                                 conn.getLinkedNotebookTable().updateNotebook(books.get(i), false); 
1192                         }
1193                 }
1194                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncLinkedNotebooks");
1195         }
1196         // Sync remote Notebooks 2
1197         private void syncRemoteNotebooks(List<Notebook> notebooks) {
1198                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");
1199                 if (notebooks != null) {
1200                         for (int i=0; i<notebooks.size() && keepRunning; i++) {
1201                                 String oldGuid;
1202                                 oldGuid = conn.getNotebookTable().findNotebookByName(notebooks.get(i).getName());
1203                                 if (oldGuid != null && !conn.getNotebookTable().isNotebookLocal(oldGuid) && !notebooks.get(i).getGuid().equalsIgnoreCase(oldGuid))
1204                                         conn.getNotebookTable().updateNotebookGuid(oldGuid, notebooks.get(i).getGuid());
1205                                 conn.getNotebookTable().syncNotebook(notebooks.get(i), false); 
1206                                 
1207                                 // Synchronize shared notebook information
1208 //                              if (notebooks.get(i).getSharedNotebookIdsSize() > 0) {
1209 //                                      conn.getSharedNotebookTable().expungeNotebookByGuid(notebooks.get(i).getGuid(), false);
1210 //                                      for (int j=0; j<notebooks.get(i).getSharedNotebookIdsSize(); j++) {
1211 //                                              syncRemoteSharedNotebook(notebooks.get(i).getGuid(), notebooks.get(i).getSharedNotebookIds().get(j), authToken);
1212 //                                      }
1213 //                              }
1214                         }
1215                 }                       
1216                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
1217         }
1218         // Sync remote shared notebook
1219 //      private void syncRemoteSharedNotebook(String guid, Long id, String token) {
1220 //              List<SharedNotebook> books = noteStore.getSharedNotebookByAuth(authToken);
1221 //      }
1222         // Sync remote Resources
1223         private void syncRemoteResources(Client noteStore, List<Resource> resource) {
1224                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteResources");
1225                 if (resource != null) {
1226                         for (int i=0; i<resource.size() && keepRunning; i++) {
1227                                 syncRemoteResource(noteStore, resource.get(i), authToken);
1228                         }
1229                 }
1230                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteResources");
1231         }
1232         // Sync remote resource
1233         private void syncRemoteResource(Client noteStore, Resource resource, String authToken) {
1234                 // This is how the logic for this works.
1235                 // 1.) If the resource is not in the local database, we add it.
1236                 // 2.) If a copy of the resource is in the local database and the note isn't dirty, we update the local copy
1237                 // 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
1238                 //     is a conflict.  The note conflict should get a copy of the resource at that time.
1239                 
1240                 Note n = conn.getNoteTable().getNote(resource.getNoteGuid(), false, false, false, false, false);
1241                 if (n!=null) {
1242                         logger.log(logger.HIGH, "Resource for note " +n.getGuid() +" : " +n.getTitle());
1243                 }
1244                 boolean saveNeeded = false;
1245                 /* #1 */                Resource r = getEvernoteResource(noteStore, resource.getGuid(), true,true,true, authToken);
1246                                                 Resource l = conn.getNoteTable().noteResourceTable.getNoteResource(r.getGuid(), false);
1247                                                 if (l == null) {
1248                                                         logger.log(logger.HIGH, "Local resource not found");
1249                                                         saveNeeded = true;
1250                                                 } else {
1251                 /* #2 */                        boolean isNoteDirty = conn.getNoteTable().isNoteDirty(r.getNoteGuid());
1252                                                         if (!isNoteDirty) {
1253                                                                 logger.log(logger.HIGH, "Local resource found, but is not dirty");
1254                                                                 saveNeeded = true;
1255                                                         } else {
1256                 /* #3 */                                String remoteHash = "";
1257                                                                 if (r != null && r.getData() != null && r.getData().getBodyHash() != null)
1258                                                                         remoteHash = byteArrayToHexString(r.getData().getBodyHash());
1259                                                                 String localHash = "";
1260                                                                 if (l != null && l.getData() != null && l.getData().getBodyHash() != null)
1261                                                                         remoteHash = byteArrayToHexString(l.getData().getBodyHash());
1262                                                 
1263                                                                 if (localHash.equalsIgnoreCase(remoteHash))
1264                                                                         saveNeeded = true;
1265                                                         }
1266                                                 }
1267                                                 
1268                                                 logger.log(logger.HIGH, "Resource save needed: " +saveNeeded);
1269                                                 if (saveNeeded) 
1270                                                         conn.getNoteTable().noteResourceTable.updateNoteResource(r, false);
1271                                                 if (r.getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
1272                                                         downloadInkNoteImage(r.getGuid(), authToken);
1273                 
1274
1275         }
1276         // Sync remote notes
1277         private void syncRemoteNotes(Client noteStore, List<Note> note, boolean fullSync, String token) {
1278 \r
1279                 if (note != null) {
1280                         \r
1281                         logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotes");\r
1282                         logger.log(logger.LOW, "Local Dirty Notes: ");\r
1283                         logger.log(logger.LOW, "Remote Dirty Notes:");\r
1284                         for (int i=0; i<note.size();i++) {\r
1285                                 logger.log(logger.LOW, i +" : " +note.get(i).getGuid() + " : " +note.get(i).getTitle() );\r
1286                         }\r
1287                         logger.log(logger.LOW, "---");\r
1288                         \r
1289                         for (int i=0; i<note.size() && keepRunning; i++) {
1290                                 Note n = getEvernoteNote(noteStore, note.get(i).getGuid(), true, fullSync, true,true, token);
1291                                 syncRemoteNote(n, fullSync, token);
1292                         }
1293                 }
1294                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotes");
1295         }
1296         private void syncRemoteNote(Note n, boolean fullSync, String token) {
1297                 if (n!=null) {
1298                         
1299                         // Basically, this is how the sync logic for a note works.
1300                         // If the remote note has changed and the local has not, we
1301                         // accept the change.
1302                         // If both the local & remote have changed but the sequence
1303                         // numbers are the same, we don't accept the change.  This
1304                         // seems to happen when attachments are indexed by the server.
1305                         // If both the local & remote have changed and the sequence numbers
1306                         // are different we move the local copy to a local notebook (making sure
1307                         // to copy all resources) and we accept the new one.                    
1308                         boolean conflictingNote = true;
1309                         logger.log(logger.EXTREME, "Checking for duplicate note " +n.getGuid() +" : " +n.getTitle());\r
1310                         if (dirtyNoteGuids != null && dirtyNoteGuids.contains(n.getGuid())) { 
1311                                 logger.log(logger.EXTREME, "Conflict check beginning");
1312                                 conflictingNote = checkForConflict(n);
1313                                 logger.log(logger.EXTREME, "Conflict check results " +conflictingNote);
1314                                 if (conflictingNote)
1315                                         moveConflictingNote(n.getGuid());
1316                         }
1317                         boolean ignoreNote = false;
1318                         if (ignoreNotebooks.contains(n.getNotebookGuid()))
1319                                 ignoreNote = true;
1320                         for (int i=0; i<n.getTagGuidsSize(); i++) {
1321                                 if (ignoreTags.contains(n.getTagGuids().get(i))) {
1322                                         ignoreNote = true;
1323                                         i=n.getTagGuidsSize();
1324                                 }
1325                         }
1326                                 
1327                         if ((conflictingNote || fullSync) && !ignoreNote) {
1328                                 logger.log(logger.EXTREME, "Saving Note");
1329                                 conn.getNoteTable().syncNote(n);
1330                                 // The following was commented out because it caused a race condition on the database where resources 
1331                                 // may be lost.  We do the same thing elsewhere;.
1332 //                              noteSignal.noteChanged.emit(n.getGuid(), null);   // Signal to ivalidate note cache 
1333                                 noteSignal.noteDownloaded.emit(n, true);                // Signal to add note to index
1334                                         logger.log(logger.EXTREME, "Note Saved");
1335                                 if (fullSync && n.getResources() != null) {
1336                                         for (int q=0; q<n.getResources().size() && keepRunning; q++) {
1337                                                 logger.log(logger.EXTREME, "Getting note resources.");
1338                                                 conn.getNoteTable().noteResourceTable.updateNoteResource(n.getResources().get(q), false);
1339                                                 if (n.getResources().get(q).getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
1340                                                         downloadInkNoteImage(n.getResources().get(q).getGuid(), token);
1341                                         }
1342                                 }
1343                         }
1344                 }
1345         }
1346         private Note getEvernoteNote(Client noteStore, String guid, boolean withContent, boolean withResourceData, boolean withResourceRecognition, boolean withResourceAlternateData, String token) { 
1347                 Note n = null;
1348                 try {
1349                         logger.log(logger.EXTREME, "Retrieving note " +guid);
1350                         n = noteStore.getNote(token, guid, withContent, withResourceData, withResourceRecognition, withResourceAlternateData);
1351                         logger.log(logger.EXTREME, "Note " +guid +" has been retrieved.");
1352                 } catch (EDAMUserException e) {
1353                         logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");
1354                         logger.log(logger.LOW, e.toString());   
1355                         error = true;
1356                         e.printStackTrace();
1357                 } catch (EDAMSystemException e) {
1358                         logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
1359                         logger.log(logger.LOW, e.toString());   
1360                         error = true;
1361                         e.printStackTrace();
1362                 } catch (EDAMNotFoundException e) {
1363                         logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
1364                         logger.log(logger.LOW, e.toString());   
1365                         error = true;
1366                         e.printStackTrace();
1367                 } catch (TException e) {
1368                         logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
1369                         logger.log(logger.LOW, e.toString());   
1370                         error = true;
1371                         e.printStackTrace();
1372                 }
1373                 return n;
1374         }
1375         private Resource getEvernoteResource(Client noteStore, String guid, boolean withData, boolean withRecognition, boolean withAttributes, String token) { 
1376                 Resource n = null;
1377                 try {
1378                         logger.log(logger.EXTREME, "Retrieving resource " +guid);
1379                         n = noteStore.getResource(token, guid, withData, withRecognition, withAttributes, withAttributes);
1380                         logger.log(logger.EXTREME, "Resource " +guid +" has been retrieved.");
1381                 } catch (EDAMUserException e) {
1382                         logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");
1383                         logger.log(logger.LOW, e.toString());   
1384                         error = true;
1385                         e.printStackTrace();
1386                 } catch (EDAMSystemException e) {
1387                         logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
1388                         logger.log(logger.LOW, e.toString());   
1389                         error = true;
1390                         e.printStackTrace();
1391                 } catch (EDAMNotFoundException e) {
1392                         logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
1393                         logger.log(logger.LOW, e.toString());   
1394                         error = true;
1395                         e.printStackTrace();
1396                 } catch (TException e) {
1397                         logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
1398                         logger.log(logger.LOW, e.toString());   
1399                         error = true;
1400                         e.printStackTrace();
1401                 }
1402                 return n;
1403         }
1404
1405         
1406         private boolean checkForConflict(Note n) {
1407                 logger.log(logger.EXTREME, "Checking note sequence number  " +n.getGuid());
1408                 Note oldNote = conn.getNoteTable().getNote(n.getGuid(), false, false, false, false, false);
1409                 logger.log(logger.EXTREME, "Local/Remote sequence numbers: " +oldNote.getUpdateSequenceNum()+"/"+n.getUpdateSequenceNum());
1410                 logger.log(logger.LOW, "Remote Note Title:" +n.getTitle());\r
1411                 logger.log(logger.LOW, "Local Note Title:" +oldNote.getTitle());\r
1412                 if (oldNote.getUpdateSequenceNum() == n.getUpdateSequenceNum()) {\r
1413                         return false;\r
1414                 } \r
1415                 boolean oldIsDirty = conn.getNoteTable().isNoteDirty(n.getGuid());\r
1416                 if (!oldIsDirty) \r
1417                         return false;
1418                 return true;
1419         }
1420         
1421         private void moveConflictingNote(String guid) {
1422                 logger.log(logger.EXTREME, "Conflicting change found for note " +guid);
1423                 List<Notebook> books = conn.getNotebookTable().getAllLocal();
1424                 String notebookGuid = null;
1425                 for (int i=0; i<books.size() && keepRunning; i++) {
1426                         if (books.get(i).getName().equalsIgnoreCase("Conflicting Changes (local)") ||
1427                                         books.get(i).getName().equalsIgnoreCase("Conflicting Changes")) {
1428                                 notebookGuid = books.get(i).getGuid();
1429                                 i=books.size();
1430                         }
1431                 }
1432                 
1433                 if (notebookGuid == null) {
1434                         logger.log(logger.EXTREME, "Building conflicting change notebook " +guid);
1435                         Calendar currentTime = new GregorianCalendar();
1436                         Long l = new Long(currentTime.getTimeInMillis());
1437                         long prevTime = l;
1438                         while (prevTime==l) {
1439                                 currentTime = new GregorianCalendar();
1440                                 l=currentTime.getTimeInMillis();
1441                         }
1442                         String randint = new String(Long.toString(l));
1443                 
1444                         Notebook newBook = new Notebook();
1445                         newBook.setUpdateSequenceNum(0);
1446                         newBook.setGuid(randint);
1447                         newBook.setName("Conflicting Changes");
1448                         newBook.setServiceCreated(new Date().getTime());
1449                         newBook.setServiceUpdated(new Date().getTime());
1450                         newBook.setDefaultNotebook(false);
1451                         newBook.setPublished(false);
1452                         
1453                         conn.getNotebookTable().addNotebook(newBook, false, true);
1454                         notebookSignal.listChanged.emit();
1455                         notebookGuid = newBook.getGuid();
1456                         refreshNeeded = true;
1457                 }
1458                 
1459                 // Now that we have a good notebook guid, we need to move the conflicting note
1460                 // to the local notebook
1461                 logger.log(logger.EXTREME, "Moving conflicting note " +guid);
1462                 Calendar currentTime = new GregorianCalendar();
1463                 Long l = new Long(currentTime.getTimeInMillis());
1464                 long prevTime = l;
1465                 while (prevTime==l) {
1466                         currentTime = new GregorianCalendar();
1467                         l = currentTime.getTimeInMillis();
1468                 }
1469                 String newGuid = new String(Long.toString(l));
1470                                         
1471                 Note oldNote = conn.getNoteTable().getNote(guid, true, true, false, false, false);
1472                 for (int i=0; i<oldNote.getResources().size() && keepRunning; i++) {
1473                         l = new Long(currentTime.getTimeInMillis());
1474                         String newResG = new String(Long.toString(l));
1475                         String oldResG = oldNote.getResources().get(i).getGuid();
1476                         conn.getNoteTable().noteResourceTable.resetUpdateSequenceNumber(oldResG, true);
1477                         conn.getNoteTable().noteResourceTable.updateNoteResourceGuid(oldResG, newResG, true);
1478                 }
1479                 
1480                 conn.getNoteTable().resetNoteSequence(guid);
1481                 conn.getNoteTable().updateNoteGuid(guid, newGuid);
1482                 conn.getNoteTable().updateNoteNotebook(newGuid, notebookGuid, true);
1483                 
1484                 noteSignal.notebookChanged.emit(newGuid, notebookGuid);
1485                 refreshNeeded = true;
1486                 noteSignal.guidChanged.emit(guid,newGuid);
1487         }
1488         
1489
1490
1491         
1492         //******************************************************
1493         //******************************************************
1494         //** Utility Functions
1495         //******************************************************
1496         //******************************************************
1497         // Convert a byte array to a hex string
1498         private static String byteArrayToHexString(byte data[]) {
1499                 StringBuffer buf = new StringBuffer();
1500             for (byte element : data) {
1501                 int halfbyte = (element >>> 4) & 0x0F;
1502                 int two_halfs = 0;
1503                 do {
1504                         if ((0 <= halfbyte) && (halfbyte <= 9))
1505                                buf.append((char) ('0' + halfbyte));
1506                            else
1507                                 buf.append((char) ('a' + (halfbyte - 10)));
1508                         halfbyte = element & 0x0F;
1509                 } while(two_halfs++ < 1);
1510             }
1511             return buf.toString();              
1512         }
1513
1514         
1515         
1516         //*******************************************************
1517         //* Find dirty tags, which do not have newly created parents
1518         //*******************************************************
1519         private Tag findNextTag() {
1520                 logger.log(logger.HIGH, "Entering SyncRunner.findNextTag");
1521                 Tag nextTag = null;
1522                 List<Tag> tags = conn.getTagTable().getDirty();
1523                 
1524                 // Find the parent.  If the parent has a sequence > 0 then it is a good
1525                 // parent.
1526                 for (int i=0; i<tags.size() && keepRunning; i++) {
1527                         if (!badTagSync.containsKey(tags.get(i).getGuid())) {
1528                                 if (tags.get(i).getParentGuid() == null) {
1529                                         logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found without parent");
1530                                         return tags.get(i);
1531                                 }
1532                                 Tag parentTag = conn.getTagTable().getTag(tags.get(i).getParentGuid());
1533                                 if (parentTag.getUpdateSequenceNum() > 0) {
1534                                         logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found");
1535                                         return tags.get(i);
1536                                 }
1537                         }
1538                 }
1539                 
1540                 logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - no tags returned");
1541                 return nextTag;
1542         }
1543         
1544         
1545            // Connect to Evernote
1546     public boolean enConnect()  {
1547         try {
1548                         userStoreTrans = new THttpClient(userStoreUrl);
1549                         userStoreTrans.setCustomHeader("User-Agent", userAgent);
1550                 } catch (TTransportException e) {
1551                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1552                         mb.exec();
1553                         e.printStackTrace();
1554                 }
1555                 userStoreProt = new TBinaryProtocol(userStoreTrans);
1556             userStore = new UserStore.Client(userStoreProt, userStoreProt);
1557             syncSignal.saveUserStore.emit(userStore);
1558             try {
1559                         //authResult = userStore.authenticate(username, password, consumerKey, consumerSecret);
1560                 user = userStore.getUser(authToken);
1561                 } catch (EDAMUserException e) {
1562                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Error", "Invalid Authorization");
1563                         mb.exec();
1564                         isConnected = false;
1565                         return false;
1566                 } catch (EDAMSystemException e) {
1567                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "EDAM System Excepton", e.getLocalizedMessage());
1568                         mb.exec();
1569                         e.printStackTrace();
1570                         isConnected = false;
1571                 } catch (TException e) {
1572                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1573                         mb.exec();
1574                         e.printStackTrace();
1575                         isConnected = false;
1576                 }
1577                 
1578             boolean versionOk = false;
1579                 try {
1580                         versionOk = userStore.checkVersion("NeighborNote", 
1581                     com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR, 
1582                       com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR);
1583                 } catch (TException e) {
1584                         e.printStackTrace();
1585                         isConnected = false;
1586                 } 
1587             if (!versionOk) { 
1588                 System.err.println("Incomatible EDAM client protocol version"); 
1589                 isConnected = false;
1590             }
1591             //if (authResult != null) {
1592                 //user = authResult.getUser(); 
1593                 //authToken = authResult.getAuthenticationToken(); 
1594             if (user == null || noteStoreUrlBase == null) {\r
1595                 logger.log(logger.LOW, "Error retrieving user information.  Aborting.");\r
1596                 System.err.println("Error retrieving user information.");\r
1597                 isConnected = false;    \r
1598                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, tr("Connection Error"), tr("Error retrieving user information.  Synchronization not complete"));\r
1599                         mb.exec();\r
1600                         return false;\r
1601                 \r
1602             }\r
1603                 noteStoreUrl = noteStoreUrlBase + user.getShardId();
1604                 syncSignal.saveAuthToken.emit(authToken);
1605                 syncSignal.saveNoteStore.emit(localNoteStore);
1606                 
1607          
1608                 try {
1609                         noteStoreTrans = new THttpClient(noteStoreUrl);
1610                         noteStoreTrans.setCustomHeader("User-Agent", userAgent);
1611                 } catch (TTransportException e) {
1612                         QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
1613                         mb.exec();
1614                         e.printStackTrace();
1615                         isConnected = false;
1616                 } 
1617                 noteStoreProt = new TBinaryProtocol(noteStoreTrans);
1618                 localNoteStore = 
1619                         new NoteStore.Client(noteStoreProt, noteStoreProt); 
1620                 isConnected = true;
1621                 //authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
1622                 //authRefreshTime = authTimeRemaining / 2;
1623             //}
1624             
1625                 // Get user information
1626                 try {
1627                         User user = userStore.getUser(authToken);
1628                         syncSignal.saveUserInformation.emit(user);
1629                 } catch (EDAMUserException e1) {
1630                         e1.printStackTrace();
1631                 } catch (EDAMSystemException e1) {
1632                         e1.printStackTrace();
1633                 } catch (TException e1) {
1634                         e1.printStackTrace();
1635                 }
1636             
1637             return isConnected;
1638     }
1639         // Disconnect from the database                         
1640     public void enDisconnect() {
1641         isConnected = false;
1642     }
1643     
1644     /*
1645     // Refresh the connection
1646     private synchronized boolean refreshConnection() {
1647         
1648                 logger.log(logger.EXTREME, "Entering SyncRunner.refreshConnection()");
1649 //        Calendar cal = Calendar.getInstance();
1650                 
1651         // If we are not connected let's get out of here
1652         if (!isConnected)
1653                 return false;
1654         
1655                 // If we fail too many times, then let's give up.
1656                 if (failedRefreshes >=5) {
1657                         logger.log(logger.EXTREME, "Refresh attempts have failed.  Disconnecting.");
1658                         isConnected = false;
1659                         status.message.emit(tr("Unable to synchronize - Authentication failed"));
1660                         return false;
1661                 }
1662         
1663                 // If this is the first time through, then we need to set this
1664 //              if (authRefreshTime == 0 || cal.getTimeInMillis() > authRefreshTime) 
1665 //                      authRefreshTime = cal.getTimeInMillis();
1666                 
1667  //             // Default to checking again in 5 min.  This in case we fail.
1668  //             authRefreshTime = authRefreshTime +(5*60*1000);     
1669
1670                 // Try to get a new token
1671                 AuthenticationResult newAuth = null; 
1672                 logger.log(logger.EXTREME, "Beginning to try authentication refresh");
1673         try {
1674                 if (userStore != null && authToken != null) 
1675                         newAuth = userStore.refreshAuthentication(authToken); 
1676                 else
1677                         return false;
1678                 logger.log(logger.EXTREME, "UserStore.refreshAuthentication has succeeded.");
1679                 } catch (EDAMUserException e) {
1680                         e.printStackTrace();
1681                         syncSignal.authRefreshComplete.emit(false);
1682                         failedRefreshes++;
1683                         return false;
1684                 } catch (EDAMSystemException e) {
1685                         e.printStackTrace();
1686                         syncSignal.authRefreshComplete.emit(false);
1687                         failedRefreshes++;
1688                         return false;           
1689                 } catch (TException e) { 
1690                         e.printStackTrace();
1691                         syncSignal.authRefreshComplete.emit(false);
1692                         failedRefreshes++;
1693                         return false;
1694                 }
1695                 
1696                 // If we didn't get a good auth, then we've failed
1697                 if (newAuth == null) {
1698                         failedRefreshes++;
1699                         status.message.emit(tr("Unable to synchronize - Authentication failed"));
1700                         logger.log(logger.EXTREME, "Authentication failure #" +failedRefreshes);
1701                         status.message.emit(tr("Unable to synchronize - Authentication failed"));
1702                         syncSignal.authRefreshComplete.emit(false);
1703                         return false;
1704                 }
1705                 
1706                 // We got a good token.  Now we need to setup the time to renew it.
1707                 logger.log(logger.EXTREME, "Saving authentication tokens");
1708                 authResult = newAuth;
1709                 authToken = new String(newAuth.getAuthenticationToken());
1710 //              authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
1711 //              authRefreshTime = cal.getTimeInMillis() + (authTimeRemaining/4);        
1712                 failedRefreshes=0;
1713                 syncSignal.authRefreshComplete.emit(true);
1714                 authRefreshNeeded = false;
1715                         
1716                 // This should never happen, but if it does we consider this a faild attempt.
1717 //              if (authTimeRemaining <= 0) {
1718 //                      failedRefreshes++;
1719 //                      syncSignal.authRefreshComplete.emit(false);
1720 //              }
1721                 
1722                 return true;
1723     }
1724     
1725     */
1726     
1727         public synchronized boolean addWork(String request) {
1728                 if (workQueue.offer(request))
1729                         return true;
1730                 return false;
1731         }
1732     
1733     private Note getNoteContent(Note n) {
1734                 QTextCodec codec = QTextCodec.codecForLocale();
1735                 codec = QTextCodec.codecForName("UTF-8");
1736         n.setContent(codec.toUnicode(new QByteArray(n.getContent())));
1737         return n;
1738     }
1739
1740
1741
1742     //*********************************************************
1743     //* Special download instructions.  Used for DB upgrades
1744     //*********************************************************
1745     private void downloadAllSharedNotebooks(Client noteStore) {
1746         try {
1747                         List<SharedNotebook> books = noteStore.listSharedNotebooks(authToken);
1748                         logger.log(logger.LOW, "Shared notebooks found = " +books.size());
1749                         for (int i=0; i<books.size(); i++) {
1750                                 conn.getSharedNotebookTable().updateNotebook(books.get(i), false);
1751                         }
1752                         conn.getSyncTable().deleteRecord("FullSharedNotebookSync");
1753                 } catch (EDAMUserException e1) {
1754                         e1.printStackTrace();
1755                         status.message.emit(tr("User exception Listing shared notebooks."));
1756                         logger.log(logger.LOW, e1.getMessage());
1757                         return;
1758                 } catch (EDAMSystemException e1) {
1759                         e1.printStackTrace();
1760                         status.message.emit(tr("System exception Listing shared notebooks."));
1761                         logger.log(logger.LOW, e1.getMessage());
1762                         return;
1763                 } catch (TException e1) {
1764                         e1.printStackTrace();
1765                         status.message.emit(tr("Transaction exception Listing shared notebooks."));
1766                         logger.log(logger.LOW, e1.getMessage());
1767                         return;
1768                 } catch (EDAMNotFoundException e1) {
1769                         e1.printStackTrace();
1770                         status.message.emit(tr("EDAM Not Found exception Listing shared notebooks."));
1771                         logger.log(logger.LOW, e1.getMessage());
1772                 }
1773     }
1774     private void downloadAllNotebooks(Client noteStore) {
1775         try {
1776                         List<Notebook> books = noteStore.listNotebooks(authToken);
1777                         logger.log(logger.LOW, "Shared notebooks found = " +books.size());
1778                         for (int i=0; i<books.size(); i++) {
1779                                 conn.getNotebookTable().updateNotebook(books.get(i), false);
1780                         }
1781                         conn.getSyncTable().deleteRecord("FullNotebookSync");
1782                 } catch (EDAMUserException e1) {
1783                         e1.printStackTrace();
1784                         status.message.emit(tr("User exception Listing notebooks."));
1785                         logger.log(logger.LOW, e1.getMessage());
1786                         return;
1787                 } catch (EDAMSystemException e1) {
1788                         e1.printStackTrace();
1789                         status.message.emit(tr("System exception Listing notebooks."));
1790                         logger.log(logger.LOW, e1.getMessage());
1791                         return;
1792                 } catch (TException e1) {
1793                         e1.printStackTrace();
1794                         status.message.emit(tr("Transaction exception Listing notebooks."));
1795                         logger.log(logger.LOW, e1.getMessage());
1796                         return;
1797                 }
1798     }
1799     private void downloadAllLinkedNotebooks(Client noteStore) {
1800         try {
1801                         List<LinkedNotebook> books = noteStore.listLinkedNotebooks(authToken);
1802                         logger.log(logger.LOW, "Linked notebooks found = " +books.size());
1803                         for (int i=0; i<books.size(); i++) {
1804                                 conn.getLinkedNotebookTable().updateNotebook(books.get(i), false);
1805                         }
1806                         conn.getSyncTable().deleteRecord("FullLinkedNotebookSync");
1807                 } catch (EDAMUserException e1) {
1808                         e1.printStackTrace();
1809                         status.message.emit(tr("User exception Listing linked notebooks."));
1810                         logger.log(logger.LOW, e1.getMessage());
1811                         return;
1812                 } catch (EDAMSystemException e1) {
1813                         e1.printStackTrace();
1814                         status.message.emit(tr("System exception Listing linked notebooks."));
1815                         logger.log(logger.LOW, e1.getMessage());
1816                         return;
1817                 } catch (TException e1) {
1818                         e1.printStackTrace();
1819                         status.message.emit(tr("Transaction exception Listing lineked notebooks."));
1820                         logger.log(logger.LOW, e1.getMessage());
1821                         return;
1822                 } catch (EDAMNotFoundException e1) {
1823                         e1.printStackTrace();
1824                         status.message.emit(tr("EDAM Not Found exception Listing linked notebooks."));
1825                         logger.log(logger.LOW, e1.getMessage());
1826                 }
1827     }
1828
1829     
1830     private void downloadInkNoteImage(String guid, String authToken) {
1831                 String urlBase = noteStoreUrl.replace("/edam/note/", "/shard/") + "/res/"+guid+".ink?slice=";
1832 //              urlBase = "https://www.evernote.com/shard/s1/res/52b567a9-54ae-4a08-afc5-d5bae275b2a8.ink?slice=";
1833                 Integer slice = 1;
1834                 Resource r = conn.getNoteTable().noteResourceTable.getNoteResource(guid, false);
1835                 conn.getInkImagesTable().expungeImage(r.getGuid());
1836                 int sliceCount = 1+((r.getHeight()-1)/480);
1837                 HttpClient http = new DefaultHttpClient();
1838         for (int i=0; i<sliceCount; i++) {
1839                 String url = urlBase + slice.toString();
1840                 HttpPost post = new HttpPost(url);
1841                 post.getParams().setParameter("auth", authToken);
1842                 List <NameValuePair> nvps = new ArrayList <NameValuePair>();
1843             nvps.add(new BasicNameValuePair("auth", authToken));
1844
1845             try {
1846                                 post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
1847                         } catch (UnsupportedEncodingException e1) {
1848                                 e1.printStackTrace();
1849                         }
1850                 try {
1851                         HttpResponse response = http.execute(post);
1852                         HttpEntity resEntity = response.getEntity();
1853                         InputStream is = resEntity.getContent();
1854                         QByteArray data = writeToFile(is);
1855                         conn.getInkImagesTable().saveImage(guid, slice, data);
1856                         } catch (ClientProtocolException e) {
1857                                 e.printStackTrace();
1858                         } catch (IOException e) {
1859                                 e.printStackTrace();
1860                         }
1861
1862                         slice++;
1863         }
1864         http.getConnectionManager().shutdown(); 
1865                 noteSignal.noteChanged.emit(r.getNoteGuid(), null);   // Signal to ivalidate note cache
1866     }
1867     
1868     
1869     public QByteArray writeToFile(InputStream iStream) throws IOException {
1870
1871             File temp = File.createTempFile("nn-inknote-temp", ".png");
1872
1873             // Save InputStream to the file.
1874             BufferedOutputStream fOut = null;
1875             try {
1876               fOut = new BufferedOutputStream(new FileOutputStream(temp));
1877               byte[] buffer = new byte[32 * 1024];
1878               int bytesRead = 0;
1879               while ((bytesRead = iStream.read(buffer)) != -1) {
1880                 fOut.write(buffer, 0, bytesRead);
1881               }
1882             }
1883             finally {
1884                 iStream.close();
1885                 fOut.close();
1886             }
1887             QFile tempFile = new QFile(temp.getAbsoluteFile().toString());
1888             tempFile.open(OpenModeFlag.ReadOnly);
1889             QByteArray data = tempFile.readAll();
1890             tempFile.close();
1891             tempFile.remove();
1892             return data;
1893     }
1894     
1895     
1896         //******************************************
1897         //* Begin syncing shared notebooks 
1898         //******************************************
1899     private void syncLinkedNotebooks() {
1900         logger.log(logger.MEDIUM, "Authenticating linked Notebooks");
1901         status.message.emit(tr("Synchronizing shared notebooks."));
1902         List<LinkedNotebook> books = conn.getLinkedNotebookTable().getAll();
1903         
1904         errorSharedNotebooks.clear();
1905                 
1906         for (int i=0; i<books.size(); i++) {
1907                 if (errorSharedNotebooksIgnored.containsKey(books.get(i).getGuid()))
1908                         break;
1909                 try {
1910                         logger.log(logger.EXTREME, "Checking notebook: " +books.get(i).getShareName());
1911                                 long lastSyncDate = conn.getLinkedNotebookTable().getLastSequenceDate(books.get(i).getGuid());
1912                                 int lastSequenceNumber = conn.getLinkedNotebookTable().getLastSequenceNumber(books.get(i).getGuid());
1913
1914                                 logger.log(logger.EXTREME, "Last Sequence Number on file: " +lastSequenceNumber);
1915                                 
1916                                 // Authenticate to the owner's shard
1917                                 String linkedNoteStoreUrl       = noteStoreUrlBase + books.get(i).getShardId();
1918                                 logger.log(logger.EXTREME, "linkedNoteStoreURL: " +linkedNoteStoreUrl);
1919                                 THttpClient linkedNoteStoreTrans        = new THttpClient(linkedNoteStoreUrl);
1920                                 TBinaryProtocol linkedNoteStoreProt     = new TBinaryProtocol(linkedNoteStoreTrans);
1921                                 Client linkedNoteStore = new NoteStore.Client(linkedNoteStoreProt, linkedNoteStoreProt);        
1922
1923                                 linkedAuthResult = null;
1924                                 if (books.get(i).getShareKey() != null) {
1925                                         logger.log(logger.EXTREME, "Share Key Not Null: " +books.get(i).getShareKey());
1926                                         linkedAuthResult = linkedNoteStore.authenticateToSharedNotebook(books.get(i).getShareKey(), authToken);
1927                                         logger.log(logger.EXTREME, "Authentication Token" +linkedAuthResult.getAuthenticationToken());
1928                                 } else {
1929                                         logger.log(logger.EXTREME, "Share key is null");
1930                                         linkedAuthResult = new AuthenticationResult();
1931                                         linkedAuthResult.setAuthenticationToken("");
1932                                 }
1933                                 SyncState linkedSyncState = 
1934                                         linkedNoteStore.getLinkedNotebookSyncState(linkedAuthResult.getAuthenticationToken(), books.get(i));
1935                                 if (linkedSyncState.getUpdateCount() > lastSequenceNumber) {
1936                                         logger.log(logger.EXTREME, "Remote changes found");
1937                                         if (lastSyncDate < linkedSyncState.getFullSyncBefore()) {
1938                                                 lastSequenceNumber = 0;
1939                                         } 
1940                                         logger.log(logger.EXTREME, "Calling syncLinkedNotebook for " +books.get(i).getShareName());
1941                                         syncLinkedNotebook(linkedNoteStore, books.get(i), 
1942                                                         lastSequenceNumber, linkedSyncState.getUpdateCount(), authToken);
1943                                 }
1944                         
1945                         // Synchronize local changes
1946                         syncLocalLinkedNoteChanges(linkedNoteStore, books.get(i));
1947                                 
1948                 } catch (EDAMUserException e) {
1949                         e.printStackTrace();
1950                 } catch (EDAMNotFoundException e) {
1951                         status.message.emit(tr("Error synchronizing \" " +
1952                                         books.get(i).getShareName()+"\". Please verify you still have access to that shared notebook."));
1953                         errorSharedNotebooks.add(books.get(i).getGuid());
1954                         errorSharedNotebooksIgnored.put(books.get(i).getGuid(), books.get(i).getGuid());
1955                         logger.log(logger.LOW, "Error synchronizing shared notebook.  EDAMNotFound: "+e.getMessage());
1956                         logger.log(logger.LOW, e.getStackTrace());
1957                         error = true;
1958                         e.printStackTrace();
1959                 } catch (EDAMSystemException e) {
1960                         error = true;
1961                         logger.log(logger.LOW, "System error authenticating against shared notebook. "+
1962                                         "Key: "+books.get(i).getShareKey() +" Error:" +e.getMessage());
1963                         e.printStackTrace();
1964                 } catch (TException e) {
1965                         error = true;
1966                         e.printStackTrace();
1967                 }
1968         }
1969         
1970         // Cleanup tags
1971         conn.getTagTable().removeUnusedLinkedTags();
1972         conn.getTagTable().cleanupTags();
1973         tagSignal.listChanged.emit();
1974         return;
1975         }
1976
1977     
1978     //**************************************************************
1979     //* Linked notebook contents (from someone else's account)
1980     //*************************************************************
1981         private void syncLinkedNotebook(Client linkedNoteStore, LinkedNotebook book, int usn, int highSequence, String token) {
1982                 logger.log(logger.EXTREME, "Entering syncLinkedNotebook");
1983                 if (ignoreLinkedNotebooks.contains(book.getGuid()))
1984                         return;
1985                 List<Note> dirtyNotes = conn.getNoteTable().getDirtyLinkedNotes();
1986                 if (dirtyNoteGuids == null) 
1987                         dirtyNoteGuids = new ArrayList<String>();\r
1988
1989                 for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
1990                         dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
1991                 }
1992                 boolean fullSync = false;
1993                 if (usn == 0)
1994                         fullSync = true;
1995                 boolean syncError = false;
1996                 while (usn < highSequence && !syncError) {
1997                         refreshNeeded = true;
1998                         try {
1999                                 SyncChunk chunk = 
2000                                         linkedNoteStore.getLinkedNotebookSyncChunk(token, book, usn, 10, fullSync);
2001                                 
2002                                 // Expunge notes
2003                                 syncExpungedNotes(chunk);
2004
2005                                 logger.log(logger.EXTREME, "Syncing remote notes: " +chunk.getNotesSize());
2006                                 syncRemoteNotes(linkedNoteStore, chunk.getNotes(), fullSync, linkedAuthResult.getAuthenticationToken());
2007                                 logger.log(logger.EXTREME, "Finding new linked tags");
2008                                 findNewLinkedTags(linkedNoteStore, chunk.getNotes(), linkedAuthResult.getAuthenticationToken());
2009                                 // Sync resources
2010                                 logger.log(logger.EXTREME, "Synchronizing tags: " +chunk.getTagsSize());
2011                                 for (int i=0; i<chunk.getResourcesSize(); i++) {
2012                                         syncRemoteResource(linkedNoteStore, chunk.getResources().get(i), linkedAuthResult.getAuthenticationToken());
2013                                 }
2014                                 logger.log(logger.EXTREME, "Synchronizing linked notebooks: " +chunk.getNotebooksSize());
2015                                 syncRemoteLinkedNotebooks(linkedNoteStore, chunk.getNotebooks(), false, book);
2016                                 syncLinkedTags(chunk.getTags(), book.getGuid());
2017                                 
2018                                 // Go through & signal any notes that have changed so we can refresh the user's view
2019                                 for (int i=0; i<chunk.getNotesSize(); i++) 
2020                                         noteSignal.noteChanged.emit(chunk.getNotes().get(i).getGuid(), null);
2021
2022                                 // Expunge Notebook records
2023                                 logger.log(logger.EXTREME, "Expunging linked notebooks: " +chunk.getExpungedLinkedNotebooksSize());
2024                                 for (int i=0; i<chunk.getExpungedLinkedNotebooksSize(); i++) {
2025                                         conn.getLinkedNotebookTable().expungeNotebook(chunk.getExpungedLinkedNotebooks().get(i), false);
2026                                 }
2027                                 usn = chunk.getChunkHighUSN();
2028                                 conn.getLinkedNotebookTable().setLastSequenceDate(book.getGuid(),chunk.getCurrentTime());
2029                                 conn.getLinkedNotebookTable().setLastSequenceNumber(book.getGuid(),chunk.getChunkHighUSN());
2030                         } catch (EDAMUserException e) {
2031                                 syncError = true;
2032                                 status.message.emit(tr("EDAM UserException synchronizing linked notbook.  See the log for datails."));
2033                                 e.printStackTrace();
2034                                 logger.log(logger.LOW, tr("EDAM UserException synchronizing linked notbook ")+ e.getMessage());
2035                         } catch (EDAMSystemException e) {
2036                                 syncError = true;
2037                                 status.message.emit(tr("EDAM SystemException synchronizing linked notbook.  See the log for datails."));
2038                                 e.printStackTrace();
2039                                 logger.log(logger.LOW, tr("EDAM SystemException synchronizing linked notbook.  See the log for datails") +e.getMessage());
2040                         } catch (EDAMNotFoundException e) {
2041                                 syncError = true;
2042                                 status.message.emit(tr("Notebook URL not found. Removing notobook ") +book.getShareName());
2043                                 conn.getNotebookTable().deleteLinkedTags(book.getGuid());
2044                                 conn.getLinkedNotebookTable().expungeNotebook(book.getGuid(), false);
2045                                 logger.log(logger.LOW, tr("Notebook URL not found. Removing notobook ") +e.getMessage());
2046                         } catch (TException e) {
2047                                 syncError = true;
2048                                 status.message.emit(tr("EDAM TException synchronizing linked notbook.  See the log for datails."));
2049                                 e.printStackTrace();
2050                                 logger.log(logger.LOW, tr("EDAM TException synchronizing linked notbook.  See the log for datails." )+e.getMessage());
2051                         }
2052                 }
2053                 logger.log(logger.EXTREME, "leaving syncLinkedNotebook");
2054         }
2055         // Sync remote tags
2056         private void syncLinkedTags(List<Tag> tags, String notebookGuid) {
2057                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
2058                 if (tags != null) {
2059                         for (int i=0; i<tags.size() && keepRunning; i++) {
2060                                 conn.getTagTable().syncLinkedTag(tags.get(i), notebookGuid, false);
2061                         }
2062                 }
2063                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
2064         }
2065         
2066         // Sync notebooks from a linked notebook
2067         private void syncRemoteLinkedNotebooks(Client noteStore, List<Notebook> notebooks, boolean readOnly, LinkedNotebook linked) {
2068                 logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");
2069                 if (notebooks != null) {
2070                         for (int i=0; i<notebooks.size() && keepRunning; i++) {
2071                                 try {
2072                                         logger.log(logger.EXTREME, "auth token:" +linkedAuthResult.getAuthenticationToken());
2073                                         if (!linkedAuthResult.getAuthenticationToken().equals("")) {
2074                                                 SharedNotebook s = noteStore.getSharedNotebookByAuth(linkedAuthResult.getAuthenticationToken());
2075                                                 logger.log(logger.EXTREME, "share key:"+s.getShareKey() +" notebookGuid" +s.getNotebookGuid());
2076                                                 conn.getLinkedNotebookTable().setNotebookGuid(s.getShareKey(), s.getNotebookGuid());
2077                                                 readOnly = !s.isNotebookModifiable();
2078                                         } else {
2079                                                 readOnly = true;
2080                                         }
2081                                         notebooks.get(i).setName(linked.getShareName());
2082                                         notebooks.get(i).setDefaultNotebook(false);
2083                                         conn.getNotebookTable().syncLinkedNotebook(notebooks.get(i), false, readOnly); 
2084                                 } catch (EDAMUserException e) {
2085                                         readOnly = true;
2086                                         e.printStackTrace();
2087                                 } catch (EDAMNotFoundException e) {
2088                                         readOnly = true;
2089                                         e.printStackTrace();
2090                                 } catch (EDAMSystemException e) {
2091                                         readOnly = true;
2092                                         e.printStackTrace();
2093                                 } catch (TException e) {
2094                                         readOnly = true;
2095                                         e.printStackTrace();
2096                                 }
2097
2098                         }
2099                 }                       
2100                 logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
2101         }
2102
2103         private void findNewLinkedTags(Client noteStore, List<Note> newNotes, String token) {
2104                 if (newNotes == null)
2105                         return;
2106                 for (int i=0; i<newNotes.size(); i++) {
2107                         Note n = newNotes.get(i);
2108                         for (int j=0; j<n.getTagGuidsSize(); j++) {
2109                                 String tag = n.getTagGuids().get(j);
2110                                 if (!conn.getTagTable().exists(tag)) {
2111                                         Tag newTag;
2112                                         try {
2113                                                 newTag = noteStore.getTag(token, tag);
2114                                                 conn.getTagTable().addTag(newTag, false);
2115                                         } catch (EDAMUserException e) {
2116                                                 e.printStackTrace();
2117                                         } catch (EDAMSystemException e) {
2118                                                 e.printStackTrace();
2119                                         } catch (EDAMNotFoundException e) {
2120                                                 e.printStackTrace();
2121                                         } catch (TException e) {
2122                                                 e.printStackTrace();
2123                                         }
2124                                         
2125                                 }
2126                         }
2127                 }
2128         }
2129
2130         // Synchronize changes locally done to linked notes
2131         private void syncLocalLinkedNoteChanges(Client noteStore, LinkedNotebook book) {
2132                 logger.log(logger.EXTREME, "Entering SyncRunner.synclocalLinkedNoteChanges");
2133                 String notebookGuid = conn.getLinkedNotebookTable().getNotebookGuid(book.getGuid());
2134                 logger.log(logger.EXTREME, "Finding changes for " +book.getShareName() +":" +book.getGuid() + ":" +notebookGuid);
2135                 List<Note> notes = conn.getNoteTable().getDirtyLinked(notebookGuid);
2136                 logger.log(logger.EXTREME, "Number of changes found: " +notes.size());
2137                 for (int i=0; i<notes.size(); i++) {
2138                         logger.log(logger.EXTREME, "Calling syncLocalNote with key " +linkedAuthResult.getAuthenticationToken());
2139                         syncLocalNote(noteStore, notes.get(i), linkedAuthResult.getAuthenticationToken());
2140                 }
2141                 logger.log(logger.EXTREME, "Leaving SyncRunner.synclocalLinkedNoteChanges");
2142         }
2143
2144 }