// Synchronize remote tags with the current database // If there is a conflict, the remote wins void SyncRunner::syncRemoteTags(QList<Tag> tags, qint32 account) { QLOG_TRACE() << "Entering SyncRunner::syncRemoteTags"; TagTable tagTable(db); for (int i = 0; i < tags.size() && keepRunning; i++) { Tag t = tags.at(i); // There are two ways to get the tag. We can get // it by name or by guid. We check both. We'll find it by // name if a new tag was created locally with the same name // as an unsynced remote. We then merge them. We'll find it by guid // if a note was synchrozied with this tag before a chunk // with this tag was downloaded. qint32 lid = tagTable.findByName(t.name, account); if (lid == 0) lid = tagTable.getLid(t.guid); if (lid > 0) { Tag currentTag; tagTable.get(currentTag, lid); QString tagname = ""; if (currentTag.name.isSet()) tagname = currentTag.name; QString tname = ""; if (t.name.isSet()) tname = t.name; if (tagname != tname) changedTags.insert(t.guid, t.name); lid = tagTable.sync(lid, t, account); } else { tagTable.sync(t, account); lid = tagTable.getLid(t.guid); changedTags.insert(t.guid, t.name); } QString parentGuid = ""; if (t.parentGuid.isSet()) parentGuid = t.parentGuid; if (!finalSync) { if (t.name.isSet()) emit tagUpdated(lid, t.name, parentGuid, account); else emit(tagUpdated(lid, "", parentGuid, account)); } } QLOG_TRACE() << "Leaving SyncRunner::syncRemoteTags"; }
// Load up the data from the database void NTagView::loadData() { // Empty out the old data store QList<qint32> keys = dataStore.keys(); for (int i=0; i<keys.size(); i++) { if (dataStore.contains(keys[i])) { NTagViewItem *ptr = dataStore.take(keys[i]); dataStore.remove(keys[i]); if (ptr->parent() != NULL) ptr->parent()->removeChild(ptr); QLOG_DEBUG() << ptr; ptr->setHidden(true); // delete ptr; << We can leak memory, but otherwise it sometimes gets confused and causes crashes } } NSqlQuery query(global.db); TagTable tagTable(global.db); query.exec("Select lid, name, parent_gid, account from TagModel order by name"); while (query.next()) { qint32 lid = query.value(0).toInt(); QString name = query.value(1).toString(); QString parentGid = query.value(2).toString(); qint32 account = query.value(3).toInt(); NTagViewItem *newWidget = new NTagViewItem(); newWidget->setData(NAME_POSITION, Qt::DisplayRole, name); newWidget->setData(NAME_POSITION, Qt::UserRole, lid); newWidget->account = account; if (account != accountFilter) newWidget->setHidden(true); else newWidget->setHidden(false); this->dataStore.insert(lid, newWidget); newWidget->parentGuid = parentGid; newWidget->parentLid = tagTable.getLid(parentGid); root->addChild(newWidget); } query.finish(); this->rebuildTree(); }
//*********************************************************** //* Process a <note> tag //*********************************************************** void BatchImport::addNoteNode() { Note note; note.title = QString(tr("Untitled Note")); QUuid uuid; QString newGuid = uuid.createUuid().toString().replace("{", "").replace("}", ""); note.guid = newGuid; QStringList tagNames; QStringList tagGuids; QString newNoteBody = QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")+ QString("<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">")+ QString("<en-note style=\"word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;\"><br/></en-note>"); note.active = true; note.content = newNoteBody; note.created = QDateTime::currentMSecsSinceEpoch(); note.updated = QDateTime::currentMSecsSinceEpoch(); bool atEnd = false; while(!atEnd) { QString name = reader->name().toString().toLower(); if (name == "title" && !reader->isEndElement()) { note.title = textValue(); } if (name == "created" && !reader->isEndElement()) { QString dateString = textValue(); //QDateTime date = QDateTime::fromString("2010-10-25T10:28:58.570Z", "yyyy-MM-ddTHH:mm:ss.zzzZ"); QDateTime date = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ss.zzzZ"); note.created = date.toMSecsSinceEpoch(); } if (name == "updated" && !reader->isEndElement()) { QString dateString = textValue(); QDateTime date = QDateTime::fromString(dateString, "yyyy-MM-ddTHH:mm:ss.zzzZ"); note.updated = date.toMSecsSinceEpoch(); } if (name == "notebook" && !reader->isEndElement()) { QString notebookName = textValue(); NotebookTable notebookTable(global.db); qint32 lid = notebookTable.findByName(notebookName); QString notebookGuid; // Do we need to add the notebook? if (lid == 0) { Notebook book; book.name = notebookName; QUuid uuid; QString newGuid = uuid.createUuid().toString().replace("{", "").replace("}", ""); book.guid = newGuid; notebookGuid = newGuid; lid = notebookTable.add(0, book, true, false); } else { notebookTable.getGuid(notebookGuid, lid); } note.notebookGuid = notebookGuid; } if (name == "content" && !reader->isEndElement()) { note.content = textValue(); } if (name == "tag" && !reader->isEndElement()) { QString tagName = textValue(); TagTable tagTable(global.db); qint32 tagLid = tagTable.findByName(tagName, 0); QString tagGuid; // Do we need to add the tag? if (tagLid == 0) { Tag tag; tag.name = tagName; QUuid uuid; tagGuid = uuid.createUuid().toString().replace("{", "").replace("}", ""); tag.guid = tagGuid; tagTable.add(0, tag, true, 0); } else { tagTable.getGuid(tagGuid, tagLid); } tagNames.append(tagName); tagGuids.append(tagGuid); } reader->readNext(); QString endName = reader->name().toString().toLower(); if (endName == "noteadd" && reader->isEndElement()) { atEnd = true; note.tagGuids = tagGuids; note.tagNames = tagNames; NoteTable ntable(global.db); if (!note.notebookGuid.isSet()) { NotebookTable bookTable(global.db); QString book = bookTable.getDefaultNotebookGuid(); note.notebookGuid = book; } ntable.add(0, note, true); } } return; }
void SyncRunner::evernoteSync() { QLOG_TRACE() << "Sync thread:" << QThread::currentThreadId(); if (!global.connected) return; User user; UserTable userTable(db); if (!comm->getUserInfo(user)) { this->communicationErrorHandler(); error =true; return; } userTable.updateUser(user); SyncState syncState; if (!comm->getSyncState("", syncState)) { this->communicationErrorHandler(); error = true; return; } fullSync = false; qlonglong lastSyncDate = userTable.getLastSyncDate(); updateSequenceNumber = userTable.getLastSyncNumber(); if ((syncState.fullSyncBefore/1000) > lastSyncDate) { QLOG_DEBUG() << "Full sequence date has expired"; lastSyncDate = 0; fullSync = true; } if (updateSequenceNumber == 0) fullSync = true; emit setMessage(tr("Beginning Sync"), defaultMsgTimeout); // If there are remote changes QLOG_DEBUG() << "--->>> Current Chunk High Sequence Number: " << syncState.updateCount; QLOG_DEBUG() << "--->>> Last User High Sequence Number: " << updateSequenceNumber; if (syncState.updateCount > updateSequenceNumber) { QLOG_DEBUG() << "Remote changes found"; QLOG_DEBUG() << "Downloading changes"; emit setMessage(tr("Downloading changes"), defaultMsgTimeout); bool rc = syncRemoteToLocal(syncState.updateCount); if (!rc) error = true; } if (!comm->getUserInfo(user)) { this->communicationErrorHandler(); error = true; return; } userTable.updateUser(user); if (!global.disableUploads && !error) { qint32 searchUsn = uploadSavedSearches(); if (searchUsn > updateSequenceNumber) updateSequenceNumber = searchUsn; qint32 tagUsn = uploadTags(); if (tagUsn > updateSequenceNumber) updateSequenceNumber = tagUsn; qint32 notebookUsn = uploadNotebooks(); if (notebookUsn > updateSequenceNumber) updateSequenceNumber = notebookUsn; qint32 personalNotesUsn = uploadPersonalNotes(); if (personalNotesUsn > updateSequenceNumber) updateSequenceNumber = personalNotesUsn; } // Synchronize linked notebooks if (!error && !syncRemoteLinkedNotebooksActual()) error = true; if (error || !comm->getSyncState("", syncState)) { error =true; this->communicationErrorHandler(); return; } userTable.updateSyncState(syncState); // Cleanup any missing parent tags QList<qint32> lids; TagTable tagTable(db); tagTable.findMissingParents(lids); for (int i=0; i<lids.size(); i++) { if (!finalSync) emit(tagExpunged(lids[i])); } tagTable.cleanupMissingParents(); if (!error) emit setMessage(tr("Sync Complete Successfully"), defaultMsgTimeout); QLOG_TRACE() << "Leaving SyncRunner::evernoteSync()"; }
// Synchronize remote linked notebooks bool SyncRunner::syncRemoteLinkedNotebooksActual() { LinkedNotebookTable ltable(db); QList<qint32> lids; ltable.getAll(lids); bool fs; for (int i=0; i<lids.size(); i++) { LinkedNotebook book; qint32 usn = ltable.getLastUpdateSequenceNumber(lids[i]); qint32 startingUSN = usn; ltable.get(book, lids[i]); int chunkSize = 5000; // If the share key is set, we need to authenticate if (!comm->authenticateToLinkedNotebookShard(book)) { this->communicationErrorHandler(); error = true; return false; } bool more = true; SyncState syncState; if (!comm->getLinkedNotebookSyncState(syncState, book)) { this->communicationErrorHandler(); error = true; return false; } if (syncState.updateCount <= usn) more=false; qint32 startingSequenceNumber = usn; if (usn == 0) fs = true; else fs = false; // ***** STARTING PASS #1 while (more && keepRunning) { SyncChunk chunk; if (!comm->getLinkedNotebookSyncChunk(chunk, book, usn, chunkSize, fs)) { more = false; if (comm->error.type == CommunicationError::EDAMNotFoundException) { ltable.expunge(lids[i]); if (!finalSync) emit(notebookExpunged(lids[i])); } else { this->communicationErrorHandler(); error = true; return false; } } else { processSyncChunk(chunk, lids[i]); usn = chunk.chunkHighUSN; if (chunk.updateCount > 0 && chunk.updateCount > startingSequenceNumber) { int pct = (usn-startingSequenceNumber)*100/(chunk.updateCount-startingSequenceNumber); QString sharename = ""; if (book.shareName.isSet()) sharename = book.shareName; emit setMessage(tr("Downloading ") +QString::number(pct) + tr("% complete for tags in shared notebook ") +sharename + tr("."), defaultMsgTimeout); } if (!chunk.chunkHighUSN.isSet()|| chunk.chunkHighUSN >= chunk.updateCount) more = false; } } //************* STARTING PASS 2 usn = startingUSN; more = true; chunkSize = 50; if (error == true) more=false; QString sharename = ""; if (book.shareName.isSet()) sharename = book.shareName; emit setMessage(tr("Downloading notes for shared notebook ") +sharename + tr("."), defaultMsgTimeout); while (more && keepRunning) { SyncChunk chunk; if (!comm->getLinkedNotebookSyncChunk(chunk, book, usn, chunkSize, fs)) { more = false; if (comm->error.type == CommunicationError::EDAMNotFoundException) { ltable.expunge(lids[i]); if (!finalSync) emit(notebookExpunged(lids[i])); } else { this->communicationErrorHandler(); error = true; return false; } } else { processSyncChunk(chunk, lids[i]); usn = chunk.chunkHighUSN; if (chunk.updateCount > 0 && chunk.updateCount > startingSequenceNumber) { int pct = (usn-startingSequenceNumber)*100/(chunk.updateCount-startingSequenceNumber); QString sharename = ""; if (book.shareName.isSet()) sharename = book.shareName; emit setMessage(tr("Downloading ") +QString::number(pct) + tr("% complete for shared notebook ") +sharename + tr("."), defaultMsgTimeout); } if (!chunk.chunkHighUSN.isSet() || chunk.chunkHighUSN >= chunk.updateCount) { more = false; ltable.setLastUpdateSequenceNumber(lids[i], syncState.updateCount); } } } qint32 noteUSN = uploadLinkedNotes(lids[i]); if (noteUSN > usn) ltable.setLastUpdateSequenceNumber(lids[i], noteUSN); } TagTable tagTable(db); tagTable.cleanupLinkedTags(); return true; }
// Do the alter. If the notebook is not found we create it. // If a tag is not found for an add, we add it. If a tag is // not found for a deleteTag, we just ignore it. int AlterNote::alterNote() { // If a query is specified, we find the matching notes. if (query != "") { FilterCriteria *filter = new FilterCriteria(); global.filterCriteria.append(filter); global.filterPosition = 0; FilterEngine engine; filter->setSearchString(query); QList<qint32> lids; engine.filter(filter, &lids); this->lids.append(lids); } NotebookTable bookTable(global.db); TagTable tagTable(global.db); NoteTable noteTable(global.db); // Loop through each note requested. for (int i=0; i<lids.size(); i++) { qint32 lid = lids[i]; // Do the notebook request if (notebook != "") { qint32 notebookLid = bookTable.findByName(notebook); if (notebookLid<0) { Notebook book; book.name = notebook; NUuid uuid; book.guid = uuid.create(); notebookLid = bookTable.add(0,book,true,true); } if (noteTable.getNotebookLid(lid) != notebookLid) noteTable.updateNotebook(lid, notebookLid, true); } // Add the tags for (int j=0; j<addTagNames.size(); j++) { qint32 tagLid = tagTable.findByName(addTagNames[j],0); if (tagLid <= 0) { Tag t; t.name = addTagNames[j]; NUuid uuid; t.guid = uuid.create(); tagLid = tagTable.add(0,t,true,0); } if (!noteTable.hasTag(lid,tagLid)) noteTable.addTag(lid, tagLid, true); } // Remove any tags specified for (int j=0; j<delTagNames.size(); j++) { qint32 tagLid = tagTable.findByName(delTagNames[j],0); if (tagLid > 0 && noteTable.hasTag(lid,tagLid)) { noteTable.removeTag(lid, tagLid, true); } } if (reminderCompleted) { noteTable.setReminderCompleted(lid, true); } if (clearReminder) { noteTable.removeReminder(lid); } } return 0; }
// Handle what happens when something is dropped onto a tag item bool NTagView::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action) { // If this is a note-to-tag drop we are assigning tags to a note if (data->hasFormat("application/x-nixnote-note")) { QByteArray d = data->data("application/x-nixnote-note"); QString data(d); // Find the tag lid we dropped onto qint32 tagLid = parent->data(NAME_POSITION, Qt::UserRole).toInt(); // The string has a long list of note lids. We parse them out & update the note QStringList stringLids = data.split(" "); for (int i=0; i<stringLids.size(); i++) { if (stringLids[i].trimmed() != "") { qint32 noteLid = stringLids.at(i).toInt(); if (noteLid > 0) { NoteTable noteTable(global.db); if (!noteTable.hasTag(noteLid, tagLid)) { noteTable.addTag(noteLid, tagLid, true); QString tagString = noteTable.getNoteListTags(noteLid); emit(updateNoteList(noteLid, NOTE_TABLE_TAGS_POSITION, tagString)); // qint64 dt = QDateTime::currentMSecsSinceEpoch(); // noteTable.updateDate(noteLid, dt, NOTE_UPDATED_DATE, true); // emit(updateNoteList(noteLid, NOTE_TABLE_DATE_UPDATED_POSITION, dt)); } } } } if (stringLids.size() > 0) emit updateCounts(); return true; } // If this is a tag-to-tag drop then we are modifying the hierarchy if (data->hasFormat("application/x-nixnote-tag")) { // If there is no parent, then they are trying to drop to the top level, which isn't permitted if (parent == NULL) return false; // Get the lid we are dropping. QByteArray d = data->data("application/x-nixnote-tag"); qint32 lid = d.toInt(); if (lid == 0) return false; qint32 newParentLid = parent->data(NAME_POSITION, Qt::UserRole).toInt(); qint32 oldParentLid = dataStore[lid]->parentLid; if (newParentLid == oldParentLid) return false; if (newParentLid == lid) return false; NTagViewItem *item = dataStore[lid]; // If we had an old parent, remove the child from it. if (oldParentLid > 0) { NTagViewItem *parent_ptr = dataStore[oldParentLid]; for (int i=0; i<parent_ptr->childrenLids.size(); i++) { if (parent_ptr->childrenLids[i] == lid) { parent_ptr->childrenLids.removeAt(i); i=parent_ptr->childrenLids.size(); } } parent_ptr->removeChild(item); } else { root->removeChild(item); } // Update the actual database Tag tag; TagTable tagTable(global.db); tagTable.get(tag, lid); QString guid; tagTable.getGuid(guid, newParentLid); tag.parentGuid = guid; tagTable.update(tag, true); if (newParentLid>0) { NTagViewItem *parent_ptr = dataStore[newParentLid]; parent_ptr->addChild(item); parent_ptr->childrenLids.append(lid); item->parentLid = newParentLid; item->parentGuid = tag.guid; } else { item->parentLid = 0; item->parentGuid = ""; root->addChild(item); } // Resort the data this->sortByColumn(NAME_POSITION, Qt::AscendingOrder); sortItems(NAME_POSITION, Qt::AscendingOrder); return QTreeWidget::dropMimeData(parent, index, data, action); } return false; }
// A tag has been updated. Things like a sync can cause this to be called // because a tag's name may have changed. void NTagView::tagUpdated(qint32 lid, QString name, QString parentGuid, qint32 account) { this->rebuildTagTreeNeeded = true; qint32 parentLid = 0; NTagViewItem *parentWidget = root; TagTable tagTable(global.db); // Check if it already exists and if its parent exists NTagViewItem *newWidget = NULL; if (this->dataStore.contains(lid) && dataStore[lid] != NULL) { newWidget = dataStore[lid]; if (newWidget->parent() != NULL) newWidget->parent()->removeChild(newWidget); } else { newWidget = new NTagViewItem(); newWidget->account = account; dataStore.remove(lid); dataStore.insert(lid, newWidget); } parentLid = tagTable.getLid(parentGuid); if (parentGuid != "") { if (parentLid > 0 && dataStore.contains(parentLid)) { parentWidget = dataStore[parentLid]; if (parentWidget == NULL) { parentWidget = new NTagViewItem(); parentWidget->account = account; if (account != this->accountFilter) parentWidget->setHidden(true); dataStore.remove(parentLid); dataStore.insert(parentLid, parentWidget); } } else { if (parentLid == 0) { Tag parentTag; parentTag.guid = parentGuid; parentTag.updateSequenceNum = 0; parentTag.name = parentGuid; parentLid = tagTable.add(0, parentTag, false, account); } parentWidget = new NTagViewItem(); root->addChild(parentWidget); parentWidget->setData(NAME_POSITION, Qt::UserRole, parentLid); parentWidget->setData(NAME_POSITION, Qt::DisplayRole, tr("-<Missing Tag>-")); dataStore.insert(parentLid, parentWidget); } } if (account != accountFilter) newWidget->setHidden(true); else newWidget->setHidden(false); parentWidget->addChild(newWidget); newWidget->setData(NAME_POSITION, Qt::DisplayRole, name); newWidget->setData(NAME_POSITION, Qt::UserRole, lid); newWidget->parentGuid = parentGuid; newWidget->parentLid = parentLid; newWidget->account = account; if (this->dataStore.count() == 1) { this->expandAll(); } resetSize(); this->sortByColumn(NAME_POSITION); }
// Fetch an individual favorite bool FavoritesTable::get(FavoritesRecord &record, qint32 lid) { NSqlQuery query(db); record.parent = 0; db->lockForRead(); query.prepare("select key,data from datastore where lid=:lid"); query.bindValue(":lid", lid); query.exec(); int type = 0; bool retval = false; while (query.next()) { retval = true; record.lid = lid; int key = query.value(0).toInt(); switch (key) { case FAVORITES_ORDER : record.order = query.value(1).toInt(); break; case FAVORITES_TYPE : type = query.value(1).toInt(); break; case FAVORITES_TARGET : record.target = query.value(1); break; case FAVORITES_PARENT : record.parent = query.value(1).toInt(); } } query.finish(); db->unlock(); switch (type) { case FavoritesRecord::Note : record.type = FavoritesRecord::Note; break; case FavoritesRecord::LocalNotebook : record.type = FavoritesRecord::LocalNotebook; break; case FavoritesRecord::SynchronizedNotebook : record.type = FavoritesRecord::SynchronizedNotebook; break; case FavoritesRecord::ConflictNotebook : record.type = FavoritesRecord::ConflictNotebook; break; case FavoritesRecord::NotebookStack : record.type = FavoritesRecord::NotebookStack; break; case FavoritesRecord::SharedNotebook : record.type = FavoritesRecord::SharedNotebook; break; case FavoritesRecord::LinkedNotebook : record.type = FavoritesRecord::LinkedNotebook; break; case FavoritesRecord::LinkedStack : record.type = FavoritesRecord::LinkedStack; break; case FavoritesRecord::Tag : record.type = FavoritesRecord::Tag; break; case FavoritesRecord::Search : record.type = FavoritesRecord::Search; break; } record.displayName = "<missing value>"; if (record.type == FavoritesRecord::Tag) { TagTable tagTable(db); Tag t; tagTable.get(t, record.target.toInt()); if (t.name.isSet()) record.displayName = t.name; } if (record.type == FavoritesRecord::Note) { NoteTable table(db); Note r; table.get(r, record.target.toInt(), false,false); if (r.title.isSet()) record.displayName = r.title; } if (record.type == FavoritesRecord::LocalNotebook|| record.type == FavoritesRecord::SynchronizedNotebook|| record.type == FavoritesRecord::ConflictNotebook || record.type == FavoritesRecord::SharedNotebook || record.type == FavoritesRecord::LinkedNotebook) { NotebookTable table(db); Notebook r; table.get(r, record.target.toInt()); if (r.name.isSet()) record.displayName = r.name; } if (record.type == FavoritesRecord::Search) { SearchTable table(db); SavedSearch r; table.get(r, record.target.toInt()); if (r.name.isSet()) record.displayName = r.name; } if (record.type == FavoritesRecord::NotebookStack || record.type == FavoritesRecord::LinkedStack) { record.displayName = record.target.toString(); } return retval; }