Package cx.fbn.nevernote.threads

Source Code of cx.fbn.nevernote.threads.SyncRunner

/*
* This file is part of NixNote
* Copyright 2009 Randy Baumgarte
*
* This file may be licensed under the terms of of the
* GNU General Public License Version 2 (the ``GPL'').
*
* Software distributed under the License is distributed
* on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
* express or implied. See the GPL for the specific language
* governing rights and limitations.
*
* You should have received a copy of the GPL along with this
* program. If not, go to http://www.gnu.org/licenses/gpl.html
* or write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
package cx.fbn.nevernote.threads;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.THttpClient;
import org.apache.thrift.transport.TTransportException;

import com.evernote.edam.error.EDAMNotFoundException;
import com.evernote.edam.error.EDAMSystemException;
import com.evernote.edam.error.EDAMUserException;
import com.evernote.edam.notestore.NoteStore;
import com.evernote.edam.notestore.NoteStore.Client;
import com.evernote.edam.notestore.SyncChunk;
import com.evernote.edam.notestore.SyncState;
import com.evernote.edam.type.Data;
import com.evernote.edam.type.LinkedNotebook;
import com.evernote.edam.type.Note;
import com.evernote.edam.type.Notebook;
import com.evernote.edam.type.Resource;
import com.evernote.edam.type.SavedSearch;
import com.evernote.edam.type.SharedNotebook;
import com.evernote.edam.type.Tag;
import com.evernote.edam.type.User;
import com.evernote.edam.userstore.AuthenticationResult;
import com.evernote.edam.userstore.UserStore;
import com.trolltech.qt.core.QByteArray;
import com.trolltech.qt.core.QFile;
import com.trolltech.qt.core.QIODevice.OpenModeFlag;
import com.trolltech.qt.core.QObject;
import com.trolltech.qt.core.QTextCodec;
import com.trolltech.qt.gui.QMessageBox;

import cx.fbn.nevernote.signals.NoteIndexSignal;
import cx.fbn.nevernote.signals.NoteResourceSignal;
import cx.fbn.nevernote.signals.NoteSignal;
import cx.fbn.nevernote.signals.NotebookSignal;
import cx.fbn.nevernote.signals.SavedSearchSignal;
import cx.fbn.nevernote.signals.StatusSignal;
import cx.fbn.nevernote.signals.SyncSignal;
import cx.fbn.nevernote.signals.TagSignal;
import cx.fbn.nevernote.sql.DatabaseConnection;
import cx.fbn.nevernote.sql.DeletedItemRecord;
import cx.fbn.nevernote.utilities.ApplicationLogger;

public class SyncRunner extends QObject implements Runnable {
 
  private final ApplicationLogger logger;
    private DatabaseConnection     conn;
    private boolean          idle;
    public boolean           error;
    public volatile List<String>  errorSharedNotebooks;
    public volatile HashMap<String,String>  errorSharedNotebooksIgnored;
    public volatile boolean      isConnected;
    public volatile boolean       keepRunning;
    public volatile String      authToken;
    private long          evernoteUpdateCount;
    private final String userAgent = "NixNote/" + System.getProperty("os.name")
                +"/"+System.getProperty("java.vendor") + "/"
                + System.getProperty("java.version") +";";
   
    public volatile NoteStore.Client     localNoteStore;
    private UserStore.Client        userStore;
   
    public volatile StatusSignal      status;
    public volatile TagSignal        tagSignal;
    public volatile NotebookSignal      notebookSignal;
    public volatile NoteIndexSignal      noteIndexSignal;
    public volatile NoteSignal        noteSignal;
    public volatile SavedSearchSignal    searchSignal;
    public volatile NoteResourceSignal    resourceSignal;
    public volatile SyncSignal        syncSignal;
    public volatile boolean          authRefreshNeeded;
    public volatile boolean          syncNeeded;
    public volatile boolean          disableUploads;
    public volatile boolean         syncDeletedContent;
    private volatile List<String>      dirtyNoteGuids;
   
      public volatile String username = "";
      public volatile String password = "";
    public volatile String userStoreUrl;
//      private final static String consumerKey = "baumgarte";
//      private final static String consumerSecret = "eb8b5740e17cb55f";
      public String noteStoreUrlBase;
      private THttpClient userStoreTrans;
      private TBinaryProtocol userStoreProt;
      //private AuthenticationResult authResult;
      private AuthenticationResult linkedAuthResult;
      private User user;
//      private long authTimeRemaining;
      public long authRefreshTime;
      public long failedRefreshes = 0;
      public  THttpClient noteStoreTrans;
      public TBinaryProtocol noteStoreProt;
      public String noteStoreUrl;
      public long sequenceDate;
      public int updateSequenceNumber;
      private boolean refreshNeeded;
      private volatile LinkedBlockingQueue<String> workQueue;
    private static int MAX_QUEUED_WAITING = 1000;
    String dbuid;
    String dburl;
    String indexUrl;
    String resourceUrl;
    String dbpswd;
    String dbcpswd;
    private final TreeSet<String> ignoreTags;
    private final TreeSet<String> ignoreNotebooks;
    private final TreeSet<String> ignoreLinkedNotebooks;
    private HashMap<String,String> badTagSync;
 
   
   
  public SyncRunner(String logname, String u, String i, String r, String uid, String pswd, String cpswd) {
    logger = new ApplicationLogger(logname);
   
    noteSignal = new NoteSignal();
    status = new StatusSignal();
    tagSignal = new TagSignal();
    notebookSignal = new NotebookSignal();
    noteIndexSignal = new NoteIndexSignal();
    noteSignal = new NoteSignal();
    searchSignal = new SavedSearchSignal();
    syncSignal = new SyncSignal();
    resourceSignal = new NoteResourceSignal();
    resourceUrl = r;
    indexUrl = i;
    dbuid = uid;
    dburl = u;
    dbpswd = pswd;
    dbcpswd = cpswd;
//    this.setAutoDelete(false);
   
    isConnected = false;
    syncNeeded = false;
    authRefreshNeeded = false;
    keepRunning = true;
    idle = true;
    disableUploads = false;
    ignoreTags = new TreeSet<String>();
    ignoreNotebooks = new TreeSet<String>();
    ignoreLinkedNotebooks = new TreeSet<String>();
   
//    setAutoDelete(false);
    workQueue=new LinkedBlockingQueue<String>(MAX_QUEUED_WAITING);
  }
  @Override
  public void run() {
    errorSharedNotebooks = new ArrayList<String>();
    errorSharedNotebooksIgnored = new HashMap<String,String>();
    try {
      logger.log(logger.EXTREME, "Starting thread");
      conn = new DatabaseConnection(logger, dburl, indexUrl, resourceUrl, dbuid, dbpswd, dbcpswd, 200);
      while(keepRunning) {
        logger.log(logger.EXTREME, "Blocking until work is found");
        String work = workQueue.take();
        logger.log(logger.LOW, "Dirty Notes Before Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
        logger.log(logger.EXTREME, "Work found: " +work);
        if (work.equalsIgnoreCase("stop")) {
          idle=false;
          return;
        }
        conn.getNoteTable().dumpDirtyNotes()// Debugging statement
        idle=false;
        error=false;
        if (syncNeeded) {
          logger.log(logger.EXTREME, "SyncNeeded is true");
          refreshNeeded=false;
          sequenceDate = conn.getSyncTable().getLastSequenceDate();
          updateSequenceNumber = conn.getSyncTable().getUpdateSequenceNumber();
          try {
            logger.log(logger.EXTREME, "Beginning sync");
            evernoteSync(localNoteStore);
            logger.log(logger.EXTREME, "Sync finished");
          } catch (UnknownHostException e) {
            status.message.emit(e.getMessage());
          }
        }
        idle=true;
        logger.log(logger.EXTREME, "Signaling refresh finished.  refreshNeeded=" +refreshNeeded);
        syncSignal.finished.emit(refreshNeeded);
        if (error) {
          syncSignal.errorDisconnect.emit();
          status.message.emit(tr("Error synchronizing - see log for details."));
        }
        logger.log(logger.LOW, "Dirty Notes After Sync: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
        conn.getNoteTable().dumpDirtyNotes();
        logger.log(logger.LOW, "---");
      }
    } 
    catch (InterruptedException e1) {
      e1.printStackTrace();
    }
    conn.dbShutdown();
  }

 
  public DatabaseConnection getConnection() {
    return conn;
  }

  public boolean isIdle() {
    return idle;
  }


  public void setConnected(boolean c) {
    isConnected = c;
  }
  public void setKeepRunning(boolean r) {
    logger.log(logger.EXTREME, "Setting keepRunning=" +r);
    keepRunning = r;
  }
  public void setNoteStore(NoteStore.Client c) {
    logger.log(logger.EXTREME, "Setting NoteStore in sync thread");
    localNoteStore = c;
  }
  public void setUserStore(UserStore.Client c) {
    logger.log(logger.EXTREME, "Setting UserStore in sync thread");
    userStore = c;
  }

  public void setEvernoteUpdateCount(long s) {
    logger.log(logger.EXTREME, "Setting Update Count in sync thread");
    evernoteUpdateCount = s;
  }
 
  //***************************************************************
    //***************************************************************
    //** These functions deal with Evernote communications
    //***************************************************************
    //***************************************************************
  // Synchronize changes with Evernote
  @SuppressWarnings("unused")
  private void evernoteSync(Client noteStore) throws java.net.UnknownHostException {
    logger.log(logger.HIGH, "Entering SyncRunner.evernoteSync");
   
    // Rebuild list of tags & notebooks to ignore
    ignoreNotebooks.clear();
    List<String> ignore = conn.getSyncTable().getIgnoreRecords("NOTEBOOK");
    for (int i=0; i<ignore.size(); i++)
      ignoreNotebooks.add(ignore.get(i));
   
    ignore.clear();
    ignore = conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK");
    for (int i=0; i<ignore.size(); i++)
      ignoreLinkedNotebooks.add(ignore.get(i));
   
    ignoreTags.clear();
    ignore = conn.getSyncTable().getIgnoreRecords("TAG");
    for (int i=0; i<ignore.size(); i++)
      ignoreTags.add(ignore.get(i));

    // Make sure we are connected & should keep running
    if (isConnected && keepRunning) {
      error = false;
      logger.log(logger.EXTREME, "Synchronizing with Evernote");
      status.message.emit(tr("Synchronizing with Evernote"));
     
      // Get user information
      try {
        logger.log(logger.EXTREME, "getting user from userstore");
        User user = userStore.getUser(authToken);
        logger.log(logger.EXTREME, "Saving user information");
        syncSignal.saveUserInformation.emit(user);
      } catch (EDAMUserException e1) {
        e1.printStackTrace();
        status.message.emit(tr("User exception getting user account information.  Aborting sync and disconnecting"));
        syncSignal.errorDisconnect.emit();
        error = true;
        enDisconnect();
        return;
      } catch (EDAMSystemException e1) {
        e1.printStackTrace();
        status.message.emit(tr("System error user account information.  Aborting sync and disconnecting!"));
        syncSignal.errorDisconnect.emit();
        error = true;
        enDisconnect();
        return;
      } catch (TException e1) {
        e1.printStackTrace();
        syncSignal.errorDisconnect.emit();
        error = true;
        status.message.emit(tr("Transaction error getting user account information.  Aborting sync and disconnecting!"));
        enDisconnect();
        return;
      }
     
      // Get sync state
      SyncState syncState = null;
      try
        logger.log(logger.EXTREME, "Getting sync state");
        syncState = noteStore.getSyncState(authToken)
        syncSignal.saveUploadAmount.emit(syncState.getUploaded());
        syncSignal.saveEvernoteUpdateCount.emit(syncState.getUpdateCount());
        evernoteUpdateCount = syncState.getUpdateCount();
      } catch (EDAMUserException e) {
        e.printStackTrace();
        status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
        syncSignal.errorDisconnect.emit();
        enDisconnect();
        return;
      } catch (EDAMSystemException e) {
        e.printStackTrace();
        status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
        syncSignal.errorDisconnect.emit();
        enDisconnect();
        return;
      } catch (TException e) {
        e.printStackTrace();
        status.message.emit(tr("Error getting sync state! Aborting sync and disconnecting!"));
        syncSignal.errorDisconnect.emit();
        enDisconnect();
        return;
      }
     
      if (syncState == null) {
        logger.log(logger.EXTREME, "Sync State is null");
        status.message.emit(tr("Syncronization Error!"));
        return;
      }

      // Determine what to do.
      // If we need to do a full sync.
      logger.log(logger.LOW, "Full Sequence Before: " +syncState.getFullSyncBefore());
      logger.log(logger.LOW, "Last Sequence Date: " +sequenceDate);
      logger.log(logger.LOW, "Var Last Sequence Number: " +updateSequenceNumber);
      logger.log(logger.LOW, "DB Last Sequence Number: " + conn.getSyncTable().getUpdateSequenceNumber());
      if (syncState.getFullSyncBefore() > sequenceDate) {
        logger.log(logger.EXTREME, "Full sequence date has expired");
        sequenceDate = 0;
        conn.getSyncTable().setLastSequenceDate(0);
        updateSequenceNumber = 0;
        conn.getSyncTable().setUpdateSequenceNumber(0);
      }
      // Check for "special" sync instructions
      String syncLinked = conn.getSyncTable().getRecord("FullLinkedNotebookSync");
      String syncShared = conn.getSyncTable().getRecord("FullSharedNotebookSync");
      String syncNotebooks = conn.getSyncTable().getRecord("FullNotebookSync");
      String syncInkNoteImages = conn.getSyncTable().getRecord("FullInkNoteImageSync");
      if (syncLinked != null) {
        downloadAllLinkedNotebooks(localNoteStore);
      }
      if (syncShared != null) {
        downloadAllSharedNotebooks(localNoteStore);
      }
      if (syncNotebooks != null) {
        downloadAllNotebooks(localNoteStore);
      }
     
      if (syncInkNoteImages != null) {
        List<String> guids = conn.getNoteTable().noteResourceTable.findInkNotes();
        for (int i=0; i<guids.size(); i++) {
          downloadInkNoteImage(guids.get(i), authToken);
        }
        conn.getSyncTable().deleteRecord("FullInkNoteImageSync");
      }
     
      // If there are remote changes
      logger.log(logger.LOW, "Update Count: " +syncState.getUpdateCount());
      logger.log(logger.LOW, "Last Update Count: " +updateSequenceNumber);
     
      if (syncState.getUpdateCount() > updateSequenceNumber) {
        logger.log(logger.EXTREME, "Refresh needed is true");
        refreshNeeded = true;
        logger.log(logger.EXTREME, "Downloading changes");
        syncRemoteToLocal(localNoteStore);
      }
     
      //*****************************************
      //* Sync linked/shared notebooks
      //*****************************************
      //syncLinkedNotebooks();
      //conn.getNoteTable().getDirty();
      //disableUploads = true;   /// DELETE THIS LINE!!!!
      if (!disableUploads) {
        logger.log(logger.EXTREME, "Uploading changes");
        // Synchronize remote changes
        if (!error)
          syncExpunged(localNoteStore);
        if (!error)
          syncLocalTags(localNoteStore);
        if (!error)
          syncLocalNotebooks(localNoteStore);
        if (!error)
          syncLocalLinkedNotebooks(localNoteStore);
        if (!error)
          syncDeletedNotes(localNoteStore);
        if (!error)
          syncLocalNotes();
        if (!error)
          syncLocalSavedSearches(localNoteStore);
      }
     
      status.message.emit(tr("Cleaning up"));
      List<String> notes = conn.getNoteTable().expungeIgnoreSynchronizedNotes(conn.getSyncTable().getIgnoreRecords("NOTEBOOK"),
          conn.getSyncTable().getIgnoreRecords("TAG"), conn.getSyncTable().getIgnoreRecords("LINKEDNOTEBOOK"));
      if (notes.size() > 0)
        syncSignal.refreshLists.emit();
     
      //*****************************************
      //* End of synchronization
      //*****************************************
      if (refreshNeeded)
        syncSignal.refreshLists.emit();
     
      if (!error) {
        logger.log(logger.LOW, "Sync completed.  Errors=" +error);
        if (!disableUploads)
          status.message.emit(tr("Synchronizing complete"));
        else
          status.message.emit(tr("Download syncronization complete.  Uploads have been disabled."));
       
        logger.log(logger.EXTREME, "Saving sync time");
        if (syncState.getCurrentTime() > sequenceDate)
          sequenceDate = syncState.getCurrentTime();
        if (syncState.getUpdateCount() > updateSequenceNumber)
          updateSequenceNumber = syncState.getUpdateCount();
        conn.getSyncTable().setLastSequenceDate(sequenceDate);
        conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
      }
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.evernoteSync");
  }
 
  // Sync deleted items with Evernote
  private void syncExpunged(Client noteStore) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncExpunged");
   
    List<DeletedItemRecord> expunged = conn.getDeletedTable().getAllDeleted();
     boolean error = false;
    for (int i=0; i<expunged.size() && keepRunning; i++) {

//      if (authRefreshNeeded)
//        if (!refreshConnection())
//          return;

      try {
        if (expunged.get(i).type.equalsIgnoreCase("TAG")) {
          logger.log(logger.EXTREME, "Tag expunged");
          conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "TAG")
          updateSequenceNumber = noteStore.expungeTag(authToken, expunged.get(i).guid);
          conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
          conn.getSyncTable().setLastSequenceDate(sequenceDate);
          conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);       
        }
        if   (expunged.get(i).type.equalsIgnoreCase("NOTEBOOK")) {
          logger.log(logger.EXTREME, "Notebook expunged");
          conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "NOTEBOOK");
          updateSequenceNumber = noteStore.expungeNotebook(authToken, expunged.get(i).guid);
          conn.getSyncTable().setLastSequenceDate(sequenceDate);
          conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
        }
        if (expunged.get(i).type.equalsIgnoreCase("NOTE")) {
          logger.log(logger.EXTREME, "Note expunged");
          conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "NOTE");
          updateSequenceNumber = noteStore.deleteNote(authToken, expunged.get(i).guid);
          refreshNeeded = true;
          conn.getSyncTable().setLastSequenceDate(sequenceDate);
          conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
        }
        if (expunged.get(i).type.equalsIgnoreCase("SAVEDSEARCH")) {
          logger.log(logger.EXTREME, "saved search expunged");
          conn.getDeletedTable().expungeDeletedItem(expunged.get(i).guid, "SAVEDSEARCH");
          updateSequenceNumber = noteStore.expungeSearch(authToken, expunged.get(i).guid);
          conn.getSyncTable().setLastSequenceDate(sequenceDate);
          conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
        }
      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "EDAM User Excepton in syncExpunged: " +expunged.get(i).guid);   // This can happen if we try to delete a deleted note
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "EDAM System Excepton in syncExpunged: "+expunged.get(i).guid);
        logger.log(logger.LOW, e.getStackTrace());
        error=true;
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "EDAM Not Found Excepton in syncExpunged: "+expunged.get(i).guid);
      } catch (TException e) {
        logger.log(logger.LOW, "EDAM TExcepton in syncExpunged: "+expunged.get(i).guid);
        logger.log(logger.LOW, e.getStackTrace());
        error=true;
      }
    }
    if (!error)
      conn.getDeletedTable().expungeAllDeletedRecords();
   
    logger.log(logger.HIGH, "Leaving SyncRunner.syncExpunged");

  }
  private void syncDeletedNotes(Client noteStore) {
    if (syncDeletedContent)
      return;
    logger.log(logger.HIGH, "Entering SyncRunner.syncDeletedNotes");
    status.message.emit(tr("Synchronizing deleted notes."));

    List<Note> notes = conn.getNoteTable().getDirty();
    // Sync the local notebooks with Evernote's
    for (int i=0; i<notes.size() && keepRunning; i++) {
     
//      if (authRefreshNeeded)
//        if (!refreshConnection())
//          return;
     
      Note enNote = notes.get(i);
      try {
        if (enNote.getUpdateSequenceNum() > 0 && (enNote.isActive() == false || enNote.getDeleted() > 0)) {
          // Check that the note is valid. 
          if (enNote.isActive() == true || enNote.getDeleted() == 0) {
            conn.getNoteTable().deleteNote(enNote.getGuid());
            enNote = conn.getNoteTable().getNote(enNote.getGuid(), false, false, false, false, false);
          }
          if (syncDeletedContent) {
            logger.log(logger.EXTREME, "Deleted note found & synch content selected");
            Note delNote = conn.getNoteTable().getNote(enNote.getGuid(), true, true, true, true, true);
            delNote = getNoteContent(delNote);
            delNote = noteStore.updateNote(authToken, delNote);
            enNote.setUpdateSequenceNum(delNote.getUpdateSequenceNum());
            conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
          } else {
            logger.log(logger.EXTREME, "Deleted note found & sync content not selected");
            int usn = noteStore.deleteNote(authToken, enNote.getGuid());
            enNote.setUpdateSequenceNum(usn);
            conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());           
          }
          logger.log(logger.EXTREME, "Resetting deleted dirty flag");
          conn.getNoteTable().resetDirtyFlag(enNote.getGuid());
          updateSequenceNumber = enNote.getUpdateSequenceNum();
          logger.log(logger.EXTREME, "Saving sequence number");
          conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
        }       
      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);
        //status.message.emit("Error sending local note: " +e.getParameter());
        //logger.log(logger.LOW, e.toString()); 
        //error = true;
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
        status.message.emit(tr("Error: ") +e);
        logger.log(logger.LOW, e.toString());   
        error = true;
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);
        //status.message.emit("Error deleting local note: " +e +" - Continuing");
        //logger.log(logger.LOW, e.toString());   
        //error = true;
      } catch (TException e) {
        logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);
        status.message.emit(tr("Error sending local note: ") +e);
        logger.log(logger.LOW, e.toString())
        error = true;
      }   
    }
  }
  // Sync notes with Evernote
  private void syncLocalNotes() {
    logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");
    logger.log(logger.LOW, "Dirty local notes found: " +new Integer(conn.getNoteTable().getDirtyCount()).toString());
    status.message.emit(tr("Sending local notes."));

    List<Note> notes = conn.getNoteTable().getDirty();
    // Sync the local notebooks with Evernote's
    for (int i=0; i<notes.size() && keepRunning; i++) {
      syncLocalNote(localNoteStore, notes.get(i), authToken);
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.syncNotes");

  }
  // Sync notes with Evernote
  private void syncLocalNote(Client noteStore, Note enNote, String token) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncNotes");
    status.message.emit(tr("Sending local notes."));
     
    if (enNote.isActive()) {
      try {
        if (enNote.getUpdateSequenceNum() > 0) {
          logger.log(logger.EXTREME, "Active dirty note found - non new - " +enNote.getGuid());
          logger.log(logger.EXTREME, "Fetching note content");
          enNote = getNoteContent(enNote);
          logger.log(logger.MEDIUM, "Updating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
          enNote = noteStore.updateNote(token, enNote);
        } else {
          logger.log(logger.EXTREME, "Active dirty found - new note " +enNote.getGuid());
          String oldGuid = enNote.getGuid();
          logger.log(logger.MEDIUM, "Fetching note content");
          enNote = getNoteContent(enNote);
          logger.log(logger.MEDIUM, "Creating note : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
          enNote = noteStore.createNote(token, enNote);
          logger.log(logger.MEDIUM, "New note Guid : "+ enNote.getGuid() +" <title>" +enNote.getTitle()+"</title>");
          noteSignal.guidChanged.emit(oldGuid, enNote.getGuid());
          conn.getNoteTable().updateNoteGuid(oldGuid, enNote.getGuid());
        }
        updateSequenceNumber = enNote.getUpdateSequenceNum();
        logger.log(logger.EXTREME, "Saving note");
        conn.getNoteTable().updateNoteSequence(enNote.getGuid(), enNote.getUpdateSequenceNum());
        List<Resource> rl = enNote.getResources();
        logger.log(logger.EXTREME, "Getting note resources");
        for (int j=0; j<enNote.getResourcesSize() && keepRunning; j++) {
          Resource newRes = rl.get(j);
          Data d = newRes.getData();
          if (d!=null) { 
            logger.log(logger.EXTREME, "Calculating resource hash");
            String hash = byteArrayToHexString(d.getBodyHash());
            logger.log(logger.EXTREME, "updating resources by hash");
            String oldGuid = conn.getNoteTable().noteResourceTable.getNoteResourceGuidByHashHex(enNote.getGuid(), hash);
            conn.getNoteTable().updateNoteResourceGuidbyHash(enNote.getGuid(), newRes.getGuid(), hash);
            resourceSignal.resourceGuidChanged.emit(enNote.getGuid(), oldGuid, newRes.getGuid());
          }
        }
        logger.log(logger.EXTREME, "Resetting note dirty flag");
        conn.getNoteTable().resetDirtyFlag(enNote.getGuid());
        updateSequenceNumber = enNote.getUpdateSequenceNum();
        logger.log(logger.EXTREME, "Emitting note sequence number change");
        conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);

      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotes "+e);
        status.message.emit(tr("Error sending local note: ")   +e.getParameter());
        logger.log(logger.LOW, e.toString())
        error = true;
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotes "+e);
        status.message.emit(tr("Error: ") +e);
        logger.log(logger.LOW, e.toString());   
        error = true;
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotes " +e);
        status.message.emit(tr("Error sending local note: ") +e);
        logger.log(logger.LOW, e.toString())
        error = true;
      } catch (TException e) {
        logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotes "+e);
        status.message.emit(tr("Error sending local note: ") +e);
        logger.log(logger.LOW, e.toString())
        error = true;
      }
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNote");

  }

  // Sync Notebooks with Evernote
  private void syncLocalNotebooks(Client noteStore) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncLocalNotebooks");
   
    status.message.emit(tr("Sending local notebooks."));
    List<Notebook> remoteList = new ArrayList<Notebook>();
    try {
      logger.log(logger.EXTREME, "Getting remote notebooks to compare with local");
      remoteList = noteStore.listNotebooks(authToken);
    } catch (EDAMUserException e1) {
      logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks getting remote Notebook List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString());   
      error = true;
    } catch (EDAMSystemException e1) {
      logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks getting remote Notebook List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString())
      error = true;
    } catch (TException e1) {
      logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalNotebooks getting remote Notebook List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString())
      error = true;
    }
    logger.log(logger.EXTREME, "Getting local dirty notebooks");
    List<Notebook> notebooks = conn.getNotebookTable().getDirty();
    int sequence;
    // Sync the local notebooks with Evernote's
    for (int i=0; i<notebooks.size() && keepRunning; i++) {
     
//      if (authRefreshNeeded)
//        if (!refreshConnection())
//          return;
     
      Notebook enNotebook = notebooks.get(i);
      try {
        if (enNotebook.getUpdateSequenceNum() > 0) {
          logger.log(logger.EXTREME, "Existing notebook is dirty");
          sequence = noteStore.updateNotebook(authToken, enNotebook);
        } else {
          logger.log(logger.EXTREME, "New dirty notebook found");
          String oldGuid = enNotebook.getGuid();
          boolean found = false;
         
          // Look for a notebook with the same name.  If one is found, we don't need
          // to create another one
          logger.log(logger.EXTREME, "Looking for matching notebook name");
          for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
            if (remoteList.get(k).getName().equalsIgnoreCase(enNotebook.getName())) {
              enNotebook = remoteList.get(k);
              logger.log(logger.EXTREME, "Matching notebook found");
              found = true;
            }
          }
          if (!found)
            enNotebook = noteStore.createNotebook(authToken, enNotebook);
         
          logger.log(logger.EXTREME, "Updating notebook in database");
          conn.getNotebookTable().updateNotebookGuid(oldGuid, enNotebook.getGuid());
          sequence = enNotebook.getUpdateSequenceNum();
        }
        logger.log(logger.EXTREME, "Updating notebook sequence in database");
        conn.getNotebookTable().updateNotebookSequence(enNotebook.getGuid(), sequence);
        logger.log(logger.EXTREME, "Resetting dirty flag in notebook");
        conn.getNotebookTable().resetDirtyFlag(enNotebook.getGuid());
        updateSequenceNumber = sequence;
        logger.log(logger.EXTREME, "Emitting sequence number to main thread");
        conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "*** EDAM User Excepton syncLocalNotebooks");
        logger.log(logger.LOW, e.toString() + ": Stack : " +enNotebook.getStack())
        error = true;
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "*** EDAM System Excepton syncLocalNotebooks");
        logger.log(logger.LOW, e.toString());   
        error = true;
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalNotebooks");
        logger.log(logger.LOW, e.toString());   
        error = true;
      } catch (TException e) {
        logger.log(logger.LOW, "*** EDAM TExcepton syncLocalNotebooks");
        logger.log(logger.LOW, e.toString())
        error = true;
      }   
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalNotebooks");

  }
  // Sync Tags with Evernote
  private void syncLocalTags(Client noteStore) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncLocalTags");
    List<Tag> remoteList = new ArrayList<Tag>();
    status.message.emit(tr("Sending local tags."));
   
    try {
      logger.log(logger.EXTREME, "Getting remote tags to compare names with the local tags");
      remoteList = noteStore.listTags(authToken);
    } catch (EDAMUserException e1) {
      logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote Tag List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString())
      error = true;
    } catch (EDAMSystemException e1) {
      logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote Tag List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString());   
      error = true;
    } catch (TException e1) {
      logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote Tag List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString())
      error = true;
    }   
   
    int sequence;
   
    if (badTagSync == null)
      badTagSync = new HashMap<String,String>();
    else
      badTagSync.clear();
   
    Tag enTag = findNextTag();
   
    // This is a hack.  Sometimes this function goes flookey and goes into a
    // perpetual loop.  This causes  NeverNote to flood Evernote's servers.
    // This is a safety valve to prevent unlimited loops.
    int maxCount = conn.getTagTable().getDirty().size()+10;
    int loopCount = 0;
   
    while(enTag!=null && loopCount < maxCount) {
      loopCount++;
//      if (authRefreshNeeded)
//        if (!refreshConnection())
//          return;

      try {
        if (enTag.getUpdateSequenceNum() > 0) {
          logger.log(logger.EXTREME, "Updating tag");
          sequence = noteStore.updateTag(authToken, enTag);
        } else {
         
          // Look for a tag with the same name.  If one is found, we don't need
          // to create another one
          logger.log(logger.EXTREME, "New tag.  Comparing with remote names");
          boolean found = false;
          String oldGuid = enTag.getGuid();
          for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
            if (remoteList.get(k).getName().equalsIgnoreCase(enTag.getName())) {
              conn.getTagTable().updateTagGuid(enTag.getGuid(), remoteList.get(k).getGuid());
              enTag = remoteList.get(k);
              logger.log(logger.EXTREME, "Matching tag name found");
              found = true;
            }
          }
          if (!found)
            enTag = noteStore.createTag(authToken, enTag);
          else
            enTag.setUpdateSequenceNum(noteStore.updateTag(authToken,enTag));
          sequence = enTag.getUpdateSequenceNum();
          if (!oldGuid.equals(enTag.getGuid())) {
            logger.log(logger.EXTREME, "Updating tag guid");
            conn.getTagTable().updateTagGuid(oldGuid, enTag.getGuid());
          }
        }
        logger.log(logger.EXTREME, "Updating tag sequence number");
        conn.getTagTable().updateTagSequence(enTag.getGuid(), sequence);
        logger.log(logger.EXTREME, "Resetting tag dirty flag");
        conn.getTagTable().resetDirtyFlag(enTag.getGuid());
        logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");
        updateSequenceNumber = sequence;
        conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags: " +enTag.getName());
        logger.log(logger.LOW, e.toString());
        badTagSync.put(enTag.getGuid(),null);
        error = true;
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags: " +enTag.getName());
        logger.log(logger.LOW, e.toString())
        badTagSync.put(enTag.getGuid(),null);
        error = true;
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags: " +enTag.getName());
        logger.log(logger.LOW, e.toString());
        badTagSync.put(enTag.getGuid(),null);
        error = true;
      } catch (TException e) {
        logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags: " +enTag.getName());
        logger.log(logger.LOW, e.toString());
        badTagSync.put(enTag.getGuid(),null);
        error = true;
     
     
      // Find the next tag
      logger.log(logger.EXTREME, "Finding next tag");
      enTag = findNextTag();
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalTags");
  }
  private void syncLocalLinkedNotebooks(Client noteStore) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncLocalLinkedNotebooks");
   
    List<String> list = conn.getLinkedNotebookTable().getDirtyGuids();
    for (int i=0; i<list.size(); i++) {
      LinkedNotebook book = conn.getLinkedNotebookTable().getNotebook(list.get(i));
      try {
        noteStore.updateLinkedNotebook(authToken, book);
      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "*** EDAM User Excepton syncLocalLinkedNotebooks");
        status.message.emit(tr("Error: ") +e);
        logger.log(logger.LOW, e.toString());   
        error = true;
        e.printStackTrace();
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalLinkedNotebooks");
        status.message.emit(tr("Error: ") +e);
        logger.log(logger.LOW, e.toString());   
        error = true;
        e.printStackTrace();
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "*** EDAM System Excepton syncLocalLinkedNotebooks");
        status.message.emit(tr("Error: ") +e);
        logger.log(logger.LOW, e.toString());   
        error = true;
        e.printStackTrace();
      } catch (TException e) {
        logger.log(logger.LOW, "*** EDAM TExcepton syncLocalLinkedNotebooks");
        status.message.emit(tr("Error: ") +e);
        logger.log(logger.LOW, e.toString());   
        error = true;
        e.printStackTrace();
      }
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.syncLocalLinkedNotebooks");
  }
  // Sync Saved Searches with Evernote
  private void syncLocalSavedSearches(Client noteStore) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
    List<SavedSearch> remoteList = new ArrayList<SavedSearch>();
    status.message.emit(tr("Sending saved searches."));
 
    logger.log(logger.EXTREME, "Getting saved searches to compare with local");
    try {
      remoteList = noteStore.listSearches(authToken);
    } catch (EDAMUserException e1) {
      logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags getting remote saved search List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString())
      error = true;
    } catch (EDAMSystemException e1) {
      logger.log(logger.LOW, "*** EDAM System Excepton syncLocalTags getting remote saved search List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString());   
      error = true;
    } catch (TException e1) {
      logger.log(logger.LOW, "*** EDAM Transaction Excepton syncLocalTags getting remote saved search List");
      status.message.emit(tr("Error: ") +e1);
      logger.log(logger.LOW, e1.toString())
      error = true;
    }   
   
    List<SavedSearch> searches = conn.getSavedSearchTable().getDirty();
    int sequence;
    // Sync the local notebooks with Evernote's
    logger.log(logger.EXTREME, "Beginning to send saved searches");
    for (int i=0; i<searches.size() &&  keepRunning; i++) {
     
//      if (authRefreshNeeded)
//        if (!refreshConnection())
//          return;
     
      SavedSearch enSearch = searches.get(i);
      try {
        if (enSearch.getUpdateSequenceNum() > 0)
          sequence = noteStore.updateSearch(authToken, enSearch);
        else {
          logger.log(logger.EXTREME, "New saved search found.");
          // Look for a tag with the same name.  If one is found, we don't need
          // to create another one
          boolean found = false;
          logger.log(logger.EXTREME, "Matching remote saved search names with local");
          for (int k=0; k<remoteList.size() && !found && keepRunning; k++) {
            if (remoteList.get(k).getName().equalsIgnoreCase(enSearch.getName())) {
              enSearch = remoteList.get(k);
              found = true;
              logger.log(logger.EXTREME, "Matching saved search found");
              sequence = enSearch.getUpdateSequenceNum();
            }
          }

          String oldGuid = enSearch.getGuid();
          if (!found)
            enSearch = noteStore.createSearch(authToken, enSearch);
          sequence = enSearch.getUpdateSequenceNum();
          logger.log(logger.EXTREME, "Updating tag guid in local database");
          conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, enSearch.getGuid());
        }
        logger.log(logger.EXTREME, "Updating tag sequence in local database");
        conn.getSavedSearchTable().updateSavedSearchSequence(enSearch.getGuid(), sequence);
        logger.log(logger.EXTREME, "Resetting tag dirty flag");
        conn.getSavedSearchTable().resetDirtyFlag(enSearch.getGuid());
        logger.log(logger.EXTREME, "Emitting sequence number to the main thread.");
        updateSequenceNumber = sequence;
        conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
      } catch (EDAMUserException e) {
        logger.log(logger.LOW, "*** EDAM User Excepton syncLocalTags");
        logger.log(logger.LOW, e.toString())
        error = true;
      } catch (EDAMSystemException e) {
        logger.log(logger.LOW, "** EDAM System Excepton syncLocalTags");
        logger.log(logger.LOW, e.toString())
        error = true;
      } catch (EDAMNotFoundException e) {
        logger.log(logger.LOW, "*** EDAM Not Found Excepton syncLocalTags");
        logger.log(logger.LOW, e.toString())
        error = true;
      } catch (TException e) {
        logger.log(logger.LOW, "*** EDAM TExcepton syncLocalTags");
        logger.log(logger.LOW, e.toString())
        error = true;
      }   
    }

    logger.log(logger.HIGH, "Entering SyncRunner.syncLocalSavedSearches");
 

  // Sync evernote changes with local database
  private void syncRemoteToLocal(Client noteStore) {
    logger.log(logger.HIGH, "Entering SyncRunner.syncRemoteToLocal");

    List<Note> dirtyNotes = conn.getNoteTable().getDirty();
    dirtyNoteGuids = new ArrayList<String>();
    for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
      dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
    }
   
    int chunkSize = 10;
    SyncChunk chunk = null;
    boolean fullSync = false;
    boolean more = true;
   
    if (updateSequenceNumber == 0)
      fullSync = true;
   
    status.message.emit(tr("Downloading 0% complete."));
   
    while(more &&  keepRunning) {
     
//      if (authRefreshNeeded)
//        if (!refreshConnection())
//          return;
     
      int sequence = updateSequenceNumber;
      try {
//        conn.beginTransaction();
        logger.log(logger.EXTREME, "Getting chunk from Evernote");
        chunk = noteStore.getSyncChunk(authToken, sequence, chunkSize, fullSync);
        logger.log(logger.LOW, "Chunk High Sequence: " +chunk.getChunkHighUSN());
      } catch (EDAMUserException e) {
        error = true;
        e.printStackTrace();
        status.message.emit(e.getMessage());
      } catch (EDAMSystemException e) {
        error = true;
        e.printStackTrace();
        status.message.emit(e.getMessage());
      } catch (TException e) {
        error = true;
        e.printStackTrace();
        status.message.emit(e.getMessage());
      }
      if (error || chunk == null)
        return;
       
   
     
      syncRemoteTags(chunk.getTags());
      syncRemoteSavedSearches(chunk.getSearches());
      syncRemoteNotebooks(chunk.getNotebooks());
      syncRemoteNotes(noteStore, chunk.getNotes(), fullSync, authToken);
      syncRemoteResources(noteStore, chunk.getResources());
      syncRemoteLinkedNotebooks(chunk.getLinkedNotebooks());
     
      // Signal about any updated notes to invalidate the cache
      for (int i=0; i<chunk.getNotesSize(); i++)
        noteSignal.noteChanged.emit(chunk.getNotes().get(i).getGuid(), null);
      syncExpungedNotes(chunk);
     
     
      // Check for more notes
      if (chunk.getChunkHighUSN() <= updateSequenceNumber)
        more = false;
      if (error)
        more = false;
      logger.log(logger.EXTREME, "More notes? " +more);

     
      // Save the chunk sequence number
      if (!error && chunk.getChunkHighUSN() > 0 && keepRunning) {
        logger.log(logger.EXTREME, "emitting sequence number to main thread");
        updateSequenceNumber = chunk.getChunkHighUSN();
        conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());
        conn.getSyncTable().setUpdateSequenceNumber(updateSequenceNumber);
//        conn.commitTransaction();
      }
     
     
      if (more) {
        long pct = chunk.getChunkHighUSN() * 100;
        conn.getSyncTable().setLastSequenceDate(chunk.getCurrentTime());
        pct = pct/evernoteUpdateCount;
        status.message.emit(tr("Downloading ") +new Long(pct).toString()+tr("% complete."));
      }
//      conn.commitTransaction();
    }
    logger.log(logger.HIGH, "Leaving SyncRunner.syncRemoteToLocal");
  }
  // Sync expunged notes
  private void syncExpungedNotes(SyncChunk chunk) {
    // Do the local deletes
    logger.log(logger.EXTREME, "Doing local deletes");
    List<String> guid = chunk.getExpungedNotes();
    if (guid != null) {
      for (int i=0; i<guid.size() && keepRunning; i++) {
        String notebookGuid = "";
        Note localNote = conn.getNoteTable().getNote(guid.get(i), false, false, false, false, false);
        if (localNote != null) {
          conn.getNoteTable().updateNoteSequence(guid.get(i), 0);
          notebookGuid = localNote.getNotebookGuid();
        }
        // If the note is in a local notebook (which means we moved it) or if the
        // note returned is null (which means it is already deleted or flagged expunged)
        // we delete it.
        if (!conn.getNotebookTable().isNotebookLocal(notebookGuid) || localNote == null) {
          logger.log(logger.EXTREME, "Expunging local note from database");
          conn.getNoteTable().expungeNote(guid.get(i), true, false);
        }
      }
    }
    guid = chunk.getExpungedNotebooks();
    if (guid != null)
      for (int i=0; i<guid.size() && keepRunning; i++) {
        logger.log(logger.EXTREME, "Expunging local notebook from database");
        conn.getNotebookTable().expungeNotebook(guid.get(i), false);
      }
    guid = chunk.getExpungedTags();
    if (guid != null)
      for (int i=0; i<guid.size() && keepRunning; i++) {
        logger.log(logger.EXTREME, "Expunging tags from local database");
        conn.getTagTable().expungeTag(guid.get(i), false);
      }
    guid = chunk.getExpungedSearches();
    if (guid != null)
      for (int i=0; i<guid.size() && keepRunning; i++) {
        logger.log(logger.EXTREME, "Expunging saved search from local database");
        conn.getSavedSearchTable().expungeSavedSearch(guid.get(i), false);
      }
    guid = chunk.getExpungedLinkedNotebooks();
    if (guid != null)
      for (int i=0; i<guid.size() && keepRunning; i++) {
        logger.log(logger.EXTREME, "Expunging linked notebook from local database");
        conn.getLinkedNotebookTable().expungeNotebook(guid.get(i), false);
      }

  }
  // Sync remote tags
  private void syncRemoteTags(List<Tag> tags) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
    if (tags != null) {
      for (int i=0; i<tags.size() && keepRunning; i++) {
        String oldGuid;
        oldGuid = conn.getTagTable().findTagByName(tags.get(i).getName());
        if (oldGuid != null && !tags.get(i).getGuid().equalsIgnoreCase(oldGuid))
          conn.getTagTable().updateTagGuid(oldGuid, tags.get(i).getGuid());
        conn.getTagTable().syncTag(tags.get(i), false);
      }
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
  }
  // Sync remote saved searches
  private void syncRemoteSavedSearches(List<SavedSearch> searches) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncSavedSearches");
    if (searches != null) {
      for (int i=0; i<searches.size() && keepRunning; i++) {
        String oldGuid;
        oldGuid = conn.getSavedSearchTable().findSavedSearchByName(searches.get(i).getName());
        if (oldGuid != null && !searches.get(i).getGuid().equalsIgnoreCase(oldGuid))
          conn.getSavedSearchTable().updateSavedSearchGuid(oldGuid, searches.get(i).getGuid());
        conn.getSavedSearchTable().syncSavedSearch(searches.get(i), false);
      }
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncSavedSearches");
  }
  // Sync remote linked notebooks
  private void syncRemoteLinkedNotebooks(List<LinkedNotebook> books) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncLinkedNotebooks");
    if (books != null) {
      for (int i=0; i<books.size() && keepRunning; i++) {
        conn.getLinkedNotebookTable().updateNotebook(books.get(i), false);
      }
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncLinkedNotebooks");
  }
  // Sync remote Notebooks 2
  private void syncRemoteNotebooks(List<Notebook> notebooks) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");
    if (notebooks != null) {
      for (int i=0; i<notebooks.size() && keepRunning; i++) {
        String oldGuid;
        oldGuid = conn.getNotebookTable().findNotebookByName(notebooks.get(i).getName());
        if (oldGuid != null && !conn.getNotebookTable().isNotebookLocal(oldGuid) && !notebooks.get(i).getGuid().equalsIgnoreCase(oldGuid))
          conn.getNotebookTable().updateNotebookGuid(oldGuid, notebooks.get(i).getGuid());
        conn.getNotebookTable().syncNotebook(notebooks.get(i), false);
       
        // Synchronize shared notebook information
//        if (notebooks.get(i).getSharedNotebookIdsSize() > 0) {
//          conn.getSharedNotebookTable().expungeNotebookByGuid(notebooks.get(i).getGuid(), false);
//          for (int j=0; j<notebooks.get(i).getSharedNotebookIdsSize(); j++) {
//            syncRemoteSharedNotebook(notebooks.get(i).getGuid(), notebooks.get(i).getSharedNotebookIds().get(j), authToken);
//          }
//        }
      }
    }     
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
  }
  // Sync remote shared notebook
//  private void syncRemoteSharedNotebook(String guid, Long id, String token) {
//    List<SharedNotebook> books = noteStore.getSharedNotebookByAuth(authToken);
//  }
  // Sync remote Resources
  private void syncRemoteResources(Client noteStore, List<Resource> resource) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteResources");
    if (resource != null) {
      for (int i=0; i<resource.size() && keepRunning; i++) {
        syncRemoteResource(noteStore, resource.get(i), authToken);
      }
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteResources");
  }
  // Sync remote resource
  private void syncRemoteResource(Client noteStore, Resource resource, String authToken) {
    // This is how the logic for this works.
    // 1.) If the resource is not in the local database, we add it.
    // 2.) If a copy of the resource is in the local database and the note isn't dirty, we update the local copy
    // 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
    //     is a conflict.  The note conflict should get a copy of the resource at that time.
   
    Note n = conn.getNoteTable().getNote(resource.getNoteGuid(), false, false, false, false, false);
    if (n!=null) {
      logger.log(logger.HIGH, "Resource for note " +n.getGuid() +" : " +n.getTitle());
    }
    boolean saveNeeded = false;
    /* #1 */    Resource r = getEvernoteResource(noteStore, resource.getGuid(), true,true,true, authToken);
            Resource l = conn.getNoteTable().noteResourceTable.getNoteResource(r.getGuid(), false);
            if (l == null) {
              logger.log(logger.HIGH, "Local resource not found");
              saveNeeded = true;
            } else {
    /* #2 */      boolean isNoteDirty = conn.getNoteTable().isNoteDirty(r.getNoteGuid());
              if (!isNoteDirty) {
                logger.log(logger.HIGH, "Local resource found, but is not dirty");
                saveNeeded = true;
              } else {
    /* #3 */        String remoteHash = "";
                if (r != null && r.getData() != null && r.getData().getBodyHash() != null)
                  remoteHash = byteArrayToHexString(r.getData().getBodyHash());
                String localHash = "";
                if (l != null && l.getData() != null && l.getData().getBodyHash() != null)
                  remoteHash = byteArrayToHexString(l.getData().getBodyHash());
           
                if (localHash.equalsIgnoreCase(remoteHash))
                  saveNeeded = true;
              }
            }
           
            logger.log(logger.HIGH, "Resource save needed: " +saveNeeded);
            if (saveNeeded)
              conn.getNoteTable().noteResourceTable.updateNoteResource(r, false);
            if (r.getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
              downloadInkNoteImage(r.getGuid(), authToken);
   

  }
  // Sync remote notes
  private void syncRemoteNotes(Client noteStore, List<Note> note, boolean fullSync, String token) {

    if (note != null) {
     
      logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotes");
      logger.log(logger.LOW, "Local Dirty Notes: ");
      logger.log(logger.LOW, "Remote Dirty Notes:");
      for (int i=0; i<note.size();i++) {
        logger.log(logger.LOW, i +" : " +note.get(i).getGuid() + " : " +note.get(i).getTitle() );
      }
      logger.log(logger.LOW, "---");
     
      for (int i=0; i<note.size() && keepRunning; i++) {
        Note n = getEvernoteNote(noteStore, note.get(i).getGuid(), true, fullSync, true,true, token);
        syncRemoteNote(n, fullSync, token);
      }
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotes");
  }
  private void syncRemoteNote(Note n, boolean fullSync, String token) {
    if (n!=null) {
     
      // Basically, this is how the sync logic for a note works.
      // If the remote note has changed and the local has not, we
      // accept the change.
      // If both the local & remote have changed but the sequence
      // numbers are the same, we don't accept the change.  This
      // seems to happen when attachments are indexed by the server.
      // If both the local & remote have changed and the sequence numbers
      // are different we move the local copy to a local notebook (making sure
      // to copy all resources) and we accept the new one.     
      boolean conflictingNote = true;
      logger.log(logger.EXTREME, "Checking for duplicate note " +n.getGuid() +" : " +n.getTitle());
      if (dirtyNoteGuids != null && dirtyNoteGuids.contains(n.getGuid())) {
        logger.log(logger.EXTREME, "Conflict check beginning");
        conflictingNote = checkForConflict(n);
        logger.log(logger.EXTREME, "Conflict check results " +conflictingNote);
        if (conflictingNote)
          moveConflictingNote(n.getGuid());
      }
      boolean ignoreNote = false;
      if (ignoreNotebooks.contains(n.getNotebookGuid()))
        ignoreNote = true;
      for (int i=0; i<n.getTagGuidsSize(); i++) {
        if (ignoreTags.contains(n.getTagGuids().get(i))) {
          ignoreNote = true;
          i=n.getTagGuidsSize();
        }
      }
       
      if ((conflictingNote || fullSync) && !ignoreNote) {
        logger.log(logger.EXTREME, "Saving Note");
        conn.getNoteTable().syncNote(n);
        // The following was commented out because it caused a race condition on the database where resources
        // may be lost.  We do the same thing elsewhere;.
//        noteSignal.noteChanged.emit(n.getGuid(), null);   // Signal to ivalidate note cache
        noteSignal.noteDownloaded.emit(n, true);    // Signal to add note to index
          logger.log(logger.EXTREME, "Note Saved");
        if (fullSync && n.getResources() != null) {
          for (int q=0; q<n.getResources().size() && keepRunning; q++) {
            logger.log(logger.EXTREME, "Getting note resources.");
            conn.getNoteTable().noteResourceTable.updateNoteResource(n.getResources().get(q), false);
            if (n.getResources().get(q).getMime().equalsIgnoreCase("application/vnd.evernote.ink"))
              downloadInkNoteImage(n.getResources().get(q).getGuid(), token);
          }
        }
      }
    }
  }
  private Note getEvernoteNote(Client noteStore, String guid, boolean withContent, boolean withResourceData, boolean withResourceRecognition, boolean withResourceAlternateData, String token) {
    Note n = null;
    try {
      logger.log(logger.EXTREME, "Retrieving note " +guid);
      n = noteStore.getNote(token, guid, withContent, withResourceData, withResourceRecognition, withResourceAlternateData);
      logger.log(logger.EXTREME, "Note " +guid +" has been retrieved.");
    } catch (EDAMUserException e) {
      logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    } catch (EDAMSystemException e) {
      logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    } catch (EDAMNotFoundException e) {
      logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    } catch (TException e) {
      logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    }
    return n;
  }
  private Resource getEvernoteResource(Client noteStore, String guid, boolean withData, boolean withRecognition, boolean withAttributes, String token) {
    Resource n = null;
    try {
      logger.log(logger.EXTREME, "Retrieving resource " +guid);
      n = noteStore.getResource(token, guid, withData, withRecognition, withAttributes, withAttributes);
      logger.log(logger.EXTREME, "Resource " +guid +" has been retrieved.");
    } catch (EDAMUserException e) {
      logger.log(logger.LOW, "*** EDAM User Excepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    } catch (EDAMSystemException e) {
      logger.log(logger.LOW, "*** EDAM System Excepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    } catch (EDAMNotFoundException e) {
      logger.log(logger.LOW, "*** EDAM Not Found Excepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    } catch (TException e) {
      logger.log(logger.LOW, "*** EDAM TExcepton getEvernoteNote");
      logger.log(logger.LOW, e.toString())
      error = true;
      e.printStackTrace();
    }
    return n;
  }

 
  private boolean checkForConflict(Note n) {
    logger.log(logger.EXTREME, "Checking note sequence number  " +n.getGuid());
    Note oldNote = conn.getNoteTable().getNote(n.getGuid(), false, false, false, false, false);
    logger.log(logger.EXTREME, "Local/Remote sequence numbers: " +oldNote.getUpdateSequenceNum()+"/"+n.getUpdateSequenceNum());
    logger.log(logger.LOW, "Remote Note Title:" +n.getTitle());
    logger.log(logger.LOW, "Local Note Title:" +oldNote.getTitle());
    if (oldNote.getUpdateSequenceNum() == n.getUpdateSequenceNum()) {
      return false;
    }
    boolean oldIsDirty = conn.getNoteTable().isNoteDirty(n.getGuid());
    if (!oldIsDirty)
      return false;
    return true;
  }
 
  private void moveConflictingNote(String guid) {
    logger.log(logger.EXTREME, "Conflicting change found for note " +guid);
    List<Notebook> books = conn.getNotebookTable().getAllLocal();
    String notebookGuid = null;
    for (int i=0; i<books.size() && keepRunning; i++) {
      if (books.get(i).getName().equalsIgnoreCase("Conflicting Changes (local)") ||
          books.get(i).getName().equalsIgnoreCase("Conflicting Changes")) {
        notebookGuid = books.get(i).getGuid();
        i=books.size();
      }
    }
   
    if (notebookGuid == null) {
      logger.log(logger.EXTREME, "Building conflicting change notebook " +guid);
      Calendar currentTime = new GregorianCalendar();
      Long l = new Long(currentTime.getTimeInMillis());
      long prevTime = l;
      while (prevTime==l) {
        currentTime = new GregorianCalendar();
        l=currentTime.getTimeInMillis();
      }
      String randint = new String(Long.toString(l));
   
      Notebook newBook = new Notebook();
      newBook.setUpdateSequenceNum(0);
      newBook.setGuid(randint);
      newBook.setName("Conflicting Changes");
      newBook.setServiceCreated(new Date().getTime());
      newBook.setServiceUpdated(new Date().getTime());
      newBook.setDefaultNotebook(false);
      newBook.setPublished(false);
     
      conn.getNotebookTable().addNotebook(newBook, false, true);
      notebookSignal.listChanged.emit();
      notebookGuid = newBook.getGuid();
      refreshNeeded = true;
    }
   
    // Now that we have a good notebook guid, we need to move the conflicting note
    // to the local notebook
    logger.log(logger.EXTREME, "Moving conflicting note " +guid);
    Calendar currentTime = new GregorianCalendar();
    Long l = new Long(currentTime.getTimeInMillis());
    long prevTime = l;
    while (prevTime==l) {
      currentTime = new GregorianCalendar();
      l = currentTime.getTimeInMillis();
    }
    String newGuid = new String(Long.toString(l));
         
    Note oldNote = conn.getNoteTable().getNote(guid, true, true, false, false, false);
    for (int i=0; i<oldNote.getResources().size() && keepRunning; i++) {
      l = new Long(currentTime.getTimeInMillis());
      String newResG = new String(Long.toString(l));
      String oldResG = oldNote.getResources().get(i).getGuid();
      conn.getNoteTable().noteResourceTable.resetUpdateSequenceNumber(oldResG, true);
      conn.getNoteTable().noteResourceTable.updateNoteResourceGuid(oldResG, newResG, true);
    }
   
    conn.getNoteTable().resetNoteSequence(guid);
    conn.getNoteTable().updateNoteGuid(guid, newGuid);
    conn.getNoteTable().updateNoteNotebook(newGuid, notebookGuid, true);
   
    noteSignal.notebookChanged.emit(newGuid, notebookGuid);
    refreshNeeded = true;
    noteSignal.guidChanged.emit(guid,newGuid);
  }
 


 
  //******************************************************
  //******************************************************
  //** Utility Functions
  //******************************************************
  //******************************************************
  // Convert a byte array to a hex string
  private static String byteArrayToHexString(byte data[]) {
    StringBuffer buf = new StringBuffer();
      for (byte element : data) {
        int halfbyte = (element >>> 4) & 0x0F;
          int two_halfs = 0;
          do {
             if ((0 <= halfbyte) && (halfbyte <= 9))
                   buf.append((char) ('0' + halfbyte));
               else
                 buf.append((char) ('a' + (halfbyte - 10)));
             halfbyte = element & 0x0F;
          } while(two_halfs++ < 1);
      }
      return buf.toString();   
  }

 
 
  //*******************************************************
  //* Find dirty tags, which do not have newly created parents
  //*******************************************************
  private Tag findNextTag() {
    logger.log(logger.HIGH, "Entering SyncRunner.findNextTag");
    Tag nextTag = null;
    List<Tag> tags = conn.getTagTable().getDirty();
   
    // Find the parent.  If the parent has a sequence > 0 then it is a good
    // parent.
    for (int i=0; i<tags.size() && keepRunning; i++) {
      if (!badTagSync.containsKey(tags.get(i).getGuid())) {
        if (tags.get(i).getParentGuid() == null) {
          logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found without parent");
          return tags.get(i);
        }
        Tag parentTag = conn.getTagTable().getTag(tags.get(i).getParentGuid());
        if (parentTag.getUpdateSequenceNum() > 0) {
          logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - tag found");
          return tags.get(i);
        }
      }
    }
   
    logger.log(logger.HIGH, "Leaving SyncRunner.findNextTag - no tags returned");
    return nextTag;
  }
 
 
     // Connect to Evernote
    public boolean enConnect()  {
      try {
      userStoreTrans = new THttpClient(userStoreUrl);
      userStoreTrans.setCustomHeader("User-Agent", userAgent);
    } catch (TTransportException e) {
      QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
      mb.exec();
      e.printStackTrace();
    }
    userStoreProt = new TBinaryProtocol(userStoreTrans);
      userStore = new UserStore.Client(userStoreProt, userStoreProt);
      syncSignal.saveUserStore.emit(userStore);
      try {
      //authResult = userStore.authenticate(username, password, consumerKey, consumerSecret);
        user = userStore.getUser(authToken);
    } catch (EDAMUserException e) {
      QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Error", "Invalid Authorization");
      mb.exec();
      isConnected = false;
      return false;
    } catch (EDAMSystemException e) {
      QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "EDAM System Excepton", e.getLocalizedMessage());
      mb.exec();
      e.printStackTrace();
      isConnected = false;
    } catch (TException e) {
      QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
      mb.exec();
      e.printStackTrace();
      isConnected = false;
    }
   
      boolean versionOk = false;
    try {
      versionOk = userStore.checkVersion("NixNote",
              com.evernote.edam.userstore.Constants.EDAM_VERSION_MAJOR,
                com.evernote.edam.userstore.Constants.EDAM_VERSION_MINOR);
    } catch (TException e) {
      e.printStackTrace();
      isConnected = false;
    }
      if (!versionOk) {
          System.err.println("Incomatible EDAM client protocol version");
          isConnected = false;
      }
      //if (authResult != null) {
        //user = authResult.getUser();
        //authToken = authResult.getAuthenticationToken();
      if (user == null || noteStoreUrlBase == null) {
        logger.log(logger.LOW, "Error retrieving user information.  Aborting.");
        System.err.println("Error retrieving user information.");
        isConnected = false
      QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, tr("Connection Error"), tr("Error retrieving user information.  Synchronization not complete"));
      mb.exec();
      return false;
       
      }
        noteStoreUrl = noteStoreUrlBase + user.getShardId();
        syncSignal.saveAuthToken.emit(authToken);
        syncSignal.saveNoteStore.emit(localNoteStore);
       
  
        try {
          noteStoreTrans = new THttpClient(noteStoreUrl);
          noteStoreTrans.setCustomHeader("User-Agent", userAgent);
        } catch (TTransportException e) {
          QMessageBox mb = new QMessageBox(QMessageBox.Icon.Critical, "Transport Excepton", e.getLocalizedMessage());
          mb.exec();
          e.printStackTrace();
          isConnected = false;
        }
        noteStoreProt = new TBinaryProtocol(noteStoreTrans);
        localNoteStore =
          new NoteStore.Client(noteStoreProt, noteStoreProt);
        isConnected = true;
        //authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
        //authRefreshTime = authTimeRemaining / 2;
      //}
     
    // Get user information
    try {
      User user = userStore.getUser(authToken);
      syncSignal.saveUserInformation.emit(user);
    } catch (EDAMUserException e1) {
      e1.printStackTrace();
    } catch (EDAMSystemException e1) {
      e1.printStackTrace();
    } catch (TException e1) {
      e1.printStackTrace();
    }
     
      return isConnected;
    }
  // Disconnect from the database       
    public void enDisconnect() {
      isConnected = false;
    }
   
    /*
    // Refresh the connection
    private synchronized boolean refreshConnection() {
     
    logger.log(logger.EXTREME, "Entering SyncRunner.refreshConnection()");
//        Calendar cal = Calendar.getInstance();
   
        // If we are not connected let's get out of here
        if (!isConnected)
          return false;
       
       // If we fail too many times, then let's give up.
       if (failedRefreshes >=5) {
         logger.log(logger.EXTREME, "Refresh attempts have failed.  Disconnecting.");
         isConnected = false;
         status.message.emit(tr("Unable to synchronize - Authentication failed"));
         return false;
       }
       
       // If this is the first time through, then we need to set this
//       if (authRefreshTime == 0 || cal.getTimeInMillis() > authRefreshTime)
//         authRefreshTime = cal.getTimeInMillis();
      
//      // Default to checking again in 5 min.  This in case we fail.
//      authRefreshTime = authRefreshTime +(5*60*1000);    

       // Try to get a new token
    AuthenticationResult newAuth = null;
    logger.log(logger.EXTREME, "Beginning to try authentication refresh");
      try {
        if (userStore != null && authToken != null)
          newAuth = userStore.refreshAuthentication(authToken);
        else
          return false;
        logger.log(logger.EXTREME, "UserStore.refreshAuthentication has succeeded.");
    } catch (EDAMUserException e) {
      e.printStackTrace();
      syncSignal.authRefreshComplete.emit(false);
      failedRefreshes++;
      return false;
    } catch (EDAMSystemException e) {
      e.printStackTrace();
      syncSignal.authRefreshComplete.emit(false);
      failedRefreshes++;
      return false;   
    } catch (TException e) {
      e.printStackTrace();
      syncSignal.authRefreshComplete.emit(false);
      failedRefreshes++;
      return false;
    }
   
    // If we didn't get a good auth, then we've failed
    if (newAuth == null) {
      failedRefreshes++;
      status.message.emit(tr("Unable to synchronize - Authentication failed"));
      logger.log(logger.EXTREME, "Authentication failure #" +failedRefreshes);
      status.message.emit(tr("Unable to synchronize - Authentication failed"));
      syncSignal.authRefreshComplete.emit(false);
      return false;
    }
   
    // We got a good token.  Now we need to setup the time to renew it.
    logger.log(logger.EXTREME, "Saving authentication tokens");
    authResult = newAuth;
    authToken = new String(newAuth.getAuthenticationToken());
//    authTimeRemaining = authResult.getExpiration() - authResult.getCurrentTime();
//    authRefreshTime = cal.getTimeInMillis() + (authTimeRemaining/4); 
    failedRefreshes=0;
    syncSignal.authRefreshComplete.emit(true);
    authRefreshNeeded = false;
     
    // This should never happen, but if it does we consider this a faild attempt.
//    if (authTimeRemaining <= 0) {
//      failedRefreshes++;
//      syncSignal.authRefreshComplete.emit(false);
//    }
   
    return true;
    }
   
    */
   
  public synchronized boolean addWork(String request) {
    if (workQueue.offer(request))
      return true;
    return false;
  }
   
    private Note getNoteContent(Note n) {
    QTextCodec codec = QTextCodec.codecForLocale();
    codec = QTextCodec.codecForName("UTF-8");
      n.setContent(codec.toUnicode(new QByteArray(n.getContent())));
      return n;
    }



    //*********************************************************
    //* Special download instructions.  Used for DB upgrades
    //*********************************************************
    private void downloadAllSharedNotebooks(Client noteStore) {
      try {
      List<SharedNotebook> books = noteStore.listSharedNotebooks(authToken);
      logger.log(logger.LOW, "Shared notebooks found = " +books.size());
      for (int i=0; i<books.size(); i++) {
        conn.getSharedNotebookTable().updateNotebook(books.get(i), false);
      }
      conn.getSyncTable().deleteRecord("FullSharedNotebookSync");
    } catch (EDAMUserException e1) {
      e1.printStackTrace();
      status.message.emit(tr("User exception Listing shared notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (EDAMSystemException e1) {
      e1.printStackTrace();
      status.message.emit(tr("System exception Listing shared notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (TException e1) {
      e1.printStackTrace();
      status.message.emit(tr("Transaction exception Listing shared notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (EDAMNotFoundException e1) {
      e1.printStackTrace();
      status.message.emit(tr("EDAM Not Found exception Listing shared notebooks."));
      logger.log(logger.LOW, e1.getMessage());
    }
    }
    private void downloadAllNotebooks(Client noteStore) {
      try {
      List<Notebook> books = noteStore.listNotebooks(authToken);
      logger.log(logger.LOW, "Shared notebooks found = " +books.size());
      for (int i=0; i<books.size(); i++) {
        conn.getNotebookTable().updateNotebook(books.get(i), false);
      }
      conn.getSyncTable().deleteRecord("FullNotebookSync");
    } catch (EDAMUserException e1) {
      e1.printStackTrace();
      status.message.emit(tr("User exception Listing notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (EDAMSystemException e1) {
      e1.printStackTrace();
      status.message.emit(tr("System exception Listing notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (TException e1) {
      e1.printStackTrace();
      status.message.emit(tr("Transaction exception Listing notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    }
    }
    private void downloadAllLinkedNotebooks(Client noteStore) {
      try {
      List<LinkedNotebook> books = noteStore.listLinkedNotebooks(authToken);
      logger.log(logger.LOW, "Linked notebooks found = " +books.size());
      for (int i=0; i<books.size(); i++) {
        conn.getLinkedNotebookTable().updateNotebook(books.get(i), false);
      }
      conn.getSyncTable().deleteRecord("FullLinkedNotebookSync");
    } catch (EDAMUserException e1) {
      e1.printStackTrace();
      status.message.emit(tr("User exception Listing linked notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (EDAMSystemException e1) {
      e1.printStackTrace();
      status.message.emit(tr("System exception Listing linked notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (TException e1) {
      e1.printStackTrace();
      status.message.emit(tr("Transaction exception Listing lineked notebooks."));
      logger.log(logger.LOW, e1.getMessage());
      return;
    } catch (EDAMNotFoundException e1) {
      e1.printStackTrace();
      status.message.emit(tr("EDAM Not Found exception Listing linked notebooks."));
      logger.log(logger.LOW, e1.getMessage());
    }
    }

   
    private void downloadInkNoteImage(String guid, String authToken) {
    String urlBase = noteStoreUrl.replace("/edam/note/", "/shard/") + "/res/"+guid+".ink?slice=";
//    urlBase = "https://www.evernote.com/shard/s1/res/52b567a9-54ae-4a08-afc5-d5bae275b2a8.ink?slice=";
    Integer slice = 1;
    Resource r = conn.getNoteTable().noteResourceTable.getNoteResource(guid, false);
    conn.getInkImagesTable().expungeImage(r.getGuid());
    int sliceCount = 1+((r.getHeight()-1)/480);
    HttpClient http = new DefaultHttpClient();
      for (int i=0; i<sliceCount; i++) {
        String url = urlBase + slice.toString();
        HttpPost post = new HttpPost(url);
        post.getParams().setParameter("auth", authToken);
        List <NameValuePair> nvps = new ArrayList <NameValuePair>();
            nvps.add(new BasicNameValuePair("auth", authToken));

            try {
        post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
      } catch (UnsupportedEncodingException e1) {
        e1.printStackTrace();
      }
        try {
          HttpResponse response = http.execute(post);
          HttpEntity resEntity = response.getEntity();
          InputStream is = resEntity.getContent();
          QByteArray data = writeToFile(is);
          conn.getInkImagesTable().saveImage(guid, slice, data);
      } catch (ClientProtocolException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }

      slice++;
      }
      http.getConnectionManager().shutdown();
    noteSignal.noteChanged.emit(r.getNoteGuid(), null);   // Signal to ivalidate note cache
    }
   
   
    public QByteArray writeToFile(InputStream iStream) throws IOException {

          File temp = File.createTempFile("nn-inknote-temp", ".png");

          // Save InputStream to the file.
          BufferedOutputStream fOut = null;
          try {
            fOut = new BufferedOutputStream(new FileOutputStream(temp));
            byte[] buffer = new byte[32 * 1024];
            int bytesRead = 0;
            while ((bytesRead = iStream.read(buffer)) != -1) {
              fOut.write(buffer, 0, bytesRead);
            }
          }
          finally {
              iStream.close();
              fOut.close();
          }
          QFile tempFile = new QFile(temp.getAbsoluteFile().toString());
          tempFile.open(OpenModeFlag.ReadOnly);
          QByteArray data = tempFile.readAll();
          tempFile.close();
          tempFile.remove();
          return data;
    }
   
   
  //******************************************
  //* Begin syncing shared notebooks
  //******************************************
    private void syncLinkedNotebooks() {
      logger.log(logger.MEDIUM, "Authenticating linked Notebooks");
      status.message.emit(tr("Synchronizing shared notebooks."));
      List<LinkedNotebook> books = conn.getLinkedNotebookTable().getAll();
     
      errorSharedNotebooks.clear();
   
      for (int i=0; i<books.size(); i++) {
        if (errorSharedNotebooksIgnored.containsKey(books.get(i).getGuid()))
          break;
        try {
          logger.log(logger.EXTREME, "Checking notebook: " +books.get(i).getShareName());
           long lastSyncDate = conn.getLinkedNotebookTable().getLastSequenceDate(books.get(i).getGuid());
           int lastSequenceNumber = conn.getLinkedNotebookTable().getLastSequenceNumber(books.get(i).getGuid());

           logger.log(logger.EXTREME, "Last Sequence Number on file: " +lastSequenceNumber);
          
           // Authenticate to the owner's shard
           String linkedNoteStoreUrl   = noteStoreUrlBase + books.get(i).getShardId();
           logger.log(logger.EXTREME, "linkedNoteStoreURL: " +linkedNoteStoreUrl);
           THttpClient linkedNoteStoreTrans   = new THttpClient(linkedNoteStoreUrl);
           TBinaryProtocol linkedNoteStoreProt   = new TBinaryProtocol(linkedNoteStoreTrans);
           Client linkedNoteStore = new NoteStore.Client(linkedNoteStoreProt, linkedNoteStoreProt);    

           linkedAuthResult = null;
           if (books.get(i).getShareKey() != null) {
             logger.log(logger.EXTREME, "Share Key Not Null: " +books.get(i).getShareKey());
             linkedAuthResult = linkedNoteStore.authenticateToSharedNotebook(books.get(i).getShareKey(), authToken);
             logger.log(logger.EXTREME, "Authentication Token" +linkedAuthResult.getAuthenticationToken());
           } else {
             logger.log(logger.EXTREME, "Share key is null");
             linkedAuthResult = new AuthenticationResult();
             linkedAuthResult.setAuthenticationToken("");
           }
           SyncState linkedSyncState =
             linkedNoteStore.getLinkedNotebookSyncState(linkedAuthResult.getAuthenticationToken(), books.get(i));
           if (linkedSyncState.getUpdateCount() > lastSequenceNumber) {
             logger.log(logger.EXTREME, "Remote changes found");
             if (lastSyncDate < linkedSyncState.getFullSyncBefore()) {
               lastSequenceNumber = 0;
             }
             logger.log(logger.EXTREME, "Calling syncLinkedNotebook for " +books.get(i).getShareName());
             syncLinkedNotebook(linkedNoteStore, books.get(i),
                 lastSequenceNumber, linkedSyncState.getUpdateCount(), authToken);
           }
         
          // Synchronize local changes
          syncLocalLinkedNoteChanges(linkedNoteStore, books.get(i));
       
        } catch (EDAMUserException e) {
          e.printStackTrace();
        } catch (EDAMNotFoundException e) {
          status.message.emit(tr("Error synchronizing \" " +
          books.get(i).getShareName()+"\". Please verify you still have access to that shared notebook."));
          errorSharedNotebooks.add(books.get(i).getGuid());
          errorSharedNotebooksIgnored.put(books.get(i).getGuid(), books.get(i).getGuid());
          logger.log(logger.LOW, "Error synchronizing shared notebook.  EDAMNotFound: "+e.getMessage());
          logger.log(logger.LOW, e.getStackTrace());
          error = true;
          e.printStackTrace();
        } catch (EDAMSystemException e) {
          error = true;
          logger.log(logger.LOW, "System error authenticating against shared notebook. "+
              "Key: "+books.get(i).getShareKey() +" Error:" +e.getMessage());
          e.printStackTrace();
        } catch (TException e) {
          error = true;
          e.printStackTrace();
        }
      }
     
      // Cleanup tags
      conn.getTagTable().removeUnusedLinkedTags();
      conn.getTagTable().cleanupTags();
      tagSignal.listChanged.emit();
      return;
  }

   
    //**************************************************************
    //* Linked notebook contents (from someone else's account)
    //*************************************************************
  private void syncLinkedNotebook(Client linkedNoteStore, LinkedNotebook book, int usn, int highSequence, String token) {
    logger.log(logger.EXTREME, "Entering syncLinkedNotebook");
    if (ignoreLinkedNotebooks.contains(book.getGuid()))
      return;
    List<Note> dirtyNotes = conn.getNoteTable().getDirtyLinkedNotes();
    if (dirtyNoteGuids == null)
      dirtyNoteGuids = new ArrayList<String>();

    for (int i=0; i<dirtyNotes.size() && keepRunning; i++) {
      dirtyNoteGuids.add(dirtyNotes.get(i).getGuid());
    }
    boolean fullSync = false;
    if (usn == 0)
      fullSync = true;
    boolean syncError = false;
    while (usn < highSequence && !syncError) {
      refreshNeeded = true;
      try {
        SyncChunk chunk =
          linkedNoteStore.getLinkedNotebookSyncChunk(token, book, usn, 10, fullSync);
       
        // Expunge notes
        syncExpungedNotes(chunk);

        logger.log(logger.EXTREME, "Syncing remote notes: " +chunk.getNotesSize());
        syncRemoteNotes(linkedNoteStore, chunk.getNotes(), fullSync, linkedAuthResult.getAuthenticationToken());
        logger.log(logger.EXTREME, "Finding new linked tags");
        findNewLinkedTags(linkedNoteStore, chunk.getNotes(), linkedAuthResult.getAuthenticationToken());
        // Sync resources
        logger.log(logger.EXTREME, "Synchronizing tags: " +chunk.getTagsSize());
        for (int i=0; i<chunk.getResourcesSize(); i++) {
          syncRemoteResource(linkedNoteStore, chunk.getResources().get(i), linkedAuthResult.getAuthenticationToken());
        }
        logger.log(logger.EXTREME, "Synchronizing linked notebooks: " +chunk.getNotebooksSize());
        syncRemoteLinkedNotebooks(linkedNoteStore, chunk.getNotebooks(), false, book);
        syncLinkedTags(chunk.getTags(), book.getGuid());
       
        // Go through & signal any notes that have changed so we can refresh the user's view
        for (int i=0; i<chunk.getNotesSize(); i++)
          noteSignal.noteChanged.emit(chunk.getNotes().get(i).getGuid(), null);

        // Expunge Notebook records
        logger.log(logger.EXTREME, "Expunging linked notebooks: " +chunk.getExpungedLinkedNotebooksSize());
        for (int i=0; i<chunk.getExpungedLinkedNotebooksSize(); i++) {
          conn.getLinkedNotebookTable().expungeNotebook(chunk.getExpungedLinkedNotebooks().get(i), false);
        }
        usn = chunk.getChunkHighUSN();
        conn.getLinkedNotebookTable().setLastSequenceDate(book.getGuid(),chunk.getCurrentTime());
        conn.getLinkedNotebookTable().setLastSequenceNumber(book.getGuid(),chunk.getChunkHighUSN());
      } catch (EDAMUserException e) {
        syncError = true;
        status.message.emit(tr("EDAM UserException synchronizing linked notbook.  See the log for datails."));
        e.printStackTrace();
        logger.log(logger.LOW, tr("EDAM UserException synchronizing linked notbook ")+ e.getMessage());
      } catch (EDAMSystemException e) {
        syncError = true;
        status.message.emit(tr("EDAM SystemException synchronizing linked notbook.  See the log for datails."));
        e.printStackTrace();
        logger.log(logger.LOW, tr("EDAM SystemException synchronizing linked notbook.  See the log for datails") +e.getMessage());
      } catch (EDAMNotFoundException e) {
        syncError = true;
        status.message.emit(tr("Notebook URL not found. Removing notobook ") +book.getShareName());
        conn.getNotebookTable().deleteLinkedTags(book.getGuid());
        conn.getLinkedNotebookTable().expungeNotebook(book.getGuid(), false);
        logger.log(logger.LOW, tr("Notebook URL not found. Removing notobook ") +e.getMessage());
      } catch (TException e) {
        syncError = true;
        status.message.emit(tr("EDAM TException synchronizing linked notbook.  See the log for datails."));
        e.printStackTrace();
        logger.log(logger.LOW, tr("EDAM TException synchronizing linked notbook.  See the log for datails." )+e.getMessage());
      }
    }
    logger.log(logger.EXTREME, "leaving syncLinkedNotebook");
  }
  // Sync remote tags
  private void syncLinkedTags(List<Tag> tags, String notebookGuid) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteTags");
    if (tags != null) {
      for (int i=0; i<tags.size() && keepRunning; i++) {
        conn.getTagTable().syncLinkedTag(tags.get(i), notebookGuid, false);
      }
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteTags");
  }
 
  // Sync notebooks from a linked notebook
  private void syncRemoteLinkedNotebooks(Client noteStore, List<Notebook> notebooks, boolean readOnly, LinkedNotebook linked) {
    logger.log(logger.EXTREME, "Entering SyncRunner.syncRemoteNotebooks");
    if (notebooks != null) {
      for (int i=0; i<notebooks.size() && keepRunning; i++) {
        try {
          logger.log(logger.EXTREME, "auth token:" +linkedAuthResult.getAuthenticationToken());
          if (!linkedAuthResult.getAuthenticationToken().equals("")) {
            SharedNotebook s = noteStore.getSharedNotebookByAuth(linkedAuthResult.getAuthenticationToken());
            logger.log(logger.EXTREME, "share key:"+s.getShareKey() +" notebookGuid" +s.getNotebookGuid());
            conn.getLinkedNotebookTable().setNotebookGuid(s.getShareKey(), s.getNotebookGuid());
            readOnly = !s.isNotebookModifiable();
          } else {
            readOnly = true;
          }
          notebooks.get(i).setName(linked.getShareName());
          notebooks.get(i).setDefaultNotebook(false);
          conn.getNotebookTable().syncLinkedNotebook(notebooks.get(i), false, readOnly);
        } catch (EDAMUserException e) {
          readOnly = true;
          e.printStackTrace();
        } catch (EDAMNotFoundException e) {
          readOnly = true;
          e.printStackTrace();
        } catch (EDAMSystemException e) {
          readOnly = true;
          e.printStackTrace();
        } catch (TException e) {
          readOnly = true;
          e.printStackTrace();
        }

      }
    }     
    logger.log(logger.EXTREME, "Leaving SyncRunner.syncRemoteNotebooks");
  }

  private void findNewLinkedTags(Client noteStore, List<Note> newNotes, String token) {
    if (newNotes == null)
      return;
    for (int i=0; i<newNotes.size(); i++) {
      Note n = newNotes.get(i);
      for (int j=0; j<n.getTagGuidsSize(); j++) {
        String tag = n.getTagGuids().get(j);
        if (!conn.getTagTable().exists(tag)) {
          Tag newTag;
          try {
            newTag = noteStore.getTag(token, tag);
            conn.getTagTable().addTag(newTag, false);
          } catch (EDAMUserException e) {
            e.printStackTrace();
          } catch (EDAMSystemException e) {
            e.printStackTrace();
          } catch (EDAMNotFoundException e) {
            e.printStackTrace();
          } catch (TException e) {
            e.printStackTrace();
          }
         
        }
      }
    }
  }

  // Synchronize changes locally done to linked notes
  private void syncLocalLinkedNoteChanges(Client noteStore, LinkedNotebook book) {
    logger.log(logger.EXTREME, "Entering SyncRunner.synclocalLinkedNoteChanges");
    String notebookGuid = conn.getLinkedNotebookTable().getNotebookGuid(book.getGuid());
    logger.log(logger.EXTREME, "Finding changes for " +book.getShareName() +":" +book.getGuid() + ":" +notebookGuid);
    List<Note> notes = conn.getNoteTable().getDirtyLinked(notebookGuid);
    logger.log(logger.EXTREME, "Number of changes found: " +notes.size());
    for (int i=0; i<notes.size(); i++) {
      logger.log(logger.EXTREME, "Calling syncLocalNote with key " +linkedAuthResult.getAuthenticationToken());
      syncLocalNote(noteStore, notes.get(i), linkedAuthResult.getAuthenticationToken());
    }
    logger.log(logger.EXTREME, "Leaving SyncRunner.synclocalLinkedNoteChanges");
  }

}
TOP

Related Classes of cx.fbn.nevernote.threads.SyncRunner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.