OSDN Git Service

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