Java tutorial
/* * 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"); } }