OSDN Git Service

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