cx.fbn.nevernote.threads.SyncRunner.java Source code

Java tutorial

Introduction

Here is the source code for cx.fbn.nevernote.threads.SyncRunner.java

Source

/*
 * This file is part of NixNote/NeighborNote 
 * Copyright 2009 Randy Baumgarte
 * Copyright 2013 Yuki Takahashi
 * 
 * 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 com.evernote.edam.error.EDAMErrorCode;
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.evernote.thrift.TException;
import com.evernote.thrift.protocol.TBinaryProtocol;
import com.evernote.thrift.transport.THttpClient;
import com.evernote.thrift.transport.TTransportException;
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.LimitSignal;
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 = "NeighborNote/" + 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 LimitSignal limitSignal;
    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;
    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 behaviorUrl;

    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 b, 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();
        limitSignal = new LimitSignal();
        resourceUrl = r;
        indexUrl = i;
        behaviorUrl = b;

        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, behaviorUrl, 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) {
                if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
                }

                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
            if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
            }
            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) {
            if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
            }
            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) {
            if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
            }
            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("NeighborNote",
                    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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
            if (e1.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                limitSignal.rateLimitReached.emit(e1.getRateLimitDuration());
            }
            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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                    limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                }
                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) {
                    if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                        limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                    }
                    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) {
                        if (e.getErrorCode() == EDAMErrorCode.RATE_LIMIT_REACHED) {
                            limitSignal.rateLimitReached.emit(e.getRateLimitDuration());
                        }
                        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");
    }

}