From a45def57488852d6c41e1f809ceac748b6a92322 Mon Sep 17 00:00:00 2001 From: Randy Baumgarte Date: Fri, 23 Jul 2010 12:49:41 -0400 Subject: [PATCH] There are multiple changes with this commit. - Added the ability to do basic GeoTag handling. Tags can be added, removed, or launched in a separate browser to bring up Google Earth. - Removed the DBRunner thread and changed all existing threads to have dedicated connections to the database. This allows for the removal of a lot of the classes underneath cx.fbn.nevernote.sql.* since they are no longer needed. --- src/cx/fbn/nevernote/Global.java | 25 +- src/cx/fbn/nevernote/NeverNote.java | 85 +- src/cx/fbn/nevernote/dialog/GeoDialog.java | 144 +++ src/cx/fbn/nevernote/dialog/WatchFolder.java | 6 +- src/cx/fbn/nevernote/dialog/WatchFolderAdd.java | 4 +- src/cx/fbn/nevernote/filters/EnSearch.java | 30 +- src/cx/fbn/nevernote/gui/BrowserWindow.java | 73 +- src/cx/fbn/nevernote/icons/globe.png | Bin 0 -> 21072 bytes src/cx/fbn/nevernote/icons/splash_logo.png | Bin 194775 -> 14705 bytes src/cx/fbn/nevernote/sql/DatabaseConnection.java | 180 +-- .../sql/{runners => }/DeletedItemRecord.java | 2 +- src/cx/fbn/nevernote/sql/DeletedTable.java | 88 +- src/cx/fbn/nevernote/sql/InvalidXMLTable.java | 230 +++- src/cx/fbn/nevernote/sql/NoteResourceTable.java | 622 +++++++--- src/cx/fbn/nevernote/sql/NoteTable.java | 1286 +++++++++++++++----- .../sql/{runners => }/NoteTagsRecord.java | 2 +- src/cx/fbn/nevernote/sql/NoteTagsTable.java | 184 ++- src/cx/fbn/nevernote/sql/NotebookTable.java | 466 +++++-- .../fbn/nevernote/sql/{runners => }/REnSearch.java | 15 +- src/cx/fbn/nevernote/sql/SavedSearchTable.java | 324 +++-- src/cx/fbn/nevernote/sql/SyncTable.java | 107 +- src/cx/fbn/nevernote/sql/TagTable.java | 372 ++++-- .../sql/{runners => }/WatchFolderRecord.java | 2 +- src/cx/fbn/nevernote/sql/WatchFolderTable.java | 123 +- src/cx/fbn/nevernote/sql/WordsTable.java | 123 +- .../nevernote/sql/requests/DBRunnerRequest.java | 44 - .../nevernote/sql/requests/DatabaseRequest.java | 58 - .../nevernote/sql/requests/DeletedItemRequest.java | 70 -- .../nevernote/sql/requests/EnSearchRequest.java | 75 -- .../nevernote/sql/requests/InvalidXMLRequest.java | 80 -- src/cx/fbn/nevernote/sql/requests/NoteRequest.java | 178 --- .../nevernote/sql/requests/NoteTagsRequest.java | 93 -- .../nevernote/sql/requests/NotebookRequest.java | 105 -- .../nevernote/sql/requests/ResourceRequest.java | 103 -- .../nevernote/sql/requests/SavedSearchRequest.java | 95 -- src/cx/fbn/nevernote/sql/requests/SyncRequest.java | 60 - src/cx/fbn/nevernote/sql/requests/TagRequest.java | 99 -- .../nevernote/sql/requests/WatchFolderRequest.java | 84 -- src/cx/fbn/nevernote/sql/requests/WordRequest.java | 65 - .../nevernote/sql/runners/RDatabaseConnection.java | 227 ---- .../fbn/nevernote/sql/runners/RDeletedTable.java | 95 -- .../nevernote/sql/runners/RInvalidXMLTable.java | 221 ---- .../nevernote/sql/runners/RNoteResourceTable.java | 569 --------- src/cx/fbn/nevernote/sql/runners/RNoteTable.java | 1110 ----------------- .../fbn/nevernote/sql/runners/RNoteTagsTable.java | 179 --- .../fbn/nevernote/sql/runners/RNotebookTable.java | 432 ------- .../nevernote/sql/runners/RSavedSearchTable.java | 284 ----- src/cx/fbn/nevernote/sql/runners/RSyncTable.java | 94 -- src/cx/fbn/nevernote/sql/runners/RTagTable.java | 338 ----- .../nevernote/sql/runners/RWatchFolderTable.java | 117 -- src/cx/fbn/nevernote/sql/runners/RWordsTable.java | 137 --- src/cx/fbn/nevernote/threads/CounterRunner.java | 13 +- src/cx/fbn/nevernote/threads/DBRunner.java | 936 -------------- src/cx/fbn/nevernote/threads/IndexRunner.java | 8 +- src/cx/fbn/nevernote/threads/SaveRunner.java | 7 +- src/cx/fbn/nevernote/threads/SyncRunner.java | 25 +- src/cx/fbn/nevernote/utilities/ListManager.java | 44 +- 57 files changed, 3288 insertions(+), 7250 deletions(-) create mode 100644 src/cx/fbn/nevernote/dialog/GeoDialog.java create mode 100644 src/cx/fbn/nevernote/icons/globe.png rename src/cx/fbn/nevernote/sql/{runners => }/DeletedItemRecord.java (92%) rename src/cx/fbn/nevernote/sql/{runners => }/NoteTagsRecord.java (92%) rename src/cx/fbn/nevernote/sql/{runners => }/REnSearch.java (95%) rename src/cx/fbn/nevernote/sql/{runners => }/WatchFolderRecord.java (92%) delete mode 100644 src/cx/fbn/nevernote/sql/requests/DBRunnerRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/DatabaseRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/DeletedItemRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/EnSearchRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/InvalidXMLRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/NoteRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/NoteTagsRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/NotebookRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/ResourceRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/SavedSearchRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/SyncRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/TagRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/WatchFolderRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/requests/WordRequest.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RDatabaseConnection.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RDeletedTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RInvalidXMLTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RNoteResourceTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RNoteTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RNoteTagsTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RNotebookTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RSavedSearchTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RSyncTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RTagTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RWatchFolderTable.java delete mode 100644 src/cx/fbn/nevernote/sql/runners/RWordsTable.java delete mode 100644 src/cx/fbn/nevernote/threads/DBRunner.java diff --git a/src/cx/fbn/nevernote/Global.java b/src/cx/fbn/nevernote/Global.java index 7bb54a4..3a124b5 100644 --- a/src/cx/fbn/nevernote/Global.java +++ b/src/cx/fbn/nevernote/Global.java @@ -21,6 +21,7 @@ package cx.fbn.nevernote; //import java.io.ByteArrayOutputStream; + import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -34,7 +35,6 @@ import com.evernote.edam.type.PrivilegeLevel; import com.evernote.edam.type.User; import com.evernote.edam.type.UserAttributes; import com.trolltech.qt.core.QByteArray; -import com.trolltech.qt.core.QMutex; import com.trolltech.qt.core.QSettings; import com.trolltech.qt.gui.QPalette; @@ -44,20 +44,13 @@ import cx.fbn.nevernote.config.StartupConfig; import cx.fbn.nevernote.gui.ContainsAttributeFilterTable; import cx.fbn.nevernote.gui.DateAttributeFilterTable; import cx.fbn.nevernote.gui.ShortcutKeys; -import cx.fbn.nevernote.signals.DBRunnerSignal; -import cx.fbn.nevernote.threads.DBRunner; import cx.fbn.nevernote.utilities.ApplicationLogger; public class Global { public static String version = "0.88"; public static String username = ""; - public static String password = ""; - - public static DBRunner dbRunner; // Database thread - public static DBRunnerSignal dbRunnerSignal; // Signals to the database runner - public static QMutex dbrunnerWorkLock; // mutex lock for work queue + public static String password = ""; - public static int mainThreadId=0; private static ArrayBlockingQueue mainThreadWaiter = new ArrayBlockingQueue(1); @@ -86,7 +79,7 @@ public class Global { private static ArrayBlockingQueue indexThread04ThreadWaiter = new ArrayBlockingQueue(1); public static int dbThreadId=9; // This should always be the highest thread ID - + public static HashMap passwordSafe = new HashMap(); public static List passwordRemember = new ArrayList(); @@ -118,6 +111,7 @@ public class Global { private static String wordRegex; public static boolean enableCarriageReturnFix = false; + public static String name = null; public static QSettings settings; public static boolean isConnected; public static boolean showDeleted = false; @@ -139,7 +133,7 @@ public class Global { PrintStream stdoutStream; public static QPalette originalPalette; public static ShortcutKeys shortcutKeys; - private static boolean disableViewing; + public static boolean disableViewing; public static List invalidElements = new ArrayList(); public static HashMap> invalidAttributes = new HashMap>(); @@ -151,7 +145,7 @@ public class Global { static Calendar intervalTraceTime; private static FileManager fileManager; - + // Do initial setup public static void setup(StartupConfig startupConfig) throws InitializationException { settings = new QSettings("fbn.cx", startupConfig.getName()); @@ -159,6 +153,7 @@ public class Global { fileManager = new FileManager(startupConfig.getHomeDirPath()); + getServer(); settings.beginGroup("General"); String regex = (String) settings.value("regex", "[,\\s]+"); @@ -179,7 +174,6 @@ public class Global { // indexLock = new DBLock(); logger = new ApplicationLogger("global.log"); shortcutKeys = new ShortcutKeys(); - dbrunnerWorkLock = new QMutex(); mimicEvernoteInterface = getMimicEvernoteInterface(); resourceMap = new HashMap(); @@ -910,6 +904,7 @@ public class Global { public static void dbContinue() { // Global.dbThreadWait.wakeAll(); } + public static synchronized void dbClientWait(int id) { if (id == mainThreadId) { try {mainThreadWaiter.take(); } catch (InterruptedException e) {e.printStackTrace();} @@ -1116,12 +1111,14 @@ public class Global { startTraceTime = null; } + public static FileManager getFileManager() { return fileManager; } - public static boolean getDisableViewing() { return disableViewing; } + + } diff --git a/src/cx/fbn/nevernote/NeverNote.java b/src/cx/fbn/nevernote/NeverNote.java index c9a95b3..5897cf1 100644 --- a/src/cx/fbn/nevernote/NeverNote.java +++ b/src/cx/fbn/nevernote/NeverNote.java @@ -63,6 +63,7 @@ import com.trolltech.qt.core.QFile; import com.trolltech.qt.core.QFileInfo; import com.trolltech.qt.core.QFileSystemWatcher; import com.trolltech.qt.core.QIODevice; +import com.trolltech.qt.core.QIODevice.OpenModeFlag; import com.trolltech.qt.core.QModelIndex; import com.trolltech.qt.core.QSize; import com.trolltech.qt.core.QTemporaryFile; @@ -71,19 +72,22 @@ import com.trolltech.qt.core.QThreadPool; import com.trolltech.qt.core.QTimer; import com.trolltech.qt.core.QUrl; import com.trolltech.qt.core.Qt; -import com.trolltech.qt.core.QIODevice.OpenModeFlag; import com.trolltech.qt.core.Qt.SortOrder; import com.trolltech.qt.core.Qt.WidgetAttribute; import com.trolltech.qt.gui.QAbstractItemView; +import com.trolltech.qt.gui.QAbstractItemView.ScrollHint; import com.trolltech.qt.gui.QAction; import com.trolltech.qt.gui.QApplication; import com.trolltech.qt.gui.QCloseEvent; import com.trolltech.qt.gui.QColor; import com.trolltech.qt.gui.QComboBox; +import com.trolltech.qt.gui.QComboBox.InsertPolicy; import com.trolltech.qt.gui.QCursor; import com.trolltech.qt.gui.QDesktopServices; import com.trolltech.qt.gui.QDialog; import com.trolltech.qt.gui.QFileDialog; +import com.trolltech.qt.gui.QFileDialog.AcceptMode; +import com.trolltech.qt.gui.QFileDialog.FileMode; import com.trolltech.qt.gui.QGridLayout; import com.trolltech.qt.gui.QHBoxLayout; import com.trolltech.qt.gui.QIcon; @@ -93,11 +97,13 @@ import com.trolltech.qt.gui.QListWidget; import com.trolltech.qt.gui.QMainWindow; import com.trolltech.qt.gui.QMenu; import com.trolltech.qt.gui.QMessageBox; +import com.trolltech.qt.gui.QMessageBox.StandardButton; import com.trolltech.qt.gui.QPixmap; import com.trolltech.qt.gui.QPrintDialog; import com.trolltech.qt.gui.QPrinter; import com.trolltech.qt.gui.QProgressBar; import com.trolltech.qt.gui.QSizePolicy; +import com.trolltech.qt.gui.QSizePolicy.Policy; import com.trolltech.qt.gui.QSpinBox; import com.trolltech.qt.gui.QSplashScreen; import com.trolltech.qt.gui.QSplitter; @@ -107,14 +113,8 @@ import com.trolltech.qt.gui.QTableWidgetItem; import com.trolltech.qt.gui.QTextEdit; import com.trolltech.qt.gui.QToolBar; import com.trolltech.qt.gui.QTreeWidgetItem; -import com.trolltech.qt.gui.QAbstractItemView.ScrollHint; -import com.trolltech.qt.gui.QComboBox.InsertPolicy; -import com.trolltech.qt.gui.QFileDialog.AcceptMode; -import com.trolltech.qt.gui.QFileDialog.FileMode; -import com.trolltech.qt.gui.QMessageBox.StandardButton; -import com.trolltech.qt.gui.QSizePolicy.Policy; -import com.trolltech.qt.webkit.QWebSettings; import com.trolltech.qt.webkit.QWebPage.WebAction; +import com.trolltech.qt.webkit.QWebSettings; import com.trolltech.qt.xml.QDomAttr; import com.trolltech.qt.xml.QDomDocument; import com.trolltech.qt.xml.QDomElement; @@ -148,10 +148,8 @@ import cx.fbn.nevernote.gui.TableView; import cx.fbn.nevernote.gui.TagTreeWidget; import cx.fbn.nevernote.gui.Thumbnailer; import cx.fbn.nevernote.gui.TrashTreeWidget; -import cx.fbn.nevernote.signals.DBRunnerSignal; import cx.fbn.nevernote.sql.DatabaseConnection; -import cx.fbn.nevernote.sql.runners.WatchFolderRecord; -import cx.fbn.nevernote.threads.DBRunner; +import cx.fbn.nevernote.sql.WatchFolderRecord; import cx.fbn.nevernote.threads.IndexRunner; import cx.fbn.nevernote.threads.SyncRunner; import cx.fbn.nevernote.utilities.AESEncrypter; @@ -206,7 +204,6 @@ public class NeverNote extends QMainWindow{ boolean noteDirty; // Has the note been changed? boolean inkNote; // if this is an ink note, it is read only - QThread dbThread; // Thread to handle DB communication ListManager listManager; // DB runnable task List tempFiles; // Array of temporary files; @@ -291,21 +288,14 @@ public class NeverNote extends QMainWindow{ //*************************************************************** //*************************************************************** // Application Constructor - private NeverNote(DatabaseConnection dbConn) { - conn = dbConn; + public NeverNote(DatabaseConnection dbConn) { + conn = dbConn; thread().setPriority(Thread.MAX_PRIORITY); logger = new ApplicationLogger("nevernote.log"); logger.log(logger.HIGH, tr("Starting Application")); - logger.log(logger.EXTREME, tr("Starting DB thread")); - dbThread = new QThread(Global.dbRunner, "Database Thread"); - dbThread.start(); - logger.log(logger.EXTREME, tr("DB Thread has started")); - Global.dbRunnerSignal.start.emit(); - logger.log(logger.EXTREME, tr("Setting main thread DB connection")); - logger.log(logger.EXTREME, tr("main DB thread setup complete.")); conn.checkDatabaseVersion(); // Start building the invalid XML tables @@ -340,7 +330,7 @@ public class NeverNote extends QMainWindow{ listManager = new ListManager(conn, logger, Global.mainThreadId); logger.log(logger.EXTREME, tr("Building index runners & timers")); - indexRunner = new IndexRunner("indexRunner.log"); + indexRunner = new IndexRunner("indexRunner.log", Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); indexThread = new QThread(indexRunner, "Index Thread"); indexThread.start(); @@ -357,7 +347,7 @@ public class NeverNote extends QMainWindow{ logger.log(logger.EXTREME, tr("Setting sync thread & timers")); syncThreadsReady=1; - syncRunner = new SyncRunner("syncRunner.log"); + syncRunner = new SyncRunner("syncRunner.log", Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); syncTime = new SyncTimes().timeValue(Global.getSyncInterval()); syncTimer = new QTimer(); syncTimer.timeout.connect(this, "syncTimer()"); @@ -600,8 +590,8 @@ public class NeverNote extends QMainWindow{ QApplication.initialize(args); QPixmap pixmap = new QPixmap("classpath:cx/fbn/nevernote/icons/splash_logo.png"); QSplashScreen splash = new QSplashScreen(pixmap); - boolean showSplash; + DatabaseConnection dbConn; try { @@ -624,6 +614,7 @@ public class NeverNote extends QMainWindow{ } NeverNote application = new NeverNote(dbConn); + application.setAttribute(WidgetAttribute.WA_DeleteOnClose, true); if (Global.wasWindowMaximized()) application.showMaximized(); @@ -642,10 +633,9 @@ public class NeverNote extends QMainWindow{ * @throws InitializationException when opening the database fails, e.g. because another process has it locked */ private static DatabaseConnection setupDatabaseConnection() throws InitializationException { - DatabaseConnection dbConn = new DatabaseConnection(Global.mainThreadId); - dbConn.dbSetup(); + ApplicationLogger logger = new ApplicationLogger("nevernote-database.log"); + DatabaseConnection dbConn = new DatabaseConnection(logger,Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); - Global.dbRunnerSignal = new DBRunnerSignal(); if (Global.getDatabaseUrl().toUpperCase().indexOf("CIPHER=") > -1) { boolean goodCheck = false; while (!goodCheck) { @@ -658,8 +648,6 @@ public class NeverNote extends QMainWindow{ Global.getDatabaseUserPassword(), Global.cipherPassword); } } - Global.dbRunner = new DBRunner(Global.getDatabaseUrl(), Global.getDatabaseUserid(), - Global.getDatabaseUserPassword(), Global.cipherPassword); return dbConn; } @@ -775,15 +763,6 @@ public class NeverNote extends QMainWindow{ } } - logger.log(logger.EXTREME, "Shutting down database"); - conn.dbShutdown(); - logger.log(logger.EXTREME, "Waiting for DB thread to terminate"); - try { - dbThread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - logger.log(logger.EXTREME, "DB Thread has terminated"); logger.log(logger.HIGH, "Leaving NeverNote.closeEvent"); } @@ -844,7 +823,9 @@ public class NeverNote extends QMainWindow{ browserWindow.noteSignal.subjectDateChanged.connect(listManager, "updateNoteSubjectDate(String, QDateTime)"); browserWindow.noteSignal.subjectDateChanged.connect(this, "updateListDateSubject(String, QDateTime)"); browserWindow.noteSignal.authorChanged.connect(listManager, "updateNoteAuthor(String, String)"); + browserWindow.noteSignal.geoChanged.connect(listManager, "updateNoteGeoTag(String, Double,Double,Double)"); browserWindow.noteSignal.authorChanged.connect(this, "updateListAuthor(String, String)"); + browserWindow.noteSignal.geoChanged.connect(this, "setNoteDirty()"); browserWindow.noteSignal.sourceUrlChanged.connect(listManager, "updateNoteSourceUrl(String, String)"); browserWindow.noteSignal.sourceUrlChanged.connect(this, "updateListSourceUrl(String, String)"); browserWindow.focusLost.connect(this, "saveNote()"); @@ -852,6 +833,8 @@ public class NeverNote extends QMainWindow{ // browserWindow.resourceSignal.externalFileEdit.connect(this, "saveResourceLater(String, String)"); } + + //*************************************************************** //*************************************************************** //* Settings and look & feel @@ -2626,7 +2609,7 @@ public class NeverNote extends QMainWindow{ logger.log(logger.HIGH, "Leaving NeverNote.updateListAuthor"); } private void updateListNoteNotebook(String guid, String notebook) { - logger.log(logger.HIGH, "Entering NeverNote.updateListAuthor"); + logger.log(logger.HIGH, "Entering NeverNote.updateListNoteNotebook"); for (int i=0; i -1) { @@ -4813,6 +4787,9 @@ public class NeverNote extends QMainWindow{ } } + + + //************************************************* //* Check database userid & passwords //************************************************* diff --git a/src/cx/fbn/nevernote/dialog/GeoDialog.java b/src/cx/fbn/nevernote/dialog/GeoDialog.java new file mode 100644 index 0000000..eb03e73 --- /dev/null +++ b/src/cx/fbn/nevernote/dialog/GeoDialog.java @@ -0,0 +1,144 @@ +/* + * This file is part of NeverNote + * Copyright 2009 Randy Baumgarte + * + * This file may be licensed under the terms of of the + * GNU General Public License Version 2 (the ``GPL''). + * + * Software distributed under the License is distributed + * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the GPL for the specific language + * governing rights and limitations. + * + * You should have received a copy of the GPL along with this + * program. If not, go to http://www.gnu.org/licenses/gpl.html + * or write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +*/ + +package cx.fbn.nevernote.dialog; + +import com.trolltech.qt.gui.QDialog; +import com.trolltech.qt.gui.QDoubleValidator; +import com.trolltech.qt.gui.QDoubleValidator.Notation; +import com.trolltech.qt.gui.QGridLayout; +import com.trolltech.qt.gui.QLabel; +import com.trolltech.qt.gui.QLineEdit; +import com.trolltech.qt.gui.QPushButton; + +public class GeoDialog extends QDialog { + + private boolean okPressed; + private final QLineEdit altitude; + private final QLineEdit latitude; + private final QLineEdit longitude; + private final QPushButton ok; + + + // Constructor + public GeoDialog() { + okPressed = false; + setWindowTitle("NeverNote Login"); + QGridLayout grid = new QGridLayout(); + setLayout(grid); + QGridLayout passwordGrid = new QGridLayout(); + QGridLayout buttonGrid = new QGridLayout(); + + + longitude = new QLineEdit(); + QDoubleValidator longVal = new QDoubleValidator(-180.0,180.0,4,longitude); + longVal.setNotation(Notation.StandardNotation); + longitude.setValidator(longVal); + + latitude = new QLineEdit(); + QDoubleValidator latVal = new QDoubleValidator(-90.0,90.0,4,latitude); + latVal.setNotation(Notation.StandardNotation); + latitude.setValidator(latVal); + + altitude = new QLineEdit(); + QDoubleValidator altVal = new QDoubleValidator(-9999.0,9999.0,4,altitude); + altVal.setNotation(Notation.StandardNotation); + altitude.setValidator(altVal); + + + passwordGrid.addWidget(new QLabel("Longitude"), 1,1); + passwordGrid.addWidget(longitude, 1, 2); + passwordGrid.addWidget(new QLabel("Latitude"), 2,1); + passwordGrid.addWidget(latitude, 2, 2); + passwordGrid.addWidget(new QLabel("Altitude"), 3,1); + passwordGrid.addWidget(altitude, 3, 2); + passwordGrid.setContentsMargins(10, 10, -10, -10); + grid.addLayout(passwordGrid,1,1); + + ok = new QPushButton("OK"); + ok.clicked.connect(this, "okButtonPressed()"); + QPushButton cancel = new QPushButton("Cancel"); + cancel.clicked.connect(this, "cancelButtonPressed()"); + buttonGrid.addWidget(ok, 1, 1); + buttonGrid.addWidget(cancel, 1,2); + grid.addLayout(buttonGrid,2,1); + } + + // The OK button was pressed + @SuppressWarnings("unused") + private void okButtonPressed() { + okPressed = true; + close(); + } + + // The CANCEL button was pressed + @SuppressWarnings("unused") + private void cancelButtonPressed() { + okPressed = false; + close(); + } + + // Get the longitude + public double getLongitude() { + try { + return new Double(longitude.text()); + } catch (java.lang.NumberFormatException e) { + return 0.0; + } + } + + // Get the latitude + public double getLatitude() { + try { + return new Double(latitude.text()); + } catch (java.lang.NumberFormatException e) { + return 0.0; + } + } + + // Get the altitude + public double getAltitude() { + try { + return new Double(altitude.text()); + } catch (java.lang.NumberFormatException e) { + return 0.0; + } + } + + + public void setLongitude(double value) { + longitude.setText(new Float(value).toString()); + } + + public void setLatitude(double value) { + latitude.setText(new Float(value).toString()); + } + + public void setAltitude(double value) { + altitude.setText(new Float(value).toString()); + } + + + // Check if the OK button was pressed + public boolean okPressed() { + return okPressed; + } + + +} diff --git a/src/cx/fbn/nevernote/dialog/WatchFolder.java b/src/cx/fbn/nevernote/dialog/WatchFolder.java index 3bec89b..3a86b39 100644 --- a/src/cx/fbn/nevernote/dialog/WatchFolder.java +++ b/src/cx/fbn/nevernote/dialog/WatchFolder.java @@ -24,6 +24,8 @@ import java.util.List; import com.evernote.edam.type.Notebook; import com.trolltech.qt.core.QModelIndex; +import com.trolltech.qt.gui.QAbstractItemView.SelectionBehavior; +import com.trolltech.qt.gui.QAbstractItemView.SelectionMode; import com.trolltech.qt.gui.QApplication; import com.trolltech.qt.gui.QDialog; import com.trolltech.qt.gui.QFontMetrics; @@ -32,10 +34,8 @@ import com.trolltech.qt.gui.QPushButton; import com.trolltech.qt.gui.QTableWidget; import com.trolltech.qt.gui.QTableWidgetItem; import com.trolltech.qt.gui.QVBoxLayout; -import com.trolltech.qt.gui.QAbstractItemView.SelectionBehavior; -import com.trolltech.qt.gui.QAbstractItemView.SelectionMode; -import cx.fbn.nevernote.sql.runners.WatchFolderRecord; +import cx.fbn.nevernote.sql.WatchFolderRecord; public class WatchFolder extends QDialog { private final QPushButton okButton; diff --git a/src/cx/fbn/nevernote/dialog/WatchFolderAdd.java b/src/cx/fbn/nevernote/dialog/WatchFolderAdd.java index 0186976..0e7fa53 100644 --- a/src/cx/fbn/nevernote/dialog/WatchFolderAdd.java +++ b/src/cx/fbn/nevernote/dialog/WatchFolderAdd.java @@ -25,14 +25,14 @@ import com.evernote.edam.type.Notebook; import com.trolltech.qt.gui.QComboBox; import com.trolltech.qt.gui.QDialog; import com.trolltech.qt.gui.QFileDialog; +import com.trolltech.qt.gui.QFileDialog.FileMode; import com.trolltech.qt.gui.QGridLayout; import com.trolltech.qt.gui.QHBoxLayout; import com.trolltech.qt.gui.QLabel; import com.trolltech.qt.gui.QPushButton; import com.trolltech.qt.gui.QVBoxLayout; -import com.trolltech.qt.gui.QFileDialog.FileMode; -import cx.fbn.nevernote.sql.runners.WatchFolderRecord; +import cx.fbn.nevernote.sql.WatchFolderRecord; public class WatchFolderAdd extends QDialog { private final QPushButton okButton; diff --git a/src/cx/fbn/nevernote/filters/EnSearch.java b/src/cx/fbn/nevernote/filters/EnSearch.java index 5265b1d..0a91692 100644 --- a/src/cx/fbn/nevernote/filters/EnSearch.java +++ b/src/cx/fbn/nevernote/filters/EnSearch.java @@ -20,47 +20,31 @@ package cx.fbn.nevernote.filters; -import java.util.ArrayList; import java.util.List; import com.evernote.edam.type.Note; import com.evernote.edam.type.Tag; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.EnSearchRequest; +import cx.fbn.nevernote.sql.DatabaseConnection; +import cx.fbn.nevernote.sql.REnSearch; +import cx.fbn.nevernote.utilities.ApplicationLogger; public class EnSearch { private List matches; public List hilightWords; - int id; - public EnSearch(int i, String s, List t, int len, int weight) { - id = i; + public EnSearch(DatabaseConnection conn, ApplicationLogger logger, String s, List t, int len, int weight) { if (s == null) return; if (s.trim().equals("")) return; matches = null; - EnSearchRequest request = new EnSearchRequest(); - request.requestor_id = id; -// request.type = DeletedItemRequest.Get_All; - request.string1 = s; - if (t!=null) { - request.tags = new ArrayList(); - for (int k=0; k matchWords() { diff --git a/src/cx/fbn/nevernote/gui/BrowserWindow.java b/src/cx/fbn/nevernote/gui/BrowserWindow.java index ca12e3a..50bdccf 100644 --- a/src/cx/fbn/nevernote/gui/BrowserWindow.java +++ b/src/cx/fbn/nevernote/gui/BrowserWindow.java @@ -57,6 +57,8 @@ import com.trolltech.qt.gui.QComboBox; import com.trolltech.qt.gui.QDateEdit; import com.trolltech.qt.gui.QDesktopServices; import com.trolltech.qt.gui.QFileDialog; +import com.trolltech.qt.gui.QFileDialog.AcceptMode; +import com.trolltech.qt.gui.QFileDialog.FileMode; import com.trolltech.qt.gui.QFontDatabase; import com.trolltech.qt.gui.QFormLayout; import com.trolltech.qt.gui.QGridLayout; @@ -74,17 +76,16 @@ import com.trolltech.qt.gui.QShortcut; import com.trolltech.qt.gui.QTimeEdit; import com.trolltech.qt.gui.QVBoxLayout; import com.trolltech.qt.gui.QWidget; -import com.trolltech.qt.gui.QFileDialog.AcceptMode; -import com.trolltech.qt.gui.QFileDialog.FileMode; import com.trolltech.qt.network.QNetworkRequest; import com.trolltech.qt.webkit.QWebPage; +import com.trolltech.qt.webkit.QWebPage.WebAction; import com.trolltech.qt.webkit.QWebSettings; import com.trolltech.qt.webkit.QWebView; -import com.trolltech.qt.webkit.QWebPage.WebAction; import cx.fbn.nevernote.Global; import cx.fbn.nevernote.dialog.EnCryptDialog; import cx.fbn.nevernote.dialog.EnDecryptDialog; +import cx.fbn.nevernote.dialog.GeoDialog; import cx.fbn.nevernote.dialog.InsertLinkDialog; import cx.fbn.nevernote.dialog.TableDialog; import cx.fbn.nevernote.dialog.TagAssign; @@ -101,6 +102,7 @@ public class BrowserWindow extends QWidget { private final QLineEdit urlText; private final QLabel authorLabel; private final QLineEdit authorText; + private final QComboBox geoBox; public final TagLineEdit tagEdit; public final QLabel tagLabel; private final QLabel urlLabel; @@ -190,6 +192,7 @@ public class BrowserWindow extends QWidget { titleLabel.setMaxLength(Constants.EDAM_NOTE_TITLE_LEN_MAX); urlText = new QLineEdit(); authorText = new QLineEdit(); + geoBox = new QComboBox(); urlLabel = new QLabel(); authorLabel = new QLabel(); conn = c; @@ -239,6 +242,14 @@ public class BrowserWindow extends QWidget { urlLabel.setVisible(false); urlText.setVisible(false); authorLabel.setVisible(false); + + geoBox.setVisible(false); + geoBox.addItem(new QIcon(iconPath+"globe.png"), ""); + geoBox.addItem(new String("Set")); + geoBox.addItem(new String("Clear")); + geoBox.addItem(new String("View On Map")); + geoBox.activated.connect(this, "geoBoxChanged()"); + authorText.setVisible(false); createdDate.setVisible(false); alteredLabel.setVisible(false); @@ -291,6 +302,7 @@ public class BrowserWindow extends QWidget { QHBoxLayout authorLayout = new QHBoxLayout(); authorLayout.addWidget(authorLabel, 0); authorLayout.addWidget(authorText, 0); + authorLayout.addWidget(geoBox); v.addLayout(authorLayout); dateLayout.addWidget(createdLabel, 0, 0); @@ -469,6 +481,7 @@ public class BrowserWindow extends QWidget { notebookBox.setEnabled(!v); tagEdit.setEnabled(!v); authorLabel.setEnabled(!v); + geoBox.setEnabled(!v); urlText.setEnabled(!v); createdDate.setEnabled(!v); subjectDate.setEnabled(!v); @@ -599,6 +612,7 @@ public class BrowserWindow extends QWidget { urlLabel.setVisible(extendedOn); urlText.setVisible(extendedOn); authorText.setVisible(extendedOn); + geoBox.setVisible(extendedOn); authorLabel.setVisible(extendedOn); createdDate.setVisible(extendedOn); createdTime.setVisible(extendedOn); @@ -1617,6 +1631,7 @@ public class BrowserWindow extends QWidget { icon = findIcon(type[0]); if (icon.equals("attachment.png")) icon = findIcon(url.substring(url.lastIndexOf(".")+1)); + StringBuffer imageBuffer = new StringBuffer(); String imageURL = FileUtils.toFileURLString(Global.getFileManager().getImageDirFile(icon)); logger.log(logger.EXTREME, "Creating resource "); @@ -1650,17 +1665,16 @@ public class BrowserWindow extends QWidget { PDFPreview pdfPreview = new PDFPreview(); if (pdfPreview.setupPreview(Global.getFileManager().getResDirPath(fileName), "pdf",0)) { - // NFC TODO: should this be a 'file://' url like the ones above? - imageURL = file.fileName() + ".png"; + // NFC TODO: should this be a 'file://' url like the ones above? + imageURL = file.fileName() + ".png"; } } logger.log(logger.EXTREME, "Generating link tags"); buffer.append(""); + .append(Global.getFileManager().getResDirPath(fileName)) + .append("');\" "); buffer.append("type=\"" + mimeType + "\" href=\"nnres://" + fileName +"\" hash=\""+Global.byteArrayToHexString(newRes.getData().getBodyHash()) +"\" >"); buffer.append(""); buffer.append(""); @@ -1835,6 +1849,42 @@ public class BrowserWindow extends QWidget { private void authorChanged() { noteSignal.authorChanged.emit(currentNote.getGuid(), authorText.text()); } + + private void geoBoxChanged() { + int index = geoBox.currentIndex(); + geoBox.setCurrentIndex(0); + if (index == 1) { + GeoDialog box = new GeoDialog(); + box.setLongitude(currentNote.getAttributes().getLongitude()); + box.setLatitude(currentNote.getAttributes().getLatitude()); + box.setAltitude(currentNote.getAttributes().getAltitude()); + box.exec(); + if (!box.okPressed()) + return; + double alt = box.getAltitude(); + double lat = box.getLatitude(); + double lon = box.getLongitude(); + if (alt != currentNote.getAttributes().getAltitude() || + lon != currentNote.getAttributes().getLongitude() || + lat != currentNote.getAttributes().getLatitude()) { + noteSignal.geoChanged.emit(currentNote.getGuid(), lon, lat, alt); + currentNote.getAttributes().setAltitude(alt); + currentNote.getAttributes().setLongitude(lon); + currentNote.getAttributes().setLatitude(lat); + } + } + + if (index == 2) { + noteSignal.geoChanged.emit(currentNote.getGuid(), 0.0, 0.0, 0.0); + currentNote.getAttributes().setAltitude(0.0); + currentNote.getAttributes().setLongitude(0.0); + currentNote.getAttributes().setLatitude(0.0); + } + + if (index == 3 || index == 0) { + QDesktopServices.openUrl(new QUrl("http://maps.google.com/maps?z=6&q="+currentNote.getAttributes().getLatitude() +"," +currentNote.getAttributes().getLongitude())); + } + } // ************************************************************ // * User chose to save an attachment. Pares out the request * @@ -1901,12 +1951,9 @@ public class BrowserWindow extends QWidget { fd.setAcceptMode(AcceptMode.AcceptSave); fd.setDirectory(System.getProperty("user.home")); String name = request.url().toString(); - - // Strip URL prefix and base dir path name = name.replace("nnres://", ""); String dPath = FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath()); name = name.replace(dPath, ""); - int pos = name.lastIndexOf('.'); String guid = name; if (pos > -1) { @@ -2244,8 +2291,8 @@ public class BrowserWindow extends QWidget { String source; if (locTag.startsWith("src")) { source = newSegment.substring(startSrcPos+locTag.length(),endSrcPos); - newSegment = newSegment.replace(source, - FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath(newFile))); + newSegment = newSegment.replace(source, + FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath(newFile))); } else { source = newSegment.substring(startSrcPos+locTag.length(),endSrcPos); newSegment = newSegment.replace(source, newFile); diff --git a/src/cx/fbn/nevernote/icons/globe.png b/src/cx/fbn/nevernote/icons/globe.png new file mode 100644 index 0000000000000000000000000000000000000000..5413db7cd86f017af58243c049fc727e81b04bb7 GIT binary patch literal 21072 zcmV)MK)An&P)+Tul@FaR@K3MiF5K=?|4PYUTbu3s)~ng4sp#I0l9{_Thp5TV)pIO|95 zCj`Yi!Ha-^nh{OuVx;XO7oPl1092FkO(Cjbs;B|}SvIcyX&hh*Mmp9fpYns{GsQ;9no={cAU_ ze>Wlm)ac?i;^IcC$qpOa4^UMLymxpfI1xlZP3fc3tI@`eix*zZ{-rl0>n}=REq?L< z&RxV;7g<|-6tj>t0L84Q>y^GYx=!hIkHw;*)ypjAv$)v3=2MH^$$R$CbpPb(U-`NF zfsQ`{%J7F)0p9#wKlv4P<-Vq=!`tiNU+BC;oF~)=tX=bNoLB0~6TC!Yi5Y-A>nSBK|q5>F+X~eFE=Vf_FGE#K2H6LoL$veNv>p zr|mlS_BJ^^}1+iG7d8 zNWa*7&tmVw&s})$-~8vmoIga0@I_aEH-Fbpesfd%cOJR^x4xoTdj{tnRS0zg@6*&~onF4P1Eo8xm5q9U3Fr_h{_VKGM&3f3ZKf@H6Ls=WqQC zOGWsix(5Hn`onMhZ~oyQzwNbu_OGr@>;Iu?>KAXE_$Z-_uQh(nwM5zHn;UB^2|2!?Zx5ImLl1TR#<6G9*a zpPnwel*}W%_bV>Ni6H6J`BgA-grQ2OkzO~5{k4eqm^diE1rT4~=Bl+Xo!sz-JHqyh zcG}Y)dKSiz^9TR<16P2zzUv3Bx$DdB_;=gKpZs3m-ubFfS9PcZYugVqnFw{&(C)t( zi?=YD?NB#LQ^VT&Gi+@=%+}W9)Kx_tDyljVyvG&cI|0&wt2JESe+B1u--2_-`s^&; zC1eS~kezN(-aPppGV%;bEou?Ljk(A&MQ~}=d-9PZ{TPHsCGi#gAy)1;Pvi4>8 zIxty3%f|LoG_?b}iN3uH>=bn!nXa9s@)aQjs;VORWVKZYxWWsZN5o^vlcNY-yq0-) z7v5K_O`c&=pU+$&xr^jt@_R4CekYRLNg{8vno);B9~Y`+n@mwGaQ#)7j(tdnHrGGQtT{ah-}{h5#N;Xlcprr8varj3S$V&VF39jt68`>;?CiV) zWHX_|AZ(wc>54+6q!mcsu5Xok{WamlD>mDw-+Lc_z?sD_xB}es&YyV6oo{>P|9RxP zk9_^wEc)5BVcJwon~G+-V776A^|i~?b@JzG*I`^ks2V2I^VH3AStv6O7H?(IzJXq^ zrw%PuSm5%&s#WZ@uOr&+z%iV2)YT`b+y$IZkY0rDvyj~&Y?u9e5%3rR4l6)H1jL9y z)RbYowEr>=+IuhxQY1fNV@`q$sDPBA7ZAVY>Ww#tldrlZtQ~)*JNLkOk|KP;3E~%2 z0bcdapZLo4wfHx;PCWR>);IQcksa4bmKg8NDg8TbK!xx5b$KfQ~3>rS_M z^Im`EV~<1jXkS1y_<||GYrgd-{@bnNkN?o-H6OWqwtkVdX<#~Om^L*{T~k*LeRm_f z`>$qW{US{rsj5H~U@|#J-JHSu4z=4cy9ej4Cxi;`6ZB7;hp56n!8t@cZM+-g+JV?5 z27d(P1daa`=ZJbICQX9(0plav??o(Bs;47-CqpEi>wlH3U^IQ1f@WJCl3h5@* zgH*oDa6YByR!p{&fA`6{oeSWOfgH!!q_aC%*o*1(D8Zk>i7wN|E;0PUHIRjk4VGIB z*u+v|iIiPzW2B7>)XosOmS{H&a4*@~jS@d5S0Fi-Oj4CWF#ybkBd%V5h1WFunG_xJn)>>vwO_Qy^scNb!(A3Ioc8QI(%S`J? z6#^jyyh~wOuP*?(Ia{=&8p1ZA4xgm*eVPk!vy8ZcxV04SIl+6$8Nv-rt_61zkxe4k zqD%}nfH-Je>1JV0cCp-wid{jBxK9yw_^E&;72J; z6%aQitiQq^xofLG{r->f1yX{~s{*|0oj?AAlcxHfs;VYcUE!-O>PgG`=7qufn<`|e z4>XOjHr-`X$CQZi6(KWy=K{Un1nxRQiPeM43IMf#gy8%1c@cu&rE(YW?jp`d#7z)a zC;y(aicX5P3(84EE)m=g!7uj@#IV5rfCNifsTBoC2yoDQcG?#+@83bstt0Cm3EM{O zKf%a|7%q=t=}`WQl~fxq_ebvB?oYq}qomAX&*xDIK5q(e&pUto2b(HKb2H z_^PI=C(PC^vNr2!s+7E`tBMeku#44KA;tPWhkMR>jBQL#;j-YXa6sjEaDJ1@Jxg$w zlRJiY5JFGoE)(QAob=$f@xEEc_A3)bf-<9Wr>R_vlal!xG5rDdmqak7o0VwLo3e!9Jgr*_XHKD5TRm0l)Io4)-G<7ww{i+I7ek6P!Qk?HnO4hkR@DXu> z_e$k1P=`-Zxo7eID9&vFM|0Y?8=yS74{%&WT#d*v#QBuP8>WbJ{Et%06I6b_Oaf(; z09K*_uw zsG%rB-j~A2Wu$Oel4dMjgBk|T)th(9#!YoR^O27OEuRM^_|H;+SA6^Tec#sgzxn;M z?I*(e)_FFzPP1|JQ*0f1l+7cbVr%;`R9}Gin|ab^%Gz{4!GEZTc7%C%6W-NK>O~TQ z4+IwmB`B{Uy9FnLx?2%m)iFA?-MdbuYHB<-{vghb<_9T^SGw+ZU zB{8v)-~meL$AAEfzGt_)l~(S_OrC7fc_OIz{J||`1)&HZOf7wW_&dcl-O=_moI+MK;@_CMKKhDvu z$JvN_#v*o+NqRj*?)=naKmW5sfYqoR~Y6W8$su=Qzs5U;#?D#L>n#+^kPTeZunu~hj zp~p!|@cYgh{=O98j&J(gFJ0SynDO#IjpheHg}c^F8?4!-VE3>aB-1BDdVeV)GH*ed;X#8AuR*KMFA2cUueISA_~60(J1Ycq8OkK274dJR>(evydHbnBfx~SQ<=c zf*Az%P}xerS_n2Ec!Zqu7w|6aRTl|*0b`e5*2%$L8eD$^rjH=DoA)Y1Ty81kixGM? z`lxi#=pytAQDM*i1kwL7e9jFhhKiviSK|l~*S>nX^`Y;YPM~Q5lPvJ6@+pm5rRtiJwF{LW z*tO4d@MRueM$a-+S0-wVlsHkdRDh9%DL9;iT6XaEaUwSW>nS-SC-8tnv+G=nS*S2~ zE5Zbq-Rc-oc#pp#jaCfj9z7w_eR7+w3NrV7bCE>d&xtnDD1{y&ts>!F>KJpU%>1VS7?2ypl^Q!=NeEmP(tY#ni%j?@uZZu6pQ~3n#BcZ1n zh?G!qJ`1%};1rfEl8K$i4?@oqGq{2I#$=mBt1xQ_?f?P2q?kUqeT>HdpOxkbkO?9) zgngjY`wJ@9f?R_~27r&G!X6_bh?$_8h?=6P8eKHzopC;XGsZFYXL0nG63GMnrFlFiu} zCf?&s5C!iPQ$w|*J$dg&e8HFenS-;(@3Z;YQfIi&=ZFNKvjRx<#P@9;|M=^swWp~< z&gywW$R$)>1`8LTrc|AmRNXsFwU+9gWOd7VcsF=u%SvDd(E`1_5S1g;cAg+Rh`UU1 zF(Zb86u57RCiI*{t;KN>wJzo5-Hix7SzJ!#W_pmpVvQJ6d@qt%c&A3&2ln`r;Ev-I zdO10q1K(h>0nu>M;3N>~6Za5@I)|8`?u9J)VqFxz8k7?!jkl56j(yP&}+@Y z?jp*~h}#4?f^rmOjUX3?P_HC^oH$Hobb5-&H7LhWuEFF4Dl>+fJO%F5G|Oc zc^}K}puBhTBx|$(jU(&tXKkWPLLg-HEOiK4o>5V^&wT$?*J$gXw!J5x&Pwn(@qy0? z0j66I{q^a_;~PFqsDcne@#kggH78JVy3XIO79l`C7}C6V`vn_6meOLoJTBzI`A3#9pIB?JB!Tf zzt3iKA8Vna_EmCn3Vk03)WW5-hOZoTvtV|^Z=KMCm;P7n>Gyt=!~^Cm3rc?f3UKGQ z{h7Z~uiyV#A67}6cMLUkR}=p5lv1-V+2UoeMo01g0+!-uGe>K;5#^@5LMO%eDVLU1 z#i)`1GM^}q%D0F-P1Jx{ORo{JV??Qvu(A{~qkW9dGii6%U?T*r;#GLG~Vbro!r!aj~Xqom6 z)2Ol}dP(@JgS&hLyzEmxuuSp{yBGz_z|sXc$&*2uY?F21@95xGHdRm4vwmF$<^l4G|PyyGryhQ zu4B=kV*lXIw2q+?s@l_3p629*yZue?cn5G2*aRvL5An)qe};#_H_R`-iSA%Rj89NE z?`Jx>K&O$24ih9~RzDtvbm1~G@X@g{_+`c|TeIM(-5Jd8rvrqe$f2K@gcVli>&qY~ zVwYC-CaUi-iv-)JM@f0PJRBo_Jht?*U#|9mJ^2 zq2(yXc^15gPQDYI5WEg+6`V_7Ej}<()7ut!kMn`3NAW>$ctiz@uy*tlERMhVP4O{) z5jY3zvRsRJ#p8Jq;JPpWd*9^5{V7}k@97tJuygUt*_$7y)wIky7A$b-Om0AR$qRIf z#gCwz<-p{hu;GeZ8%^0}kj<`xA+?<53lwLk5f-a5dY)^S z&vyK?Aja_B;&%{&-cMschvzhtu*X{XFl+K3sQ68sJeS<4pU`tx3KOLupd`ZLz-T}k z@BxA+G`o4FlcTO3b?upLoTs_=**pDBU-l<}6Tk+a0R^7l1&DlgUT~jSiuY)|naewG zWzl=uyux-USIt)}g(I*O{)**{5Mu0D6LgYc+fnc@VF^%kvn*7xp27w?}cbOeZI)>K9Virl9kNUH zzyuczU(cD&o@Q(NLF&rWR4%>Np6R4#`}$v}zJB8^BsXvbnDRUda8(y@{7Zl2ts?Rc zoKInklf2w1&@EoVg`KlZr|(ZotWD88X))BzGlI*p59iA5@_s-LN(kw2@@YC#I$nb1 zj9)tUsBUj`!j#C7+}L5{dkYV-ot)@NUwA%MutTp`9RD12XyqdU!eyGuGp$lGtB*bV z;^=h?r`uo-Wy+p?CB_e-wp0S|9BSM2dWxf4_i}Xoy-fV$Osdm3P0;H_L7}fbP1iHI z?$H;vr(XA__~iSZ11^wq1B=7I^Qs8o9DfRt%{0^E6XX;$@C|KyH@l0IEON($AK-LCnC6F$JX2a z&b1=)w=?a2_ zIz*=a31;p-X8r-Dd9_br;pOyJ{^cxLQL@q;0xa{2G+{LSs?Q?5s|mUR0@iZuRw z{>(vm7g27&ixNbryrc4t+V3*BXb~Wd`Laq`XsVXY>1mE{J;27=B_@>&uwRAz^o~%u zv|V|crpIk;-Y#p`-$DurXIv%odxZUKzrb|ASo-77mp_^wgY1#Nh|{U)r{o?UCSPMkiB4O)-n8xFI!G zgaavP7}C9mggJgc7XDbshXp=Xf?y9)#eahL&t~OM*O>%t%LSt%)Zq!%t0$?vr3o|v zC&t$7G<_e@UWno`R|-x*C6%cKwWI`9$_vb{x$kIq?cJ}5k39V#i40!mP~pfG2p}SF zAJ(qr1_vT;xyaE45KkBHW@rB8%=?Z->{Do%yKWeoA%84jhSI7fQk4HIMRf+Z0HbD4 zh1nZEC!dy+M_Duml+rEJ{9B9|OXCJb_VTvbtk z5Puzs4`bS;c63s%54Nk1aKEb8)D9Di^0 zKC+I}_af}&P+5jh(z|_T{y{dvhd5IICR@z|G{LCLTuZ*@A&;G@aJp<+bzf#!RAMZ3 ziye!8eWN?=K4FWCr!xW^kbd%&2;iN)Rm8nOvgKRCsez!BHSd;}IIFC&&^L4b;Ol5( z9HN4yH(uI-Qlf=ospWGz`m`;#2ga43h4hu~T~fZ!VD*+GlBkh3PS}kvVs5w4^8$K~ zp*C3+KC9L~R)q0W$LKJ!{Nb~QjvfAfba${HzK^~B8s=S}JC#L5pz@Acz2L~?F^)~2 zWvwxqDlLDj@-jG=Exd%uDTTZYxeISfozzlZpz^5|&f4+&8-M+c_hb}U2Wm!Mc7+2t z=iZonyc_km@!+!1am%XLQ7(TGUy|CVX`h=a711vRy?h_jdGb5S!V}R{HdR*1NQ(2z z=LQh9?1qY*PQcb?U(~91Xqoo{s4CNyM>LJ06t=}wJ_+iBurh`Z4Jj^Rr6v?2I zSJJt^K-d2VOn!@*w3+DUrQ?1%9WL40Q)Q4togr$eBwmaqWmpntGFK>W(}zq5>jmLx z^P(=ffK5_PpwtX36(GF+2ZM;buBesB5&=y7Qt+kI1xX1M!KA0pel>aXI8uVN&zYw0 z8K`KQBF!q0_A6-xZ9)sdp%&9rZjK}@ckfEjXEd8{{IO8S+Xd1G!53v1ov#S~YRb_2*RqJWBfV0GU!}qkNPb&#W6ER%n%4>#=14`P zLuQb{@MM^b%Se!?=Yw~t%soh&zTarC#Z_Bqx0MyZ7X=txz_bak0ba3c@qW3ask~@^OBoPFE_dukBU3ha*rzH+SW)8*^<_*n3IX#e3T;;Qb%K;;M*?ay(k zzX!{uNj`_Y-aD#7di^NT1z5Ix2J2^-iyj3Ld7Z>^Wpwb4dh`6cyXM7rju4<4P`_-b z>OEySQi6KPtxenj|3g?P;}tIjnA>dq8TPHjOm593R&~tT79Ec|IMTBOy%k z^~=VOJ)QQneaoV6>2yNR@hb`0A>of_BL zB`55VJhLT5=*x+d6(l(93QFeC33(NDN~qP7^Ms>EUr6ErYa|}vR$PF0@^Z58mtk%f zhGk5yEO01RP!>TLB4<&H`4WoVN0e0KKlIxjL%%3yf{CJe0Y_4VbQMafei;gBGIpet zZq5M=we|`6V^kJ#LT{U>+)U&I1Mg&Mk$OmghZW=Sb1c-cGK^isc;DE?jFqJP{mSlT z&3%9+%Ta{9mN1*Z;73OCPm4^_$m^gOLa9;_7!~3qm*BmlYIg8j)u}A}HK54~pkosF zQUe%#JqZbNJ|OMO+hq864x`1p4<)hJAt6TrrMI2VH zfg6mFK24R>o?D5N3UFEVGC*$`7pB zaT5HmX9WmA9{|GZfAEAjxpUmfg5v*Kwl7HwERw2~>fJ=bT=Ki7^x0>tMl{#INw)NI zAt?-%EbruUv{-@UN+0WikT54Jfr&#@m;O}Kik+yiA8(ry1BPE19Sqlh`JIrKTC@st~yB;Zkl1jc3?5DyCMTpkW z+Knl;jXlvKo%U4b36>z=V>t)V=ii#G8$m?D;&48f)gx&~kKprVdc-taep1FhCp;GY z1`D|bwSn8LJjTK}49SNRH-oi*2Blb0gy%WmVRWhbpC;G`XoAa2MZaWb5COh%L-sBi9~AQ9yUsCm#9IO9Kl&&{RJmEI5Qm~fCQ4l+yX^&N?WrMk2`Dx zxy2tB@}~r%-N_>MEc(zPz;llJsGT0>fD}Qmpup%^ z(z|2f_qOW)uW%1nQ-;G)LixLX0aMBr@z2oCy-MY!u3}x-zB%Kd862NfAIp3I$z3fKy{tNfz%IPuwVZZn_BG7=DO3 zPiJ>kyvP?--%Nu4D4t1Mm1i5`bfxkAawpyJC&ze!DqF*Z2;}jF0$!=f+O|Mr}7w+;nf*9b$F8;f+qx(@q&hG zqZEl21t=~+t}ifO0?C93xzh#X5HR^)C&iKz6Bp-M@CMGeA0znR%!Q3<>eflFj`d`# z&tYD%PP3c|%k7>mFDFCr5|R?7cd7U!LK`D{_G+LW$_t+FQG{NQpg{j&|9>?fzY-DX z)fMFVc)~ad)ytbu{xwd{5Bd8-04u-4$;o>$ThPZeF6sURo-W=>B%m>3)^l$0F4}g= zM)mKR&)>-2!97gf{TyF=4->ynFqhXnlmea6qnfRc6d;gFeZb646xbhHniYW;w=h^v zQVA`JRbW+wXf<=bnX~OC-v1iTx0L?8L$bmxaoWfWGxEfbS!Ng}nGOnGG|wbDgg*KI zXtcf2$wu;r%RI?v7XH{mM-XjUm;>9titw*ch(p=J@tncd7@h@=s;7=3V8L^$Q7hlZ&-c6j`_yxA7kKip4 z%9WWy06!^3Ayrm@@Apu73=?uB+yo0$BCfA2d;1fV+(&iMPT)NMQH}k zkv}t&_fz|MYJoS*;@N-(E^C29@S0``o8Y--_U|!^T$;auxn9G}{~GK5r*T|n$Nw(p z<_r+|_9>iy!HGqIKa8;89AM$G(^4ve+(xVk`I?u{sCaV+?5Md9bIqC`z`kS>)7MV5xJJ&KosBvyBuZe z&JdhOF{1WEYB?p0t_}fX6d2FUjYGk)8yE|<*DFKB!_mUwC_xYXjcBl@|CCvCA@xG? zsSIpzpV`RDGB_tGQmE9S!J`H@$S<;8{SN!xHPrS=#O7$Mn7H4dW}p4)Zrb?~yd7|4 zb|1mTL(U&eE09o;fTp|-VPG?`=e!uPtDEwI;;;zVx_R$ZI)oEq?yv zIN2SM^*WzBg*8!yMXWg6y^guP9+8cd4%Q5ha*B)noAKe_phUbV4ZlpQ*JIjaY!c-L zR8Atf5C7QzmEV3i4g6W%fUUZMfzXXe;9;mo&*S6$i0#nIpJd7}b8Y<(nN?}&R$1m< z_;!(0f$Op+vht49{35A$|GpmIG2@dPt@8c=F^2xkuv&1j?H!^7zCETm(_&OVc{uB zsN*c&3gH{=>ksWyK=m>{{vwm^@8bLh<~BH0eLvGGr1p5F%*$o+%d!S?>8}NGmPMaB ziUdhXp;|qCjA&2F5O%r9H72b#HHg^&oDRhzW&yJXhU$UgIUqofSs2IAE2%|W9Y|sP zY6I*mnbZ_Z$=_{xDHj$WMC}r)bE3gSD}qaF|IHk#mqDMU;{@Khfg^Zln9V`vEM$vX zpTb<1q6CAUHIxM!KaKJrojXn`bu7PmtPI6BA0}<%(6G3Lu^SkpL$68LYWtXV35C?C z>;b0o9@eIT176Rq)&H1oZWSaYft=wBXE|*Bwyb(`vaE^Z67@NQb$px2vFQ9OG%eTpEDQ2Bk(r)jAPwmTqH zuQNK^!t^qx??c0NM0sJpK26F)Kyk?uSS5jDe?3No!)~Bhc~J--f04TX6-=MOYzH51 zz=gnicm(ePTmCokzQQ}9av;viGQm6p7?+4TVk(v`QY6>^sl&Mxd?zGKUCPbdfFco3 zBx1dx&WJ9}Eat`8sZhkv5!uoFhX%G(T)>_gYX$ztbLdGqbx??CshLGVh=K$z50tpE zdq^{N>6e^jH~b}9dw_%azvW2$2-7O&oOnRY;q5`D_A~7Br}1*0b$K_9J5LagXN}hG zBI<<3_A@#~%ueT0Fu`~R>px2GUjR6qJ%-m0q8!IKN$>7Ntx91gS0ce!=wmlD^mU)+jTIhpm4|4IfGj zjrS)bXfbL8#XHL@n1rUv-O8O$Go3P|)JDX7Rro8`f9P@5y=FtQP9!o;;+9;S?U1Oh7#mWk9*K+cXc z^=o*}rLwX9EKdI)qQ8?UGt^`y3Mj7bYG`mZ*Rbopo8WH5#a}1bGfd_5P(bTk%nMi~ zWjBQZL5uaT7S1oBe{B7$>T+0gTGBLEDoH_L0gV+%i(0_iraOG5oCwl0PA!)q8w}+R zHq1en-&d4Yt@JfY$`E7nTSfNiVwTCI@r9(*R3H_5?i-oAcd)PzQpfjE#ph_;GfYAn zgbGD-4Pvy5SiGO!eaT3>m&u@M{3)jHBRIE#b2FT)@uomTTu8P5pqHRcs2<;|3J-2o@kg)7Vio%)Eo|Kasy5oj`s?o`29gW3tTNLP{)_- zY=X)JmABE#tMSs}XsP;pS=aZl=AXj5LKKx&AEv8D**_Q*V8Vjn&QtqKDUOyvxwV^! z(Q-X=yA8Ev=JJX(t}R2w!y)2|t1wV&h;lo7`brM^U#IdB=Pcjt#PA|{oMlD$r5JQM zXEzcyt4c8#%^*`*qKPqNZpWV)bf9b)sMY9P_VN!JZcdpHAjYShU&j08&e5Osb})Ir;sVBkF91=RGZ_s^lR9_`^zJ%ZdpWz^$62$VA;`sS zwa+2)pXl9NhSaaq^ECEr1b;6kn;dWxaE?Y!r%F1B7&UYEb~@Ww<^)EoNccL8&`6Nu9V4M$p&+BDS_&H}(T0L8 z=DS(=T~UG*1z3En1pTI2@Y&ysX>M9=nG|LP&?0z6%%|{B6C;h$2u_G*sYoFj=9kRd zq;P@J)0WC023{JX+(akW6Ah;QKfy_da1rf4P8X5_I2P3XKVup`M(sO-o8jEkbasL+ zZZh)^Afkxu;^PPD+}kk1N(eZ12UqZc(e^lNINlFyTwc^Txqw+DYM_oT-`YMGX_78X zP{@U#gRA7Q-#^3vMp7Q+Z`rks4llow6fIp>Ub?RyJ=niUD#j?!Tor8nqv`yX1)g*4 zqmVh8_APk>tCCe4e`_r5(Zv+S zC~j8=V~u@-?p!*@vW@Ig0j%A5$m;crm~JQkpX2_)6=+HjMa`T*aT#OONIXDuC}@hB zOXwhy6G-U?xqN-a`kgJ^K)IH|CD_upJElbWG8XbW0%uWD&)yIpqOqSP)Ezzqf>&Hf zi4)IxME2S3j^z{eL{4J*B#~1qLVrF&$Y}d&?qYBW+(2~SLLZOO?f)xg^&U^R{~g!W zKS~oq&IG1r7jEblX_N_9DB)Y4CLd->OLUXhL5}5Z%S`R^eMa&8<4ltqNUmU^w)^xs z68sMe-~sHp_Z|3CZ~xQc{3&rEbp`RMD~S1Y;|6@`7nD~E=i_N@lN&0A$&H~^grONE z45`hL-VT;t6n~`bS6Hb*<_JenYRn+?Kf{`TACo$*sI7;-dX|T;#mfb}oI^NEt2MEI zn7N*!=jbxFXDQ&ZaEBcK$m+++Kv4=N^qiuXn~8EGdwMsQ`135fGr0KS;RaKkDqZa9 zVoW_1dZq1qqAHr3Sqv~g3{;i-K4Lb@|V}Ycq&_cX%`m(TegVl_s`1qoqtNa zzh=w>N@p;2q9Cs{RmSCmW+{qplAH?FPX?2}wYbk8Pp{0+|RouaF_K~u1=oq8@ z26XazqPz1-;j3XdA~&!X--e67LCw>->uDCpr%ZeaN9xxKT9o{~)Z+vZIMOew!v9#Q>W6@vB zME`B-7a+_2NQ2+ctaJ&~%XGa)h`rHwk*-zdZO_4?XXo_si;IU&e-JniJPDj8^$=cJ z9UQU#w^99$!GV-8P!)?22@%tn-)FyHc!L@eBjzMcO~${Ld!VDF z=F39T)F4agqY-`9IBl=BKPCPl~sr;m`i0QtRn zk3lp+NPvO?;L@K^HuqX8@rXP$IBwtQPQiwUv~xS9g2 zIF}R|cmbT?s-ytk3$C-&X!4_{GNqx`Q6C+QA1DPubYMQx*s7+J5@E2eQz2vl6uBr>wMzvojyu zao4@&)#Ah5sX#p}-9YIUDjxAJ^|yEFR;;C&l(^j7Df@l0?>AQ{pA7z=k{1}uOI8PT zWdKJXimWm1e=PfczpS-`0q)2DMl51zSu0Ywz={_m-5Op|UC_3l#%z~`f5+!6_|NHm z*}3w8MBdV5+a_&4W6gbt;A6T6LSB(Anwfu$+3Jfc0PT&gOTK@x=$S8iEl$7uljUpp)@v9|$WZHrDXj%}&(e}67rT(=Eu2%F zgWwoiv(-M|5{G@@(h3p0f(sm0jL-0Xc+~+Q)w7e|VbP$A~7V_lrGFJH&ped+UCJXaC? zA>pqm!C3gPoMImYU&bh1Xs?^Z7&0*EXzW&>8k6P6k?^}-**~*=R(H>xBi%si0_kpI zEB6z;+Wi80YcKr*VfO84T$dQZ#ia}ABjRHU`CM@eJ>ned`jiNAsR@+pV_tzKkQ*2{ z(s3z`C(_Oo*XBeKtWJ`YNMXzaM2@f@+nW!?A#zO1B&iu2{c~ ztiLM?5Lqk&i^mrCXMyjK{Qe$?*5)5Z0052m{rz7f9{h3+EVFe-h^YpCa0$cyi1vMo z6jr!E(b6U4YAo(xDU8AeEUr?v@yy&9Aqq-Fu4hO7doIsw_S@8*WRYEgJ`V~k<&n|A zB5`y}_%=osUC%*xEs^W4YVn_K-}u<_&B))6pl*QvQ8gB71bu*s{}@hM(A*d+pW~|5 z-{%RZF4A_9y>llnw+}z`aT5GrAi@8f5&S=&0vOO*xBK(SpC=`tUn)Wpc8B#kkipck zsYm-l6eF7Vr}Sij;he9}J#PvJ80zNi>VteWg@^_Vc?0L{JGpRhjJ>vH-gUHnPoL`* zN+Tsb{4Y1MN^ND8wvWvFo`bIAa_c#;yH=F#a}oTCuU{F_>Nxc+)L(C z!ee;4WlOxVrx;s*%+{YRzikuwYoFl}68XDKN&sEvDz}q;-do4o>GwY3u6@(1aMf)o zyXS{>{-uRHP7b}#$MZC>NNz1>DeG36P60Vf2;*pfY%$|5nJbS&yA9~vYgy<~Dn3Eu zFD)A^Q|1M){7z~#nfeMZ`p7}ov)^{?94xrhZnH1nk{6h)ey_;$@V>*_!;WO)47GbX z!=8Z*w1W0H!z=X*N28hUH5)v;->~Z33*zUH|Ub@@8+^wq4^7HVY z|D03Rw;elaCk{tCx^?fZd;4~M-@l&!{Kff|DS8HT?K6CH2*rB}bNQ&(qLTJC+M)Dm z-*I8EdF8-x>$$i86)g4302TqP0RFDp$(W=8z<&lMRh$0;=eRqC@mj#J2_Val&RUR7 zUUxQV3U&s0v52gMi=q)+)CdziVsQ+Ev?UjR(j2Q_!L{mZC^UBuv6g~kx6S?Y;A`#cOt9QZ;t}~#uwV}fTev4z$)1Ok0<@iod^H`+MfEee}Eo-Uwi@QZ9f^yWbh;)f9yO7 zU}DYy)_Rh#0~tv`zV%roK`@h|C&f4LbCNHR&N1DCk)HMK*iCYi%OoY_*x57Apz`1k z$`buT;o7)|P5lVU)^Eb72Wy~H<8ST=ya%5=nHjyw;7hulcbhnGO68i(!nrKDP6(-9 z=cV5o0pB}*+F-DHiH6=F000sHNklfOHmD{!9)i1G%RFvJ7M_NEu$h$TC1#K-9nDiV%V<0p}oc+)g%g z0C_&9uD?$iZ~Ji*uDqdH80x~hEjW@8E2%>AVW%Z;^R>aS)L0)cV5R&L2J|)P?$dBI zSG!c#RrAf?m4v_H0}1%f1>?<{L6U6ZRQn{Gwok&?7hsL%{eI+-shmsgTnzhk#nNAt z7R9)Q@uGSgEd85c@UMYYzn%1ZucrYBfVQW<_0P~---XrQ5%j9xXplCEX|O>Xh_-On z=M93{@LEBfLcbB!*;sHQ`2m7FN*-ad)TdIYr~Nd>ftUWRf)~il?Eg8D7&j#HklUVOkSC{?e=d ztt>Tph8cLav&8w+?;te8( z9JIA4jK;9kSSfxMTlg(F?A`W#H!@hb6PiRjhT!dsIhX`~2&YW|p9Kae%e!GZ*I*U5 z;Z;gOIRw=oq`xp2ZOz;A-SJ6qsJ9NT`Ma+41J~C8avijN`)^*Pa{V7U#uq!U{WNt= zAR(jCe4f^vDh`fhbP^@x2`i4yH%OKgb1m*ovS6631(b4VDV^*Vs-MBh9#;QjZ84~T zP5aAm>M*Er_Kg4^!6$^cEI1AvLOTIJsQL!@q9I-4Xj=<18dbR;rOu#@WpCd&KP~6^ zD~qBuC@YJ>^88Zy?)f*sNuR5T(!VYAOUI63V1on%TfCRe-TN3+=fP0)k%W6}w^j1o z7lBAo2$Il_RM<9{N^}SYNl9%dqP|e^K`wNX>6I7O+-! z65ZCjX#0*K$|{g7^Og+$sLwRwu8mnLsj6Z|v@kXC;wg=zCA*xNmY znQjNuo!sxr$wEP;B?>zv$;x>=62;#PPoJ!w>a%-5! z-(q2E9W&iFrrJ3=d5+xQVve(h(ev*YIf`--L-lK*Mo%GNa)hxH@FBe{i#3VV!!M18 z_+COiagJRBe687Zii2Iv0&W?d_5~=i93*(BuP7`=g~sOD&zv2Vkan+U$qfT=*nLD?bl){g-yx!|K>^**W~kE_igNHzk=B*hv`lmoi^XN65!xLTcFg0ab-E_Gah17{Q;`%X%M>n z{k7~8O$J>&dtf{8$%_G@b2YJL<=pfz9%sO%hoRd)VLi?TB4uN)o@ zk8b@3jkZ?7>ONKdBgDAry5obwz~*oOO1JpzYpT2F&((Z;KV|BO0+L=3DpAmKR}etS zjXaWz>WH91V_OI1a&90=Ibs6Gn-Qfr`Q*uiKm?7JJ&N}95G~c=hLn!Lx1S@FwvAEM zMWr_}C^d%TF*fy2qGM=3({58 z04eyof^`eftwYGwIS>^flcCzYzZ#vw|Jap_7ZJC5T?8k7qzAwajhoc~aycxmow}gr zA5yA4_XVhIrsfaO1Wmx1fso}>>9-NkF#BvSge>Cu1M#!|z1%HgPW?3&Is?pfTim26 z;|37cI+O+&RfKW1h5mSiwem|CK@iL z)>CY7_QdA1+K=G#F@ZE-i+R}LAyn%RmBm~36@B*AAA{}wMX=o$GE7YQ?le9O0p#j9 zT7B(ZweaYaYWJQ1R67=Nd;kfOb-?f_>OYCZ3CVhSAVLZZA;%f?^6z7=TVlG?^7koV zt->hpTgkbx|&Zic0r<<=E-Xb=r#e|XEV0}ACfz1g2yK7Cpv_C z6do%;8wOuj!6dk14!VB`dh1Z77nhFObI-jFj`80|wEL^LLA#%h4@&?5zEW%D_#5hu zN9R?i_j&LZKnAr*0;(_iDjz$?*dLI1I%oqDO(w5XJlB&T!xCn)AE4JQ(ech75y$*R zr7^5DHphgO(UaIz--4xk8g|}cA`x8jUYfV3RN9ke+-<<~vTl9hEGEuUr!P8w-UI~j zt4Iv1om)9}+?{{sRq*1@GFb9usdrnk$Bm4SLI47jz|hLEQ)=On1=a37jwFf`i4NX- z0MWk=go~~MF+Awxb|8X3&)eFJHqmSSJ7(KsbX&P^x=dUZQyFZH+PE_MDmL(!Ae!4j z@+km4DU^%vQHbT?E1ku|-v)eG2hn~dR0nS#R@cGzj$b^-gC4*)Z=p zVDQg_oxUMH1o*&5BY;N)7EFSZYT@Ap)$08Or00WzuV_JH22$X?Nk}w20Pt3H|Mfxy zLIOsz%nFTO`+Jz}4A9BBdfqrfQ5g&h!sXE~V#ECrLOJy|iEotH*a^>V1fM;6ihxU) z=g*74Tb~ZMHK`BwUe_f**nXoywu;W6)y}OPKjF@P=VfqgzZ3X3rTS%jR005z1e#Wk zzpfS@oKx-IV*pxVJtsLcCUq!L>0SfWw1GbsBKWQ8sbdBrv~dD+?H^*cJw_|z?bO<` z`d=Fz!CLYAaBiQMX83}F#BmGtWwtKyUcE=a)0o}{Ex_3s4}5x@)Jja0dRuK*{|OPu zYQU70TRC>zOMVl4+21G4A3xOC4FbUb00ThN%865|cW+y@<{pB|rc>`eXn>l%Prdsx%0%0$Ni<%&f z@srIm!Mk_$_`d1rv5imc{Uj1t4OD}HyZXv;cmA0Z*sk_>1$g3B2{HUz|TvC4}H?U-bSX5vW*Or7JHTa~FPa3X{R_ zV;8^&c9#G^Btb>jPQQoF{07=H`;m1IAneb;NJ+5y&IRSn654a~sy%K*xcYR}!xX3uP_Pav+;SwJeZ3kjsJ0ayODZ2U+P-CQT`F zQ0}+%+99m5s4@j>GHB;88tsQEA7Do->_Dz3pPD~790mV`lIKhyN=cY5@1_nS$wE(> z{`KI?MjJ5+LX>J_-L0K`!!17h8e%!uG8p(}Fd3w>XV<_7ZjAm$g`{5u-Xx{u^5c+M^XG7V5cGoH3@Id z1T5E(`lL`3iF8YKQ;>ei?ulf3X&11AbC*iJHK47t@44mYkI|(cz6s9mT?VWB^I-LV z4RJ%~u7VHTS^@w*Q3T(bjJE#w0{Ul9t4@zpK7ALE&v7J>*!>XWi>j}OY^veCXKEcP zKLh79j3r(xA3X(KJW-G1!j2ZtsH+1d3OVzxke9{_nTI_IwKr^cAqpUjc`FL--D!x9;-S$0tDm`MWvzF4l7Z?gX$O z!2ay;*T1Ufk9<+h9(oK}yNf)7%32Uo1ae4IpUe{?Q%nej_*_UJ;MevPNU4e+B%mkY zhlUpL+DCj78>50o2uXniRYilP^R#~Eth@BWJ7CS%N37YGc@6c>UAJ87)A30WKt=`* zMW+Dt5H-SG*@3ToSuH&Bq?$eWS#%cej*Y0YgbWH$erIwZfj<`DClR4JCIO#@fRvIU zd7kWN2%VbC^)>1*F1htL&(Ye+C9uRxf1Z_OO2>`@tnNQHsZYlzO#m5U7N!8q16TmC z55PV(d*}hR?+Z_=*~6bzGj~6L*35!*BUK{(D(O(#*q$CqkPMaVhoQ!G_u$6{juXs) z-}Dh))5AWEmfxqXa~EmtzXrDVtN!^Gl7`K@d@XNk+%5zFypl#FLJv%a zJzx`Yr|R8*zuNQI=hW<>BWh~jVRZL!O?Loaw1FoARC0_YDbyH}`~ezCAf7?$!%{Rr z<+T-zu3VwP(k0q>>mm)8*T8CAnlWyI)%!KDv~M9wyp(Nytm=N!xSa?fBPBu?zziZ8 z?(lN~S$kgXef$wMd(VAp`mWEQyJx@ZEbK+rp5kvwNdQDMu)_#WpMMgF0Kioxm7D7* z*VbrsWt9dOuF}StWjG_9x5r=yPJrL=Q$Qd2^KKj9W!#PgNJj>C7CT_AFb#GZg%CYI z=KxHr>AUt~?%o4xYVSVPxnmF7y?L}}=8#X#sJuIctlfobwfQdEDg#KmUuZa8ku3|D z@fc<}q^dtaxxPik>IQ}vuTilkD{vKJ&wWwzeLn^8fq$>?=URNis(z<&yAwc$_vFw* z)C|*p&Nk+Zf4vJPP8&?37UFHwWj5nMmrM%=T#{H7qO^~|5}(d7Vk>ry=-Ayh(mrW? zS_qI1A%lS00fU{+X|TqS=WRcQKtcpDH=>pWcmYr&-eadkl=`wUD@3x~_MoTZ(@22k zkeq@{0a+g9Sl+)G4Io-Th+vXOo6JuQ;LY*>fdI`RQY`?d(w<8Fj`08@8B(o~zV@jF bygB|4U}or#%nY-f00000NkvXXu0mjfqs@1C literal 0 HcmV?d00001 diff --git a/src/cx/fbn/nevernote/icons/splash_logo.png b/src/cx/fbn/nevernote/icons/splash_logo.png index 6206ea760f480b7d3b6db7b88d969da1bcd466d2..dd130bee016e4821173f4ee86ac604d7bfc45395 100644 GIT binary patch literal 14705 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 NSzbe~M#dcQ{{fY&Rrmk^ literal 194775 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

6ojGJvn>{mLcj=$e?68~C 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 getAllDeleted() { - DeletedItemRequest request = new DeletedItemRequest(); - request.requestor_id = id; - request.type = DeletedItemRequest.Get_All; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - DeletedItemRequest req = Global.dbRunner.deletedItemResponse.get(id).copy(); - return req.responseDeletedRecords; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Insert Into DeletedItems (guid, type) Values(:guid, :type)"); + query.bindValue(":guid", guid); + query.bindValue(":type", type); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Insert into deleted items failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } + // Add an item to the deleted table public void expungeDeletedItem(String guid, String type) { - DeletedItemRequest request = new DeletedItemRequest(); - request.requestor_id = id; - request.string1 = guid; - request.string2 = type; - request.type = DeletedItemRequest.Expunge_Record; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("delete from DeletedItems where guid=:guid and type=:type"); + query.bindValue(":guid", guid); + query.bindValue(":type", type); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Expunge deleted items failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + } + public List getAllDeleted() { + logger.log(logger.HIGH, "Entering DeletedTable.getAllDeleted"); + List list = new ArrayList(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Select guid, type from DeletedItems"); + while (query.next()) { + DeletedItemRecord record = new DeletedItemRecord(); + record.guid = query.valueString(0); + record.type = query.valueString(1); + list.add(record); + } + logger.log(logger.HIGH, "Leaving DeletedTable.getAllDeleted"); + return list; + } public void expungeAllDeletedRecords() { - DeletedItemRequest request = new DeletedItemRequest(); - request.requestor_id = id; - request.type = DeletedItemRequest.Expunge_All; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("delete from DeletedItems"); } } diff --git a/src/cx/fbn/nevernote/sql/InvalidXMLTable.java b/src/cx/fbn/nevernote/sql/InvalidXMLTable.java index 9d504e8..6bc373a 100644 --- a/src/cx/fbn/nevernote/sql/InvalidXMLTable.java +++ b/src/cx/fbn/nevernote/sql/InvalidXMLTable.java @@ -23,81 +23,199 @@ package cx.fbn.nevernote.sql; import java.util.ArrayList; import java.util.List; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.InvalidXMLRequest; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; import cx.fbn.nevernote.utilities.ListManager; public class InvalidXMLTable { ListManager parent; - int id; + private final ApplicationLogger logger; + private final DatabaseConnection db; + // Constructor - public InvalidXMLTable(int i) { - id = i; + public InvalidXMLTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.type = InvalidXMLRequest.Create_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); +// query.exec("drop table InvalidXML"); + logger.log(logger.HIGH, "Creating table InvalidXML..."); + if (!query.exec("Create table InvalidXML (type varchar, element varchar, attribute varchar,primary key(type, element,attribute) );")) + logger.log(logger.HIGH, "Table InvalidXML creation FAILED!!!"); +// query.clear(); + + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'button', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'embed', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'fieldset', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'form', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'input', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'label', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'legend', '');"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'o:p', '')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'option', '')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'script', '')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'select', '')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'wbr', '')"); + + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'done')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'id')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'onclick')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'onmousedown')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'div', 'id')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'dl', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'dl', 'id')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'dt', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h1', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h2', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h3', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h4', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h5', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'img', 'gptag')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'li', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'ol', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'ol', 'id')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'p', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'p', 'id')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'p', 'span')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'accesskey')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'action')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'alt')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'bgcolor')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'checked')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'flashvars')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'for')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'height')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'id')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'maxlength')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'method')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'name')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'onblur')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'onchange')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'aclick')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'onsubmit')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'quality')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'selected')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'src')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'target')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'type')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'value')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'width')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'wmode')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'table', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'td', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'tr', 'class')"); + query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'ul', 'class')"); + } // Drop the table public void dropTable() { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.type = InvalidXMLRequest.Drop_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table InvalidXML"); } - // Add an invalid XML element to the table - public void addInvalidElement(String element) { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.string1 = element; - request.type = InvalidXMLRequest.Add_Invalid_Element; - Global.dbRunner.addWork(request); + // Add an item to the table + public void addAttribute(String element, String attribute) { + if (attributeExists(element,attribute)) + return; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Insert Into InvalidXML (type, element, attribute) Values('ATTRIBUTE', :element, :attribute)"); + query.bindValue(":element", element); + query.bindValue(":attribute", attribute); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Insert Attribute into invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } - // Add an invalid XML attribute to the table - public void addInvalidAttribute(String element, String attribute) { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.string1 = element; - request.string2 = attribute; - request.type = InvalidXMLRequest.Add_Invalid_Attribute; - Global.dbRunner.addWork(request); + // Add an item to the table + public void addElement(String element) { + if (elementExists(element)) + return; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Insert Into InvalidXML (type, element) Values('ELEMENT', :element)"); + query.bindValue(":element", element); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Insert Element into invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } - // Get invalid attributes for a given element + // get invalid elements + public List getInvalidElements() { + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("Select element from InvalidXML where type = 'ELEMENT'")) { + logger.log(logger.MEDIUM, "getInvalidElement from invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + List elements = new ArrayList(); + while (query.next()) { + elements.add(query.valueString(0)); + } + return elements; + } + + // get invalid elements public List getInvalidAttributeElements() { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.type = InvalidXMLRequest.Get_Invalid_Attribute_Elements; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - InvalidXMLRequest req = Global.dbRunner.invalidXMLResponse.get(id).copy(); - return req.responseList; + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("Select distinct element from InvalidXML where type = 'ATTRIBUTE'")) { + logger.log(logger.MEDIUM, "getInvalidElement from invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + List elements = new ArrayList(); + while (query.next()) { + elements.add(query.valueString(0)); + } + return elements; } - // Get the list of elements which we have invalid attributes for + // get invalid attributes for a given element public ArrayList getInvalidAttributes(String element) { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.string1 = element; - request.type = InvalidXMLRequest.Get_Invalid_Attributes; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - InvalidXMLRequest req = Global.dbRunner.invalidXMLResponse.get(id).copy(); - return req.responseArrayList; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select attribute from InvalidXML where type = 'ATTRIBUTE' and element = :element"); + query.bindValue(":element", element); + if (!query.exec()) { + logger.log(logger.MEDIUM, "getInvalidElement from invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + ArrayList elements = new ArrayList(); + while (query.next()) { + elements.add(query.valueString(0)); + } + return elements; } - // Add an invalid XML attribute to the table - public List getInvalidElements() { - InvalidXMLRequest request = new InvalidXMLRequest(); - request.requestor_id = id; - request.type = InvalidXMLRequest.Get_Invalid_Elements; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - InvalidXMLRequest req = Global.dbRunner.invalidXMLResponse.get(id).copy(); - return req.responseList; + + // Determine if an element already is in the table + public boolean elementExists(String element) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select element from InvalidXML where type='ELEMENT' and element=:element"); + query.bindValue(":element", element); + if (!query.exec()) { + logger.log(logger.MEDIUM, "elementExists in invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + if (query.next()) + return true; + else + return false; } - - + // Determine if an element already is in the table + public boolean attributeExists(String element, String attribute) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select element from InvalidXML where type='ATTRIBUTE' and element=:element and attribute=:attribute"); + query.bindValue(":element", element); + query.bindValue(":attribute", attribute); + if (!query.exec()) { + logger.log(logger.MEDIUM, "attributeExists in invalidXML failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + if (query.next()) + return true; + else + return false; + } } diff --git a/src/cx/fbn/nevernote/sql/NoteResourceTable.java b/src/cx/fbn/nevernote/sql/NoteResourceTable.java index a0461e7..25bd0d5 100644 --- a/src/cx/fbn/nevernote/sql/NoteResourceTable.java +++ b/src/cx/fbn/nevernote/sql/NoteResourceTable.java @@ -19,207 +19,551 @@ package cx.fbn.nevernote.sql; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.List; +import com.evernote.edam.type.Data; import com.evernote.edam.type.Resource; +import com.evernote.edam.type.ResourceAttributes; +import com.trolltech.qt.core.QByteArray; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.ResourceRequest; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; -public class NoteResourceTable { - private final int id; + +public class NoteResourceTable { + /** + * + */ + private static final long serialVersionUID = 1L; + private final ApplicationLogger logger; + private final DatabaseConnection db; // Constructor - public NoteResourceTable(int i) { - id = i; + public NoteResourceTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Create_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + // Create the NoteResource table + logger.log(logger.HIGH, "Creating table NoteResource..."); + if (!query.exec("Create table NoteResources (guid varchar primary key, " + + "noteGuid varchar, updateSequenceNumber integer, dataHash varchar, "+ + "dataSize integer, dataBinary blob, "+ + "mime varchar, width integer, height integer, duration integer, "+ + "active integer, recognitionHash varchar, recognitionSize integer, " + + "recognitionBinary varchar, attributeSourceUrl varchar, attributeTimestamp timestamp, " + + "attributeLatitude double, attributeLongitude double, "+ + "attributeAltitude double, attributeCameraMake varchar, attributeCameraModel varchar, " + +"attributeClientWillIndex varchar, attributeRecoType varchar, attributeFileName varchar,"+ + "attributeAttachment boolean, isDirty boolean, indexNeeded boolean)")) + logger.log(logger.HIGH, "Table NoteResource creation FAILED!!!"); + if (!query.exec("CREATE INDEX unindexed_resources on noteresources (indexneeded desc, guid);")) + logger.log(logger.HIGH, "Noteresources unindexed_resources index creation FAILED!!!"); + if (!query.exec("CREATE INDEX resources_dataheshhex on noteresources (datahash, guid);")) + logger.log(logger.HIGH, "Noteresources resources_datahash index creation FAILED!!!"); + } // Drop the table public void dropTable() { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Drop_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table NoteResources"); } // Reset the dirty flag public void resetDirtyFlag(String guid) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Reset_Dirty_Flag; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Update noteresources set isdirty=false where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Error resetting noteresource dirty field. " +query.lastError()); } // Set if the resource should be indexed public void setIndexNeeded(String guid, Boolean indexNeeded) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Set_Index_Needed; - request.string1 = new String(guid); - request.bool1 = indexNeeded; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Update noteresources set indexNeeded=:needed where guid=:guid"); + query.bindValue(":needed", indexNeeded); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Error setting noteresource indexneeded field: " +query.lastError()); } // get any unindexed resource public List getNextUnindexed(int limit) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Next_Unindexed; - request.int1 = limit; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseStrings; + List guids = new ArrayList(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + + if (!query.exec("Select guid from NoteResources where indexNeeded = true limit " +limit)) + logger.log(logger.EXTREME, "NoteResources SQL retrieve has failed on getNextUnindexed(): " +query.lastError()); + + // Get a list of the notes + String guid; + while (query.next()) { + guid = new String(); + guid = query.valueString(0); + guids.add(guid); + } + return guids; } + + + public void saveNoteResource(Resource r, boolean isDirty) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Save_Note_Resource; - request.resource = r.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); -// Global.dbClientWait(id); + logger.log(logger.HIGH, "Entering DBRunner.saveNoteResources"); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + check = query.prepare("Insert Into NoteResources (" + +"guid, noteGuid, dataHash, dataSize, dataBinary, updateSequenceNumber, " + +"mime, width, height, duration, active, recognitionHash, " + +"recognitionSize, recognitionBinary, attributeSourceUrl, attributeTimestamp, " + +"attributeLatitude, attributeLongitude, attributeAltitude, attributeCameraMake, " + +"attributeCameraModel, " + +"attributeClientWillIndex, attributeRecoType, attributeFileName, attributeAttachment, isDirty, " + +"indexNeeded) Values(" + +":guid, :noteGuid, :dataHash,:dataSize, :dataBody, :updateSequenceNumber, " + +":mime, :width, :height, :duration, :active, :recognitionHash, " + +":recognitionSize, :recognitionBody, :attributeSourceUrl, :attributeTimestamp, " + +":attributeLatitude, :attributeLongitude, :attributeAltitude, :attributeCameraMake, " + +":attributeCameraModel, " + +":attributeClientWillIndex, :attributeRecoType, :attributeFileName, :attributeAttachment, " + +":isDirty, true)"); + if (!check) { + logger.log(logger.EXTREME, "NoteResource SQL insert prepare has failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + + query.bindValue(":guid", r.getGuid()); + query.bindValue(":noteGuid", r.getNoteGuid()); + if (r.getData() != null) { +// query.bindValue(":dataHash", new QByteArray(r.getData().getBodyHash()).toHex()); +// query.bindValue(":dataHash", ""); + query.bindValue(":dataHash", byteArrayToHexString(r.getData().getBodyHash())); + query.bindValue(":dataSize", r.getData().getSize()); + query.bindBlob(":dataBody", r.getData().getBody()); + } + query.bindValue(":updateSequenceNumber", r.getUpdateSequenceNum()); + query.bindValue(":mime", r.getMime()); + query.bindValue(":width", new Integer(r.getWidth())); + query.bindValue(":height", new Integer(r.getHeight())); + query.bindValue(":duration", new Integer(r.getDuration())); + query.bindValue(":active", r.isActive()); + if (r.getRecognition() != null) { + query.bindValue(":recognitionHash", r.getRecognition().getBodyHash()); + query.bindValue(":recognitionSize", r.getRecognition().getSize()); + if (r.getRecognition().getBody() != null) + query.bindValue(":recognitionBody", new String(r.getRecognition().getBody())); + else + query.bindValue(":recognitionBody", ""); + } else { + query.bindValue(":recognitionHash", ""); + query.bindValue(":recognitionSize", 0); + query.bindValue(":recognitionBody", ""); + } + if (r.getAttributes() != null) { + query.bindValue(":attributeSourceUrl", r.getAttributes().getSourceURL()); + StringBuilder ts = new StringBuilder(simple.format(r.getAttributes().getTimestamp())); + query.bindValue(":attributeTimestamp", ts.toString()); + query.bindValue(":attributeLatitude", r.getAttributes().getLatitude()); + query.bindValue(":attributeLongitude", r.getAttributes().getLongitude()); + query.bindValue(":attributeAltitude", r.getAttributes().getAltitude()); + query.bindValue(":attributeCameraMake", r.getAttributes().getCameraMake()); + query.bindValue(":attributeCameraModel", r.getAttributes().getCameraModel()); + query.bindValue(":attributeClientWillIndex", r.getAttributes().isClientWillIndex()); + query.bindValue(":attributeRecoType", r.getAttributes().getRecoType()); + query.bindValue(":attributeFileName", r.getAttributes().getFileName()); + query.bindValue(":attributeAttachment", r.getAttributes().isAttachment()); + } + query.bindValue(":isDirty", isDirty); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "*** NoteResource Table insert failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + + + logger.log(logger.HIGH, "Leaving DBRunner.saveNoteResources"); } // delete an old resource public void expungeNoteResource(String guid) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Expunge_Note_Resource; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("delete from NoteResources where guid=:guid"); + query.bindValue(":guid", guid); + query.exec(); } + // Get a note resource from the database by it's hash value public String getNoteResourceGuidByHashHex(String noteGuid, String hash) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Note_Resource_Guid_By_Hash_Hex; - request.string1 = new String(noteGuid); - request.string2 = new String(hash); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseString; + logger.log(logger.HIGH, "Entering DBRunner.getNoteResourceGuidByHashHex"); + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Select guid from NoteResources " + + "where noteGuid=:noteGuid and dataHash=:hash"); + if (check) + logger.log(logger.EXTREME, "NoteResource SQL select prepare was successful."); + else { + logger.log(logger.EXTREME, "NoteResource SQL select prepare has failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + query.bindValue(":noteGuid", noteGuid); + query.bindValue(":hash", hash); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "dbRunner.getNoteResourceGuidByHashHex Select failed." + + "Note Guid:" +noteGuid+ + "Data Body Hash:" +hash); + logger.log(logger.MEDIUM, query.lastError()); + } + if (!query.next()) { + logger.log(logger.MEDIUM, "Note Resource not found."); + return null; + } + return query.valueString(0); } + // Get a note resource from the database by it's hash value public Resource getNoteResourceDataBodyByHashHex(String noteGuid, String hash) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Note_Resource_Data_Body_By_Hash_Hex; - request.string1 = new String(noteGuid); - request.string2 = new String(hash); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseResource; - } - - // Update a note resource guid - public void updateNoteResourceGuid(String oldGuid, String newGuid, boolean isDirty) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Update_Note_Resource_Guid; - request.string1 = new String(oldGuid); - request.string2 = new String(newGuid); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); -// Global.dbClientWait(id); - } - + logger.log(logger.HIGH, "Entering DBRunner.getNoteResourceDataBodyByHash"); - // Reset update sequence number to zero - public void resetUpdateSequenceNumber(String guid, boolean isDirty) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Reset_Update_Sequence_Number; - request.string1 = new String(guid); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Select guid, mime, from NoteResources " + + "where noteGuid=:noteGuid and dataHash=:hash"); + if (!check) { + logger.log(logger.EXTREME, "NoteResource SQL select prepare has failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + query.bindValue(":noteGuid", noteGuid); + query.bindValue(":hash", hash); + + if (!query.exec()) { + logger.log(logger.MEDIUM, "NoteResource Select failed." + + "Note Guid:" +noteGuid+ + "Data Body Hash:" +hash); + logger.log(logger.MEDIUM, query.lastError()); + } + if (!query.next()) { + logger.log(logger.MEDIUM, "Note Resource not found."); + return null; + } + + Resource r = new Resource(); + r.setGuid(query.valueString(0)); + r.setMime(query.valueString(1)); + + NSqlQuery binary = new NSqlQuery(db.getConnection()); + if (!binary.prepare("Select databinary from NoteResources " + + "where guid=:guid")) { + logger.log(logger.MEDIUM, "Prepare for NoteResources Binary failed"); + logger.log(logger.MEDIUM, binary.lastError()); + } + + if (!binary.exec()) { + logger.log(logger.MEDIUM, "NoteResources Binary Select failed." + + "Note Guid:" +noteGuid+ + "Data Body Hash:" +hash); + logger.log(logger.MEDIUM, binary.lastError()); + } + if (!binary.next()) { + logger.log(logger.MEDIUM, "Note Resource Binary not found."); + return null; + } + + Data d = new Data(); + r.setData(d); + d.setBody(binary.valueString(0).getBytes()); + logger.log(logger.HIGH, "Leaving DBRunner.getNoteResourceDataBodyByHash"); + return r; } + // Get a note's resourcesby Guid public Resource getNoteResource(String guid, boolean withBinary) { - if (guid == null) + if (guid == null) + return null; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + String queryString; + queryString = new String("Select guid, noteGuid, mime, width, height, duration, " + +"active, updateSequenceNumber, dataHash, dataSize, " + +"recognitionHash, recognitionSize, " + +"attributeLatitude, attributeLongitude, attributeAltitude, " + +"attributeCameraMake, attributeCameraModel, attributeClientWillIndex, " + +"attributeRecoType, attributeFileName, attributeAttachment, recognitionBinary " + +" from NoteResources where guid=:guid"); + + + query.prepare(queryString); + + query.bindValue(":guid", guid); + if (!query.exec()) { + logger.log(logger.EXTREME, "NoteResources SQL select has failed."); + logger.log(logger.MEDIUM, query.lastError()); return null; - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Note_Resource; - request.string1 = new String(guid); - request.bool1 = withBinary; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseResource; + } + Resource r = null; + if (query.next()) { + + r = new Resource(); + r.setGuid(query.valueString(0)); // Resource Guid + r.setNoteGuid(query.valueString(1)); // note Guid + r.setMime(query.valueString(2)); // Mime Type + r.setWidth(new Short(query.valueString(3))); // Width + r.setHeight(new Short(query.valueString(4))); // Height + r.setDuration(new Short(query.valueString(5))); // Duration + r.setActive(new Boolean(query.valueString(6))); // active + r.setUpdateSequenceNum(new Integer(query.valueString(7))); // update sequence number + + Data d = new Data(); + byte[] h = query.valueString(8).getBytes(); // data hash + QByteArray hData = new QByteArray(h); + QByteArray bData = new QByteArray(QByteArray.fromHex(hData)); + d.setBodyHash(bData.toByteArray()); + d.setSize(new Integer(query.valueString(9))); + r.setData(d); + + Data rec = new Data(); + if (query.valueObject(10) != null) + rec.setBodyHash(query.valueString(10).getBytes()); // Recognition Hash + if (query.valueObject(11) != null) + rec.setSize(new Integer(query.valueString(11))); + else + rec.setSize(0); + r.setRecognition(rec); + + ResourceAttributes a = new ResourceAttributes(); + if (!query.valueString(12).equals("")) // Latitude + a.setLatitude(new Float(query.valueString(12))); + if (!query.valueString(13).equals("")) // Longitude + a.setLongitude(new Float(query.valueString(13))); + if (!query.valueString(14).equals("")) // Altitude + a.setAltitude(new Float(query.valueString(14))); + a.setCameraMake(stringValue(query.valueString(15))); // Camera Make + a.setCameraModel(stringValue(query.valueString(16))); + a.setClientWillIndex(booleanValue(query.valueString(17).toString(),false)); // Camera Model + a.setRecoType(stringValue(query.valueString(18))); // Recognition Type + a.setFileName(stringValue(query.valueString(19))); // File Name + a.setAttachment(booleanValue(query.valueString(20).toString(),false)); + r.setAttributes(a); + + if (withBinary) { + + query.prepare("Select dataBinary from NoteResources where guid=:guid"); + query.bindValue(":guid", r.getGuid()); + query.exec(); + if (query.next()) { + byte[] b = query.getBlob(0); + r.getData().setBody(b); + } + } + } + return r; } // Get a note's resourcesby Guid public List getNoteResources(String noteGuid, boolean withBinary) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Note_Resources; - request.string1 = new String(noteGuid); - request.bool1 = withBinary; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseResources; + if (noteGuid == null) + return null; + List res = new ArrayList(); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select guid" + +" from NoteResources where noteGuid = :noteGuid"); + query.bindValue(":noteGuid", noteGuid); + if (!query.exec()) { + logger.log(logger.EXTREME, "NoteResources SQL select has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + while (query.next()) { + String guid = (query.valueString(0)); + res.add(getNoteResource(guid, withBinary)); + } + return res; } - - // Get all of a note's recognition data by the note guid public List getNoteResourcesRecognition(String noteGuid) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Note_Resources_Recognition; - request.string1 = new String(noteGuid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseResources; + if (noteGuid == null) + return null; + boolean check; + List res = new ArrayList(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Select " + +"recognitionHash, recognitionSize, recognitionBinary " + +" from NoteResources where noteGuid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "NoteTable.getNoteRecognition SQL prepare has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + query.bindValue(":guid", noteGuid); + if (!check) { + logger.log(logger.EXTREME, "NoteTable.getNoteRecognition exec has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + while (query.next()) { + Resource r = new Resource(); + res.add(r); + Data rec = new Data(); + rec.setBodyHash(query.valueString(0).getBytes()); + String x = new String(query.valueString(1)); + if (!x.equals("")) { + rec.setSize(new Integer(x)); + rec.setBody(query.valueString(2).getBytes()); + } else + rec.setSize(0); + r.setRecognition(rec); + } + return res; } + // Get a note's recognition data by it's guid. public Resource getNoteResourceRecognition(String guid) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Note_Resource_Recognition; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseResource; + if (guid == null) + return null; + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Select " + +"recognitionHash, recognitionSize, recognitionBinary, noteGuid " + +" from NoteResources where guid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "NoteTable.getNoteRecognition SQL prepare has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + query.bindValue(":guid", guid); + query.exec(); + if (!check) { + logger.log(logger.EXTREME, "NoteTable.getNoteRecognition exec has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + Resource r = null; + while (query.next()) { + + r = new Resource(); + Data rec = new Data(); + rec.setBodyHash(query.valueString(0).getBytes()); + String x = new String(query.valueString(1)); + if (!x.equals("")) { + rec.setSize(new Integer(x)); + rec.setBody(query.valueString(2).getBytes()); + } else + rec.setSize(0); + r.setRecognition(rec); + r.setNoteGuid(query.valueString(3)); + } + return r; } + // Save Note Resource public void updateNoteResource(Resource r, boolean isDirty) { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Update_Note_Resource; - request.resource = r.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + logger.log(logger.HIGH, "Entering ListManager.updateNoteResource"); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("delete from NoteResources where guid=:recGuid"); + query.bindValue(":recGuid", r.getGuid()); + query.exec(); + saveNoteResource(r, isDirty); + logger.log(logger.HIGH, "Leaving RNoteResourceTable.updateNoteResource"); + } + // Update note resource GUID + public void updateNoteResourceGuid(String oldGuid, String newGuid, boolean isDirty) { + logger.log(logger.HIGH, "Entering RNoteResourceTable.updateNoteResourceGuid"); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("update NoteResources set guid=:newGuid, isDirty=:isDirty where guid=:oldGuid"); + query.bindValue(":newGuid", newGuid); + query.bindValue(":isDirty", isDirty); + query.bindValue(":oldGuid", oldGuid); + query.exec(); + logger.log(logger.HIGH, "Leaving RNoteResourceTable.updateNoteResourceGuid"); + } + // Update note resource GUID + public void resetUpdateSequenceNumber(String guid, boolean isDirty) { + logger.log(logger.HIGH, "Entering RNoteResourceTable.updateNoteResourceGuid"); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("update NoteResources set updateSequenceNumber=0, isDirty=:isDirty where guid=:guid"); + query.bindValue(":isDirty", isDirty); + query.bindValue(":guid", guid); + query.exec(); + logger.log(logger.HIGH, "Leaving RNoteResourceTable.updateNoteResourceGuid"); } // Drop the table public void reindexAll() { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Reindex_All; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Update NoteResources set indexneeded=true"); } // Count unindexed notes public int getResourceCount() { - ResourceRequest request = new ResourceRequest(); - request.requestor_id = id; - request.type = ResourceRequest.Get_Resource_Count; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - ResourceRequest req = Global.dbRunner.resourceResponse.get(id).copy(); - return req.responseInteger; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("select count(*) from noteresources"); + query.next(); + int returnValue = new Integer(query.valueString(0)); + return returnValue; + } + + //******************************************** + //** 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(); } + + + private String stringValue(Object value) { + if (value != null && value.toString() != null) + return value.toString(); + else + return null; + } + + private boolean booleanValue(Object value, boolean unknown) { + if (value != null && value.toString() != null) { + try { + if ((Integer)value > 0) + return true; + else + return false; + } catch (java.lang.ClassCastException e) { + try { + String stringValue = (String)value; + if (stringValue.equalsIgnoreCase("true")) + return true; + else + return false; + } catch (java.lang.ClassCastException e1) { + return unknown; + } + } + } + else + return unknown; + } + } diff --git a/src/cx/fbn/nevernote/sql/NoteTable.java b/src/cx/fbn/nevernote/sql/NoteTable.java index fa89e40..945e7e3 100644 --- a/src/cx/fbn/nevernote/sql/NoteTable.java +++ b/src/cx/fbn/nevernote/sql/NoteTable.java @@ -20,379 +20,908 @@ package cx.fbn.nevernote.sql; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.List; import com.evernote.edam.type.Note; +import com.evernote.edam.type.NoteAttributes; +import com.evernote.edam.type.Resource; +import com.evernote.edam.type.Tag; import com.trolltech.qt.core.QByteArray; import com.trolltech.qt.core.QDateTime; +import com.trolltech.qt.core.QTextCodec; import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.NoteRequest; +import cx.fbn.nevernote.evernote.EnmlConverter; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; import cx.fbn.nevernote.utilities.Pair; public class NoteTable { + private final ApplicationLogger logger; + public final NoteTagsTable noteTagsTable; public NoteResourceTable noteResourceTable; - public NoteTagsTable noteTagsTable; - + private final DatabaseConnection db; int id; + + // Prepared Queries to improve speed + private NSqlQuery getQueryWithContent; + private NSqlQuery getQueryWithoutContent; + private NSqlQuery getAllQueryWithoutContent; // Constructor - public NoteTable(int i) { - id = i; - noteTagsTable = new NoteTagsTable(id); - noteResourceTable = new NoteResourceTable(id); + public NoteTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; + id = 0; + noteResourceTable = new NoteResourceTable(logger, db); + noteTagsTable = new NoteTagsTable(logger, db); + getQueryWithContent = null; + getQueryWithoutContent = null; + } // Create the table public void createTable() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Create_Table; - Global.dbRunner.addWork(request); + getQueryWithContent = new NSqlQuery(db.getConnection()); + getQueryWithoutContent = new NSqlQuery(db.getConnection()); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table Note..."); + if (!query.exec("Create table Note (guid varchar primary key, " + + "updateSequenceNumber integer, title varchar, content varchar, contentHash varchar, "+ + "contentLength integer, created timestamp, updated timestamp, deleted timestamp, " + +"active integer, notebookGuid varchar, attributeSubjectDate timestamp, "+ + "attributeLatitude double, attributeLongitude double, attributeAltitude double,"+ + "attributeAuthor varchar, attributeSource varchar, attributeSourceUrl varchar, "+ + "attributeSourceApplication varchar, indexNeeded boolean, isExpunged boolean, " + + "isDirty boolean)")) + logger.log(logger.HIGH, "Table Note creation FAILED!!!"); + if (!query.exec("CREATE INDEX unindexed_notess on note (indexneeded desc, guid);")) + logger.log(logger.HIGH, "Note unindexed_notes index creation FAILED!!!"); + if (!query.exec("CREATE INDEX unsynchronized_notes on note (isDirty desc, guid);")) + logger.log(logger.HIGH, "note unsynchronized_notes index creation FAILED!!!"); + noteTagsTable.createTable(); + noteResourceTable.createTable(); } // Drop the table public void dropTable() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Drop_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table Note"); + noteTagsTable.dropTable(); + noteResourceTable.dropTable(); } // Save Note List from Evernote public void addNote(Note n, boolean isDirty) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.note = n.deepCopy(); - request.bool1 = isDirty; - request.type = NoteRequest.Add_Note; - Global.dbRunner.addWork(request); + logger.log(logger.EXTREME, "Inside addNote"); + if (n == null) + return; + + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Insert Into Note (" + +"guid, updateSequenceNumber, title, content, " + +"contentHash, contentLength, created, updated, deleted, active, notebookGuid, " + +"attributeSubjectDate, attributeLatitude, attributeLongitude, attributeAltitude, " + +"attributeAuthor, attributeSource, attributeSourceUrl, attributeSourceApplication, " + +"indexNeeded, isExpunged, isDirty, titlecolor, thumbnailneeded" + +") Values(" + +":guid, :updateSequenceNumber, :title, :content, " + +":contentHash, :contentLength, :created, :updated, :deleted, :active, :notebookGuid, " + +":attributeSubjectDate, :attributeLatitude, :attributeLongitude, :attributeAltitude, " + +":attributeAuthor, :attributeSource, :attributeSourceUrl, :attributeSourceApplication, " + +":indexNeeded, :isExpunged, :isDirty, -1, true) "); + + StringBuilder created = new StringBuilder(simple.format(n.getCreated())); + StringBuilder updated = new StringBuilder(simple.format(n.getUpdated())); + StringBuilder deleted = new StringBuilder(simple.format(n.getDeleted())); + + EnmlConverter enml = new EnmlConverter(logger); + + query.bindValue(":guid", n.getGuid()); + query.bindValue(":updateSequenceNumber", n.getUpdateSequenceNum()); + query.bindValue(":title", n.getTitle()); + query.bindValue(":content", enml.fixEnXMLCrap(enml.fixEnMediaCrap(n.getContent()))); + query.bindValue(":contentHash", n.getContentHash()); + query.bindValue(":contentLength", n.getContentLength()); + query.bindValue(":created", created.toString()); + query.bindValue(":updated", updated.toString()); + query.bindValue(":deleted", deleted.toString()); + query.bindValue(":active", n.isActive()); + query.bindValue(":notebookGuid", n.getNotebookGuid()); + + if (n.getAttributes() != null) { + created = new StringBuilder(simple.format(n.getAttributes().getSubjectDate())); + query.bindValue(":attributeSubjectDate", created.toString()); + query.bindValue(":attributeLatitude", n.getAttributes().getLatitude()); + query.bindValue(":attributeLongitude", n.getAttributes().getLongitude()); + query.bindValue(":attributeAltitude", n.getAttributes().getAltitude()); + query.bindValue(":attributeAuthor", n.getAttributes().getAuthor()); + query.bindValue(":attributeSource", n.getAttributes().getSource()); + query.bindValue(":attributeSourceUrl", n.getAttributes().getSourceURL()); + query.bindValue(":attributeSourceApplication", n.getAttributes().getSourceApplication()); + } + query.bindValue(":indexNeeded", true); + query.bindValue(":isExpunged", false); + query.bindValue(":isDirty", isDirty); + + + if (!query.exec()) + logger.log(logger.MEDIUM, query.lastError()); + + // Save the note tags + if (n.getTagGuids() != null) { + for (int i=0; i " +query.lastError().toString()); + logger.log(logger.EXTREME, " -> " +query.lastError()); + return null; + } + Note n = mapNoteFromQuery(query, loadContent, loadResources, loadRecognition, loadBinary, loadTags); + n.setContent(fixCarriageReturn(n.getContent())); + return n; + } + // Get a note by Guid + public Note mapNoteFromQuery(NSqlQuery query, boolean loadContent, boolean loadResources, boolean loadRecognition, boolean loadBinary, boolean loadTags) { + DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); +// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + + + Note n = new Note(); + NoteAttributes na = new NoteAttributes(); + n.setAttributes(na); + + n.setGuid(query.valueString(0)); + n.setUpdateSequenceNum(new Integer(query.valueString(1))); + n.setTitle(query.valueString(2)); + + try { + n.setCreated(indfm.parse(query.valueString(3)).getTime()); + n.setUpdated(indfm.parse(query.valueString(4)).getTime()); + n.setDeleted(indfm.parse(query.valueString(5)).getTime()); + } catch (ParseException e) { + e.printStackTrace(); + } + + n.setActive(query.valueBoolean(6,true)); + n.setNotebookGuid(query.valueString(7)); + + try { + String attributeSubjectDate = query.valueString(8); + if (!attributeSubjectDate.equals("")) + na.setSubjectDate(indfm.parse(attributeSubjectDate).getTime()); + } catch (ParseException e) { + e.printStackTrace(); + } + na.setLatitude(new Float(query.valueString(9))); + na.setLongitude(new Float(query.valueString(10))); + na.setAltitude(new Float(query.valueString(11))); + na.setAuthor(query.valueString(12)); + na.setSource(query.valueString(13)); + na.setSourceURL(query.valueString(14)); + na.setSourceApplication(query.valueString(15)); + + if (loadTags) { + n.setTagGuids(noteTagsTable.getNoteTags(n.getGuid())); + List tagNames = new ArrayList(); + TagTable tagTable = new TagTable(logger, db); + for (int i=0; i resources = noteResourceTable.getNoteResourcesRecognition(n.getGuid()); + n.setResources(resources); + } else { + // We need to merge the recognition resources with the note resources retrieved earlier + for (int i=0; i getDirty() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Dirty; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseNotes; + String guid; + Note tempNote; + List notes = new ArrayList(); + List index = new ArrayList(); + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid from Note where isDirty = true and isExpunged = false and notebookGuid not in (select guid from notebook where local = true)"); + if (!check) + logger.log(logger.EXTREME, "Note SQL retrieve has failed: " +query.lastError().toString()); + + // Get a list of the notes + while (query.next()) { + guid = new String(); + guid = query.valueString(0); + index.add(guid); + } + + // Start getting notes + for (int i=0; i getUnsynchronizedGUIDs() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Unsynchronized_Guids; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseStrings; - } public boolean isNoteDirty(String guid) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Is_Note_Dirty; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseBoolean; + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Select guid from Note where isDirty = true and guid=:guid"); + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) + logger.log(logger.EXTREME, "Note SQL retrieve has failed: " +query.lastError().toString()); + + boolean returnValue; + // Get a list of the notes + if (query.next()) + returnValue = true; + else + returnValue = false; + + return returnValue; + } + // Get a list of notes that need to be updated + public List getUnsynchronizedGUIDs() { + String guid; + List index = new ArrayList(); + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid from Note where isDirty = true"); + if (!check) + logger.log(logger.EXTREME, "Note SQL retrieve has failed: " +query.lastError().toString()); + + // Get a list of the notes + while (query.next()) { + guid = new String(); + guid = query.valueString(0); + index.add(guid); + } + return index; } // Reset the dirty bit public void resetDirtyFlag(String guid) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Reset_Dirty_Flag; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Update note set isdirty=false where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Error resetting note dirty field."); } // Get all notes public List getAllGuids() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_All_Guids; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseStrings; + List notes = new ArrayList(); + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid from Note"); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL retrieve has failed: "+query.lastError()); + + // Get a list of the notes + while (query.next()) { + notes.add(new String(query.valueString(0))); + } + return notes; } // Get all notes public List getAllNotes() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_All_Notes; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseNotes; + List notes = new ArrayList(); + prepareQueries(); + boolean check; + NSqlQuery query = getAllQueryWithoutContent; + check = query.exec(); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL retrieve has failed: "+query.lastError()); + // Get a list of the notes + while (query.next()) { + notes.add(mapNoteFromQuery(query, false, false, false, false, true)); + } + return notes; } // Count unindexed notes public int getUnindexedCount() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Unindexed_Count; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseInt; - } - // Count unsynchronized count + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("select count(*) from note where indexneeded=true and isExpunged = false"); + query.next(); + int returnValue = new Integer(query.valueString(0)); + return returnValue; + } + // Count unsynchronized notes public int getDirtyCount() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Dirty_Count; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseInt; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("select count(*) from note where isDirty=true and isExpunged = false"); + query.next(); + int returnValue = new Integer(query.valueString(0)); + return returnValue; } - // Count unindexed notes + // Count notes public int getNoteCount() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Note_Count; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseInt; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("select count(*) from note where isExpunged = false"); + query.next(); + int returnValue = new Integer(query.valueString(0)); + return returnValue; } // Count deleted notes public int getDeletedCount() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Deleted_Count; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseInt; - } - // Reset a note's sequence count to zero. This is useful when moving a conflicting note - public void resetSequenceNumber(String guid) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.string1 = new String(guid); - request.type = NoteRequest.Reset_Note_Sequence; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("select count(*) from note where isExpunged = false and active = false"); + if (!query.next()) + return 0; + int returnValue = new Integer(query.valueString(0)); + return returnValue; + } + // Reset a note sequence number to zero. This is useful for moving conflicting notes + public void resetNoteSequence(String guid) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + boolean check = query.prepare("Update Note set updateSequenceNumber=0, isDirty=true where guid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "Update note ResetSequence sql prepare has failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) { + logger.log(logger.EXTREME, "Update note sequence number has failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } + // Update a note resource by the hash public void updateNoteResourceGuidbyHash(String noteGuid, String resGuid, String hash) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Update_Resource_Guid_By_Hash; - request.string1 = new String(noteGuid); - request.string2 = new String(resGuid); - request.string3 = new String(hash); - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); +/* query.prepare("Select guid from NoteResources where noteGuid=:noteGuid and datahash=:hex"); + query.bindValue(":noteGuid", noteGuid); + query.bindValue(":hex", hash); + query.exec(); + if (!query.next()) { + logger.log(logger.LOW, "Error finding note resource in RNoteTable.updateNoteResourceGuidbyHash. GUID="+noteGuid +" resGuid="+ resGuid+" hash="+hash); + return; + } + String guid = query.valueString(0); +*/ + query.prepare("update noteresources set guid=:guid where noteGuid=:noteGuid and datahash=:hex"); + query.bindValue(":guid", resGuid); + query.bindValue(":noteGuid", noteGuid); + query.bindValue(":hex", hash); + if (!query.exec()) { + logger.log(logger.EXTREME, "Note Resource Update by Hash failed"); + logger.log(logger.EXTREME, query.lastError().toString()); + } } - - // Get the title color of notes - public List> getNoteTitleColors() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Title_Colors; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responsePair; + + // Fix CRLF problem that is on some notes + private String fixCarriageReturn(String note) { + if (note == null || !Global.enableCarriageReturnFix) + return note; + QByteArray a0Hex = new QByteArray("a0"); + String a0 = QByteArray.fromHex(a0Hex).toString(); + note = note.replace("
    "+a0+"
    ", "
     
    "); + return note.replace("
    ", "
     
    "); } - // Get the title color of notes - public void setNoteTitleColor(String guid, int color) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.string1 = new String(guid); - request.int1 = color; - request.type = NoteRequest.Set_Title_Colors; - Global.dbRunner.addWork(request); - } //******************************************************************************** @@ -402,96 +931,201 @@ public class NoteTable { //******************************************************************************** // set/unset a note to be reindexed public void setIndexNeeded(String guid, Boolean flag) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Set_Index_Needed; - request.string1 = new String(guid); - request.bool1 = flag; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Update Note set indexNeeded=:flag where guid=:guid"); + + if (flag) + query.bindValue(":flag", 1); + else + query.bindValue(":flag", 0); + query.bindValue(":guid", guid); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Note indexNeeded update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } // Set all notes to be reindexed public void reindexAllNotes() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Reindex_All_Notes; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("Update Note set indexNeeded=true")) { + logger.log(logger.MEDIUM, "Note reindexAllNotes update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } + // Get all unindexed notes public List getUnindexed() { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Unindexed; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseStrings; + String guid; + List index = new ArrayList(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + + if (!query.exec("Select guid from Note where isExpunged = false and indexNeeded = true and DATEDIFF('MINUTE',updated,CURRENT_TIMESTAMP)>5")) + logger.log(logger.EXTREME, "Note SQL retrieve has failed on getUnindexed()."); + + // Get a list of the notes + while (query.next()) { + guid = new String(); + guid = query.valueString(0); + index.add(guid); + } + return index; } public List getNextUnindexed(int limit) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Next_Unindexed; - request.int1 = limit; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseStrings; + List guids = new ArrayList(); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + if (!query.exec("Select guid from Note where isExpunged = false and indexNeeded = true and DATEDIFF('MINUTE',Updated,CURRENT_TIMESTAMP)>5 limit " +limit)) + logger.log(logger.EXTREME, "Note SQL retrieve has failed on getUnindexed()."); + + // Get a list of the notes + String guid; + while (query.next()) { + guid = new String(); + guid = query.valueString(0); + guids.add(guid); + } + return guids; + } + + + //********************************************************************************** + //* Title color functions + //********************************************************************************** + // Get the title color of all notes + public List> getNoteTitleColors() { + List> returnValue = new ArrayList>(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + + if (!query.exec("Select guid,titleColor from Note where titleColor != -1")) + logger.log(logger.EXTREME, "Note SQL retrieve has failed on getUnindexed()."); + + String guid; + Integer color; + + // Get a list of the notes + while (query.next()) { + Pair pair = new Pair(); + guid = query.valueString(0); + color = query.valueInteger(1); + pair.setFirst(guid); + pair.setSecond(color); + returnValue.add(pair); + } + + + + return returnValue; + } + // Set a title color + // Reset the dirty bit + public void setNoteTitleColor(String guid, int color) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Update note set titlecolor=:color where guid=:guid"); + query.bindValue(":guid", guid); + query.bindValue(":color", color); + if (!query.exec()) + logger.log(logger.EXTREME, "Error updating title color."); } + - //********************************************************************************* - //* Thumbnail Functions - //********************************************************************************* + //********************************************************************************** + //* Thumbnail functions + //********************************************************************************** // Set if a new thumbnail is needed public void setThumbnailNeeded(String guid, boolean needed) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Set_Thumbnail_Needed; - request.string1 = new String(guid); - request.bool1 = needed; - Global.dbRunner.addWork(request); + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Update note set thumbnailneeded = :needed where guid=:guid"); + query.bindValue(":guid", guid); + query.bindValue(":needed", needed); + check = query.exec(); + if (!check) + logger.log(logger.EXTREME, "Note SQL set thumbail needed failed: " +query.lastError().toString()); + } // Is a thumbail needed for this guid? public boolean isThumbnailNeeded(String guid) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Is_Thumbail_Needed; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseBoolean; + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("select thumbnailneeded from note where guid=:guid"); + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) + logger.log(logger.EXTREME, "Note SQL isThumbnailNeeded query failed: " +query.lastError().toString()); + + boolean returnValue; + // Get a list of the notes + if (query.next()) + returnValue = query.valueBoolean(0, false); + else + returnValue = false; + + return returnValue; } // Set if a new thumbnail is needed public void setThumbnail(String guid, QByteArray thumbnail) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Set_Thumbnail; - request.string1 = new String(guid); - request.bytes = thumbnail; - Global.dbRunner.addWork(request); + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Update note set thumbnail = :thumbnail where guid=:guid"); + query.bindValue(":guid", guid); + query.bindValue(":thumbnail", thumbnail.toByteArray()); + check = query.exec(); + if (!check) + logger.log(logger.EXTREME, "Note SQL set thumbail failed: " +query.lastError().toString()); + } // Set if a new thumbnail is needed public QByteArray getThumbnail(String guid) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Get_Thumbnail; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteRequest req = Global.dbRunner.noteResponse.get(id).copy(); - return req.responseBytes; + + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Select thumbnail from note where guid=:guid"); + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) + logger.log(logger.EXTREME, "Note SQL get thumbail failed: " +query.lastError().toString()); + // Get a list of the notes + if (query.next()) + if (query.getBlob(0) != null) + return new QByteArray(query.getBlob(0)); + return null; } - // Update a note content's hash. This happens if a resource is edited outside of NN public void updateResourceContentHash(String guid, String oldHash, String newHash) { - NoteRequest request = new NoteRequest(); - request.requestor_id = id; - request.type = NoteRequest.Update_Resource_Content_Hash; - request.string1 = new String(guid); - request.string2 = new String(oldHash); - request.string3 = new String(newHash); - Global.dbRunner.addWork(request); + Note n = getNote(guid, true, false, false, false,false); + int position = n.getContent().indexOf("-1;) { + endPos = n.getContent().indexOf(">", position+1); + String oldSegment = n.getContent().substring(position,endPos); + int hashPos = oldSegment.indexOf("hash=\""); + int hashEnd = oldSegment.indexOf("\"", hashPos+7); + String hash = oldSegment.substring(hashPos+6, hashEnd); + if (hash.equalsIgnoreCase(oldHash)) { + String newSegment = oldSegment.replace(oldHash, newHash); + String content = n.getContent().substring(0,position) + + newSegment + + n.getContent().substring(endPos); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("update note set isdirty=true, content=:content where guid=:guid"); + query.bindValue(":content", content); + query.bindValue(":guid", n.getGuid()); + query.exec(); + } + + position = n.getContent().indexOf(" getNoteTags(String noteGuid) { - NoteTagsRequest request = new NoteTagsRequest(); - request.requestor_id = id; - request.type = NoteTagsRequest.Get_Note_Tags; - request.string1 = new String(noteGuid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteTagsRequest req = Global.dbRunner.noteTagsResponse.get(id).copy(); - return req.responseStrings; + if (noteGuid == null) + return null; + boolean check; + List tags = new ArrayList(); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.exec("Select " + +"TagGuid from NoteTags where noteGuid = '" +noteGuid +"'"); + if (!check) { + logger.log(logger.EXTREME, "NoteTags SQL select has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + while (query.next()) { + tags.add(query.valueString(0)); + } + return tags; } // Get a note tags by the note's Guid public List getAllNoteTags() { - NoteTagsRequest request = new NoteTagsRequest(); - request.requestor_id = id; - request.type = NoteTagsRequest.Get_All_Note_Tags; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteTagsRequest req = Global.dbRunner.noteTagsResponse.get(id).copy(); - return req.responseNoteTagsRecord; + List tags = new ArrayList(); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("Select TagGuid, NoteGuid from NoteTags")) { + logger.log(logger.EXTREME, "NoteTags SQL select has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + while (query.next()) { + NoteTagsRecord record = new NoteTagsRecord(); + record.tagGuid = query.valueString(0); + record.noteGuid = query.valueString(1); + tags.add(record); + } + return tags; } // Check if a note has a specific tag already public boolean checkNoteNoteTags(String noteGuid, String tagGuid) { - NoteTagsRequest request = new NoteTagsRequest(); - request.requestor_id = id; - request.type = NoteTagsRequest.Check_Note_Note_Tags; - request.string1 = new String(noteGuid); - request.string2 = new String(tagGuid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteTagsRequest req = Global.dbRunner.noteTagsResponse.get(id).copy(); - return req.responseBoolean; + if (noteGuid == null || tagGuid == null) + return false; + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Select " + +"NoteGuid, TagGuid from NoteTags where noteGuid = :noteGuid and tagGuid = :tagGuid"); + if (!check) + logger.log(logger.EXTREME, "checkNoteTags SQL prepare has failed."); + + query.bindValue(":noteGuid", noteGuid); + query.bindValue(":tagGuid", tagGuid); + query.exec(); + + if (!check) { + logger.log(logger.EXTREME, "checkNoteTags SQL select has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return false; + } + + if (query.next()) { + return true; + } + return false; } // Save Note Tags public void saveNoteTag(String noteGuid, String tagGuid) { - NoteTagsRequest request = new NoteTagsRequest(); - request.requestor_id = id; - request.type = NoteTagsRequest.Save_Note_Tag; - request.string1 = new String(noteGuid); - request.string2 = new String(tagGuid); - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Insert Into NoteTags (noteGuid, tagGuid) " + +"Values(" + +":noteGuid, :tagGuid)"); + if (!check) + logger.log(logger.EXTREME, "Note SQL insert prepare has failed."); + + query.bindValue(":noteGuid", noteGuid); + query.bindValue(":tagGuid", tagGuid); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "NoteTags Table insert failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + check = query.prepare("Update Note set isDirty=1 where guid=:guid"); + if (!check) + logger.log(logger.EXTREME, "RNoteTagsTable.saveNoteTag prepare has failed."); + query.bindValue(":guid", noteGuid); + if (!check) { + logger.log(logger.MEDIUM, "RNoteTagsTable.saveNoteTag has failed to set note as dirty."); + logger.log(logger.MEDIUM, query.lastError()); + } } // Delete a note's tags public void deleteNoteTag(String noteGuid) { - NoteTagsRequest request = new NoteTagsRequest(); - request.requestor_id = id; - request.type = NoteTagsRequest.Delete_Note_Tag; - request.string1 = new String(noteGuid); - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Delete from NoteTags where noteGuid = :noteGuid"); + if (!check) + logger.log(logger.EXTREME, "Note SQL delete prepare has failed."); + + query.bindValue(":noteGuid", noteGuid); + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "NoteTags Table delete failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + } - // Get tag counts + // Get a note tag counts public List> getTagCounts() { - NoteTagsRequest request = new NoteTagsRequest(); - request.requestor_id = id; - request.type = NoteTagsRequest.Tag_Counts; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NoteTagsRequest req = Global.dbRunner.noteTagsResponse.get(id).copy(); - return req.responseCounts; - + List> counts = new ArrayList>(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("select tagguid, count(noteguid) from notetags group by tagguid;")) { + logger.log(logger.EXTREME, "NoteTags SQL getTagCounts has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + while (query.next()) { + Pair newCount = new Pair(); + newCount.setFirst(query.valueString(0)); + newCount.setSecond(query.valueInteger(1)); + counts.add(newCount); + } + return counts; } } diff --git a/src/cx/fbn/nevernote/sql/NotebookTable.java b/src/cx/fbn/nevernote/sql/NotebookTable.java index c872d53..70c884e 100644 --- a/src/cx/fbn/nevernote/sql/NotebookTable.java +++ b/src/cx/fbn/nevernote/sql/NotebookTable.java @@ -20,178 +20,410 @@ package cx.fbn.nevernote.sql; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.List; import com.evernote.edam.type.Notebook; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.NotebookRequest; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; import cx.fbn.nevernote.utilities.Pair; public class NotebookTable { - int id; - public NotebookTable(int i) { - id = i; - } + private final ApplicationLogger logger; + DatabaseConnection db; + // Constructor + public NotebookTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; + } // Create the table public void createTable() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Create_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table Notebook..."); + if (!query.exec("Create table Notebook (guid varchar primary key, " + + "sequence integer, name varchar, defaultNotebook varchar, "+ + "serviceCreated timestamp, serviceUpdated timestamp, published boolean, isDirty boolean, "+ + "autoEncrypt boolean, local boolean, archived boolean)")) + logger.log(logger.HIGH, "Table Notebook creation FAILED!!!"); + Notebook newnote = new Notebook(); + newnote.setDefaultNotebook(true); + newnote.setName("My Notebook"); + newnote.setPublished(false); + newnote.setGuid("1"); + addNotebook(newnote, true, false); + } // Drop the table public void dropTable() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Drop_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table Notebook"); } // Save an individual notebook public void addNotebook(Notebook tempNotebook, boolean isDirty, boolean local) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Add_Notebook; - request.notebook = tempNotebook; - request.bool1 = isDirty; - request.bool2 = local; - Global.dbRunner.addWork(request); + boolean check; + + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Insert Into Notebook (guid, sequence, name, defaultNotebook, " + +"serviceCreated, serviceUpdated, published, " + + "isDirty, autoEncrypt," + + "local, archived) Values(" + +":guid, :sequence, :name, :defaultNotebook, " + +":serviceCreated, :serviceUpdated, :published, " + +":isDirty, :autoEncrypt, " + +":local, false)"); + query.bindValue(":guid", tempNotebook.getGuid()); + query.bindValue(":sequence", tempNotebook.getUpdateSequenceNum()); + query.bindValue(":name", tempNotebook.getName()); + query.bindValue(":defaultNotebook", tempNotebook.isDefaultNotebook()); + + StringBuilder serviceCreated = new StringBuilder(simple.format(tempNotebook.getServiceCreated())); + StringBuilder serviceUpdated = new StringBuilder(simple.format(tempNotebook.getServiceUpdated())); + if (serviceUpdated.toString() == null) + serviceUpdated = serviceCreated; + query.bindValue(":serviceCreated", serviceCreated.toString()); + query.bindValue(":serviceUpdated", serviceCreated.toString()); + query.bindValue(":published",tempNotebook.isPublished()); + + if (isDirty) + query.bindValue(":isDirty", true); + else + query.bindValue(":isDirty", false); + query.bindValue(":autoEncrypt", false); + query.bindValue(":local", local); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Notebook Table insert failed."); + logger.log(logger.MEDIUM, query.lastError().toString()); + } } // Delete the notebook based on a guid public void expungeNotebook(String guid, boolean needsSync) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Expunge_Notebook; - request.string1 = guid; - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("delete from Notebook " + +"where guid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "Notebook SQL delete prepare has failed."); + logger.log(logger.EXTREME, query.lastError().toString()); + } + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) + logger.log(logger.MEDIUM, "Notebook delete failed."); + + // Signal the parent that work needs to be done + if (needsSync) { + DeletedTable deletedTable = new DeletedTable(logger, db); + deletedTable.addDeletedItem(guid, "Notebook"); + } } // Update a notebook public void updateNotebook(Notebook tempNotebook, boolean isDirty) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Update_Notebook; - request.notebook = tempNotebook; - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + boolean check; + + SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update Notebook set sequence=:sequence, name=:name, defaultNotebook=:defaultNotebook, " + + "serviceCreated=:serviceCreated, serviceUpdated=:serviceUpdated, "+ + "published=:published, isDirty=:isDirty where guid=:guid "); + query.bindValue(":sequence", tempNotebook.getUpdateSequenceNum()); + query.bindValue(":name", tempNotebook.getName()); + query.bindValue(":defaultNotebook", tempNotebook.isDefaultNotebook()); + + StringBuilder serviceCreated = new StringBuilder(simple.format(tempNotebook.getServiceCreated())); + StringBuilder serviceUpdated = new StringBuilder(simple.format(tempNotebook.getServiceUpdated())); + query.bindValue(":serviceCreated", serviceCreated.toString()); + query.bindValue(":serviceUpdated", serviceUpdated.toString()); + + query.bindValue(":published", tempNotebook.isPublished()); + query.bindValue(":isDirty", isDirty); + query.bindValue(":guid", tempNotebook.getGuid()); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Notebook Table update failed."); + logger.log(logger.MEDIUM, query.lastError().toString()); + } } // Load notebooks from the database public List getAll() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Get_All; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req = Global.dbRunner.notebookResponse.get(id).copy(); - return req.responseNotebooks; - + Notebook tempNotebook; + List index = new ArrayList(); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, sequence, name, defaultNotebook, " + + "serviceCreated, "+ + "serviceUpdated, "+ + "published, defaultNotebook from Notebook order by name"); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); + while (query.next()) { + tempNotebook = new Notebook(); + tempNotebook.setGuid(query.valueString(0)); + int sequence = new Integer(query.valueString(1)).intValue(); + tempNotebook.setUpdateSequenceNum(sequence); + tempNotebook.setName(query.valueString(2)); + DateFormat indfm = null; + try { + indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); +// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + } catch (Exception e) { } + try { + tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); + tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); + } catch (ParseException e) { + e.printStackTrace(); + } + tempNotebook.setPublished(new Boolean(query.valueString(6))); + tempNotebook.setDefaultNotebook(new Boolean(query.valueString(7))); + index.add(tempNotebook); + } + return index; } public List getAllLocal() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Get_All_Local; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req = Global.dbRunner.notebookResponse.get(id).copy(); - return req.responseNotebooks; + Notebook tempNotebook; + List index = new ArrayList(); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, sequence, name, defaultNotebook, " + + "serviceCreated, serviceUpdated, published from Notebook where local=true order by name"); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); + while (query.next()) { + tempNotebook = new Notebook(); + tempNotebook.setGuid(query.valueString(0)); + int sequence = new Integer(query.valueString(1)).intValue(); + tempNotebook.setUpdateSequenceNum(sequence); + tempNotebook.setName(query.valueString(2)); + + DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); +// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + try { + tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); + tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); + } catch (ParseException e) { + e.printStackTrace(); + } + index.add(tempNotebook); + } + return index; } // Archive or un-archive a notebook - public void setArchived(String guid, boolean isDirty) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Set_Archived; - request.string1 = guid; - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + public void setArchived(String guid, boolean val) { + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update notebook set archived=:archived where guid=:guid"); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL archive update has failed."); + query.bindValue(":guid", guid); + query.bindValue(":archived", val); + query.exec(); } // Load non-archived notebooks from the database public List getAllArchived() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Get_All_Archived; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req = Global.dbRunner.notebookResponse.get(id).copy(); - return req.responseNotebooks; + Notebook tempNotebook; + List index = new ArrayList(); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, sequence, name, defaultNotebook, " + + "serviceCreated, serviceUpdated, published from Notebook where archived=true order by name"); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); + while (query.next()) { + tempNotebook = new Notebook(); + tempNotebook.setGuid(query.valueString(0)); + int sequence = new Integer(query.valueString(1)).intValue(); + tempNotebook.setUpdateSequenceNum(sequence); + tempNotebook.setName(query.valueString(2)); + + DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); +// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + try { + tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); + tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); + } catch (ParseException e) { + e.printStackTrace(); + } + tempNotebook.setPublished(new Boolean(query.valueString(6))); + index.add(tempNotebook); + } + return index; } // Check for a local/remote notebook public boolean isNotebookLocal(String guid) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Is_Notebook_Local; - request.string1 = guid; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req =(Global.dbRunner.notebookResponse.get(id)).copy(); - return req.responseBoolean; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Select local from Notebook where guid=:guid"); + query.bindValue(":guid", guid); + query.exec(); + if (!query.next()) { + return false; + } + boolean returnValue = false; + String returnVal = query.valueString(0); + if (returnVal.equals("false")) + returnValue = false; + else + returnValue = true; + return returnValue; } // Update a notebook sequence number public void updateNotebookSequence(String guid, int sequence) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Update_Notebook_Sequence; - request.int1 = sequence; - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update Notebook set sequence=:sequence where guid=:guid"); + query.bindValue(":guid", guid); + query.bindValue(":sequence", sequence); + query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Notebook sequence update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } // Update a notebook GUID number public void updateNotebookGuid(String oldGuid, String newGuid) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Update_Notebook_Guid; - request.string1 = oldGuid; - request.string2 = newGuid; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Update Notebook set guid=:newGuid where guid=:oldGuid"); + query.bindValue(":oldGuid", oldGuid); + query.bindValue(":newGuid", newGuid); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Notebook guid update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + + // Update any notes containing the notebook guid + query.prepare("Update Note set notebookGuid=:newGuid where notebookGuid=:oldGuid"); + query.bindValue(":oldGuid", oldGuid); + query.bindValue(":newGuid", newGuid); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Notebook guid update for note failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + + // Update any watch folders with the new guid + query = new NSqlQuery(db.getConnection()); + query.prepare("Update WatchFolders set notebook=:newGuid where notebook=:oldGuid"); + query.bindValue(":oldGuid", oldGuid); + query.bindValue(":newGuid", newGuid); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Update WatchFolder notebook failed."); + logger.log(logger.MEDIUM, query.lastError().toString()); + } } // Get a list of notes that need to be updated public List getDirty() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Get_Dirty; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req = Global.dbRunner.notebookResponse.get(id).copy(); - return req.responseNotebooks; + Notebook tempNotebook; + List index = new ArrayList(); + boolean check; + + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, sequence, name, defaultNotebook, " + + "serviceCreated, serviceUpdated, published from Notebook where isDirty = true and local=false"); + if (!check) + logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); + while (query.next()) { + tempNotebook = new Notebook(); + tempNotebook.setGuid(query.valueString(0)); + int sequence = new Integer(query.valueString(1)).intValue(); + tempNotebook.setUpdateSequenceNum(sequence); + tempNotebook.setName(query.valueString(2)); + + DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); +// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + try { + tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); + tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); + } catch (ParseException e) { + e.printStackTrace(); + } + tempNotebook.setPublished(new Boolean(query.valueString(6))); + index.add(tempNotebook); + } + return index; } - // This is a convience method to check if a tag exists & update/create based upon it public void syncNotebook(Notebook notebook, boolean isDirty) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Sync_Notebook; - request.notebook = notebook; - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + if (!exists(notebook.getGuid())) { + addNotebook(notebook, isDirty, isDirty); + return; + } + updateNotebook(notebook, isDirty); + } + // does a record exist? + private boolean exists(String guid) { + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Select guid from notebook where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "notebook SQL retrieve has failed."); + boolean retval = query.next(); + return retval; } // Reset the dirty flag. Typically done after a sync. public void resetDirtyFlag(String guid) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Reset_Dirty; - request.string1 = guid; - Global.dbRunner.addWork(request); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Update notebook set isdirty='false' where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Error resetting notebook dirty field."); } + + + + // does a record exist? public String findNotebookByName(String newname) { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Find_Note_By_Name; - request.string1 = newname; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req = Global.dbRunner.notebookResponse.get(id).copy(); - return req.responseString; - } - // Get Notebook counts - public List> getNotebookCounts() { - NotebookRequest request = new NotebookRequest(); - request.requestor_id = id; - request.type = NotebookRequest.Notebook_Counts; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - NotebookRequest req = Global.dbRunner.notebookResponse.get(id).copy(); - return req.responseCounts; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select guid from notebook where name=:newname"); + query.bindValue(":newname", newname); + if (!query.exec()) + logger.log(logger.EXTREME, "notebook SQL retrieve has failed."); + String val = null; + if (query.next()) + val = query.valueString(0); + return val; } + // Get a note tag counts + public List> getNotebookCounts() { + List> counts = new ArrayList>(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("select notebookGuid, count(guid) from note where active=1 group by notebookguid;")) { + logger.log(logger.EXTREME, "NoteTags SQL getTagCounts has failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + while (query.next()) { + Pair newCount = new Pair(); + newCount.setFirst(query.valueString(0)); + newCount.setSecond(query.valueInteger(1)); + counts.add(newCount); + } + return counts; + } + } diff --git a/src/cx/fbn/nevernote/sql/runners/REnSearch.java b/src/cx/fbn/nevernote/sql/REnSearch.java similarity index 95% rename from src/cx/fbn/nevernote/sql/runners/REnSearch.java rename to src/cx/fbn/nevernote/sql/REnSearch.java index 73e2725..b45a3cb 100644 --- a/src/cx/fbn/nevernote/sql/runners/REnSearch.java +++ b/src/cx/fbn/nevernote/sql/REnSearch.java @@ -18,7 +18,7 @@ */ -package cx.fbn.nevernote.sql.runners; +package cx.fbn.nevernote.sql; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -54,14 +54,13 @@ public class REnSearch { private final List todo; private final List tagIndex; private final ApplicationLogger logger; - private final RDatabaseConnection db; +// private final DatabaseConnection db; private boolean any; private int minimumWordLength = 3; private int minimumRecognitionWeight = 80; - private final RDatabaseConnection conn; + private final DatabaseConnection conn; - public REnSearch(RDatabaseConnection c, ApplicationLogger l, RDatabaseConnection d, String s, List t, int m, int r) { - db = d; + public REnSearch(DatabaseConnection c, ApplicationLogger l, String s, List t, int m, int r) { logger = l; conn = c; tagIndex = t; @@ -176,7 +175,7 @@ public class REnSearch { private boolean matchNotebook(String guid) { if (getNotebooks().size() == 0) return true; - RNotebookTable bookTable = new RNotebookTable(logger, conn); + NotebookTable bookTable = new NotebookTable(logger, conn); List books = bookTable.getAll(); String name = new String(""); @@ -635,7 +634,7 @@ public class REnSearch { } } - NSqlQuery query = new NSqlQuery(db.getConnection()); + NSqlQuery query = new NSqlQuery(conn.getConnection()); if (!query.prepare(buffer.toString())) logger.log(logger.HIGH, "EnSearch Sql Prepare Failed:" +query.lastError()); @@ -653,7 +652,7 @@ public class REnSearch { } List guids = new ArrayList(); - RNoteTable noteTable = new RNoteTable(logger, conn); + NoteTable noteTable = new NoteTable(logger, conn); if (!query.exec()) logger.log(logger.EXTREME, "EnSearch.matchWords query failed: " +query.lastError()); List validGuids = new ArrayList(); diff --git a/src/cx/fbn/nevernote/sql/SavedSearchTable.java b/src/cx/fbn/nevernote/sql/SavedSearchTable.java index aaf5d54..fe26133 100644 --- a/src/cx/fbn/nevernote/sql/SavedSearchTable.java +++ b/src/cx/fbn/nevernote/sql/SavedSearchTable.java @@ -20,145 +20,265 @@ package cx.fbn.nevernote.sql; +import java.util.ArrayList; import java.util.List; +import com.evernote.edam.type.QueryFormat; import com.evernote.edam.type.SavedSearch; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.SavedSearchRequest; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; public class SavedSearchTable { - int id; + private final ApplicationLogger logger; + private final DatabaseConnection db; + // Constructor - public SavedSearchTable(int i) { - id = i; + public SavedSearchTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Create_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table SavedSearch..."); + if (!query.exec("Create table SavedSearch (guid varchar primary key, " + + "name varchar, query varchar, format integer, sequence integer, isDirty boolean)")) + logger.log(logger.HIGH, "Table SavedSearch creation FAILED!!!"); } // Drop the table public void dropTable() { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Drop_Table; - Global.dbRunner.addWork(request); - } + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table SavedSearch"); + } // get all tags public List getAll() { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Get_All; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SavedSearchRequest req = Global.dbRunner.savedSearchResponse.get(id).copy(); - return req.responseSavedSearches; + SavedSearch tempSearch; + List index = new ArrayList(); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, name, query, format, sequence" + +" from SavedSearch"); + if (!check) + logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in getAll()."); + while (query.next()) { + tempSearch = new SavedSearch(); + tempSearch.setGuid(query.valueString(0)); + tempSearch.setName(query.valueString(1)); + tempSearch.setQuery(query.valueString(2)); + int fmt = new Integer(query.valueString(3)); + if (fmt == 1) + tempSearch.setFormat(QueryFormat.USER); + else + tempSearch.setFormat(QueryFormat.SEXP); + int sequence = new Integer(query.valueString(4)).intValue(); + tempSearch.setUpdateSequenceNum(sequence); + index.add(tempSearch); + } + return index; } public SavedSearch getSavedSearch(String guid) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Get_Saved_Search; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SavedSearchRequest req = Global.dbRunner.savedSearchResponse.get(id).copy(); - return req.responseSavedSearch; + SavedSearch tempSearch = null; + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("Select guid, name, query, format, sequence" + +" from SavedSearch where guid=:guid"); + if (!check) + logger.log(logger.EXTREME, "SavedSearch SQL prepare has failed in getSavedSearch."); + query.bindValue(":guid", guid); + query.exec(); + if (!check) + logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in getSavedSearch."); + if (query.next()) { + tempSearch = new SavedSearch(); + tempSearch.setGuid(query.valueString(0)); + tempSearch.setName(query.valueString(1)); + tempSearch.setQuery(query.valueString(2)); + int fmt = new Integer(query.valueString(3)); + if (fmt == 1) + tempSearch.setFormat(QueryFormat.USER); + else + tempSearch.setFormat(QueryFormat.SEXP); + int sequence = new Integer(query.valueString(4)).intValue(); + tempSearch.setUpdateSequenceNum(sequence); + } + return tempSearch; } - // Update a saved search + // Update a tag public void updateSavedSearch(SavedSearch search, boolean isDirty) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Update_Saved_Search; - request.savedSearch = search.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update SavedSearch set sequence=:sequence, "+ + "name=:name, isDirty=:isDirty, query=:query, format=:format " + +"where guid=:guid"); + + if (!check) { + logger.log(logger.EXTREME, "SavedSearch SQL update prepare has failed."); + logger.log(logger.EXTREME, query.lastError().toString()); + } + query.bindValue(":sequence", search.getUpdateSequenceNum()); + query.bindValue(":name", search.getName()); + query.bindValue(":isDirty", isDirty); + query.bindValue(":query", search.getQuery()); + if (search.getFormat() == QueryFormat.USER) + query.bindValue(":format", 1); + else + query.bindValue(":format", 2); + + query.bindValue(":guid", search.getGuid()); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Tag Table update failed."); + logger.log(logger.EXTREME, query.lastError().toString()); + } } - // Delete a saved search + // Delete a tag public void expungeSavedSearch(String guid, boolean needsSync) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Expunge_Saved_Search; - request.string1 = new String(guid); - request.bool1 = needsSync; - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("delete from SavedSearch " + +"where guid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "SavedSearch SQL delete prepare has failed."); + logger.log(logger.EXTREME, query.lastError().toString()); + } + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Saved Search delete failed."); + logger.log(logger.EXTREME, query.lastError().toString()); + } + + // Add the work to the parent queue + if (needsSync) { + DeletedTable del = new DeletedTable(logger, db); + del.addDeletedItem(guid, "SavedSearch"); + } } - // Save a saved search + // Save a tag public void addSavedSearch(SavedSearch search, boolean isDirty) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Add_Saved_Search; - request.savedSearch = search.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Insert Into SavedSearch (guid, query, sequence, format, name, isDirty)" + +" Values(:guid, :query, :sequence, :format, :name, :isDirty)"); + if (!check) { + logger.log(logger.EXTREME, "Search SQL insert prepare has failed."); + logger.log(logger.EXTREME, query.lastError().toString()); + } + query.bindValue(":guid", search.getGuid()); + query.bindValue(":query", search.getQuery()); + query.bindValue(":sequence", search.getUpdateSequenceNum()); + if (search.getFormat() == QueryFormat.USER) + query.bindValue(":format", 1); + else + query.bindValue(":format", 2); + query.bindValue(":name", search.getName()); + query.bindValue(":isDirty", isDirty); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Search Table insert failed."); + logger.log(logger.MEDIUM, query.lastError().toString()); + } } - // Update sequence number + // Update a tag sequence number public void updateSavedSearchSequence(String guid, int sequence) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Update_Saved_Search_Sequence; - request.string1 = new String(guid); - request.int1 = sequence; - Global.dbRunner.addWork(request); + boolean check; + ; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update SavedSearch set sequence=:sequence where guid=:guid"); + query.bindValue(":sequence", sequence); + query.bindValue(":guid", guid); + query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "SavedSearch sequence update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } - // Update saved search guid + // Update a tag sequence number public void updateSavedSearchGuid(String oldGuid, String newGuid) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Get_Saved_Search; - request.string1 = new String(oldGuid); - request.string2 = new String(newGuid); - Global.dbRunner.addWork(request); - } - public void resetDirtyFlag(String guid) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Reset_Dirty_Flag; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); + boolean check; + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update SavedSearch set guid=:newGuid where guid=:oldGuid"); + query.bindValue(":newGuid", newGuid); + query.bindValue(":oldGuid", oldGuid); + query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "SavedSearch guid update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } - // Get dirty records + // Get dirty tags public List getDirty() { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Get_Dirty; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SavedSearchRequest req = Global.dbRunner.savedSearchResponse.get(id).copy(); - return req.responseSavedSearches; + SavedSearch search; + List index = new ArrayList(); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, query, sequence, name, format" + +" from SavedSearch where isDirty = true"); + if (!check) + logger.log(logger.EXTREME, "SavedSearch getDirty prepare has failed."); + while (query.next()) { + search = new SavedSearch(); + search.setGuid(query.valueString(0)); + search.setQuery(query.valueString(1)); + int sequence = new Integer(query.valueString(2)).intValue(); + search.setUpdateSequenceNum(sequence); + search.setName(query.valueString(3)); + int fmt = new Integer(query.valueString(4)).intValue(); + if (fmt == 1) + search.setFormat(QueryFormat.USER); + else + search.setFormat(QueryFormat.SEXP); + index.add(search); + } + return index; } // Find a guid based upon the name public String findSavedSearchByName(String name) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Find_Saved_Search_By_Name; - request.string1 = new String(name); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SavedSearchRequest req = Global.dbRunner.savedSearchResponse.get(id).copy(); - return req.string1; + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Select guid from SavedSearch where name=:name"); + query.bindValue(":name", name); + if (!query.exec()) + logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in findSavedSearchByName()."); + String val = null; + if (query.next()) + val = query.valueString(0); + return val; } - // given a guid, does the saved search exist + // given a guid, does the tag exist public boolean exists(String guid) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Exists; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SavedSearchRequest req = Global.dbRunner.savedSearchResponse.get(id).copy(); - return req.bool1; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select guid from SavedSearch where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in exists()."); + boolean retval = query.next(); + return retval; } // This is a convience method to check if a tag exists & update/create based upon it public void syncSavedSearch(SavedSearch search, boolean isDirty) { - SavedSearchRequest request = new SavedSearchRequest(); - request.requestor_id = id; - request.type = SavedSearchRequest.Sync_Saved_Search; - request.savedSearch = search.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + if (exists(search.getGuid())) + updateSavedSearch(search, isDirty); + else + addSavedSearch(search, isDirty); + } + public void resetDirtyFlag(String guid) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Update SavedSearch set isdirty=false where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Error resetting SavedSearch dirty field in resetDirtyFlag()."); } } diff --git a/src/cx/fbn/nevernote/sql/SyncTable.java b/src/cx/fbn/nevernote/sql/SyncTable.java index 79b469a..7812054 100644 --- a/src/cx/fbn/nevernote/sql/SyncTable.java +++ b/src/cx/fbn/nevernote/sql/SyncTable.java @@ -20,74 +20,91 @@ package cx.fbn.nevernote.sql; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.SyncRequest; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; +import cx.fbn.nevernote.utilities.ListManager; public class SyncTable { - int id; + ListManager parent; + private final ApplicationLogger logger; + private final DatabaseConnection db; + // Constructor - public SyncTable(int i) { - id = i; + public SyncTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - SyncRequest request = new SyncRequest(); - request.requestor_id = id; - request.type = SyncRequest.Create_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table Sync..."); + if (!query.exec("Create table Sync (key varchar primary key, value varchar);")) + logger.log(logger.HIGH, "Table Sync creation FAILED!!!"); + addRecord("LastSequenceDate","0"); + addRecord("UpdateSequenceNumber", "0"); } // Drop the table public void dropTable() { - SyncRequest request = new SyncRequest(); - request.requestor_id = id; - request.type = SyncRequest.Drop_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table Sync"); + } + // Add an item to the table + public void addRecord(String key, String value) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Insert Into Sync (key, value) values (:key, :value);"); + query.bindValue(":key", key); + query.bindValue(":value", value); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Add to into Sync failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + } + // Set a key field + public String getRecord(String key) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select value from Sync where key=:key"); + query.bindValue(":key", key); + if (!query.exec()) { + logger.log(logger.MEDIUM, "getRecord from sync failed."); + logger.log(logger.MEDIUM, query.lastError()); + return null; + } + if (query.next()) { + return (query.valueString(0)); + } + return null; + } + // Set a key field + public void setRecord(String key, String value) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Update Sync set value=:value where key=:key"); + query.bindValue(":key", key); + query.bindValue(":value", value); + if (!query.exec()) { + logger.log(logger.MEDIUM, "setRecord from sync failed."); + logger.log(logger.MEDIUM, query.lastError()); + } + return; } + // Set the last sequence date public void setLastSequenceDate(long date) { - SyncRequest request = new SyncRequest(); - request.requestor_id = id; - request.type = SyncRequest.Set_Record; - request.key = "LastSequenceDate"; - request.value = new Long(date).toString(); - Global.dbRunner.addWork(request); + setRecord("LastSequenceDate", new Long(date).toString()); } // Set the last sequence date public void setUpdateSequenceNumber(int number) { - SyncRequest request = new SyncRequest(); - request.requestor_id = id; - request.type = SyncRequest.Set_Record; - request.key = "UpdateSequenceNumber"; - request.value = new Integer(number).toString(); - Global.dbRunner.addWork(request); + setRecord("UpdateSequenceNumber", new Integer(number).toString()); } // get last sequence date public long getLastSequenceDate() { - SyncRequest request = new SyncRequest(); - request.requestor_id = id; - request.type = SyncRequest.Get_Record; - request.key = "LastSequenceDate"; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SyncRequest req = Global.dbRunner.syncResponse.get(id).copy(); - Long date = new Long(req.responseValue); - return date; + return new Long(getRecord("LastSequenceDate")); } // Get invalid attributes for a given element public int getUpdateSequenceNumber() { - SyncRequest request = new SyncRequest(); - request.requestor_id = id; - request.type = SyncRequest.Get_Record; - request.key = "UpdateSequenceNumber"; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - SyncRequest req = Global.dbRunner.syncResponse.get(id).copy(); - Integer number = new Integer(req.responseValue); - return number; + return new Integer(getRecord("UpdateSequenceNumber")); } - - + } diff --git a/src/cx/fbn/nevernote/sql/TagTable.java b/src/cx/fbn/nevernote/sql/TagTable.java index ce2fe57..23d4a19 100644 --- a/src/cx/fbn/nevernote/sql/TagTable.java +++ b/src/cx/fbn/nevernote/sql/TagTable.java @@ -20,165 +20,319 @@ package cx.fbn.nevernote.sql; +import java.util.ArrayList; import java.util.List; import com.evernote.edam.type.Tag; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.DeletedItemRequest; -import cx.fbn.nevernote.sql.requests.TagRequest; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; public class TagTable { - int id; - - public TagTable (int i) { - id = i; + private final ApplicationLogger logger; + DatabaseConnection db; + + public TagTable (ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Create_Table; - Global.dbRunner.addWork(request); - } + + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table Tag..."); + if (!query.exec("Create table Tag (guid varchar primary key, " + + "parentGuid varchar, sequence integer, hashCode integer, name varchar, isDirty boolean)")) + logger.log(logger.HIGH, "Table TAG creation FAILED!!!"); + + } // Drop the table public void dropTable() { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = DeletedItemRequest.Drop_Table; - Global.dbRunner.addWork(request); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table Tag"); + } // get all tags public List getAll() { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = DeletedItemRequest.Get_All; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - TagRequest req = Global.dbRunner.tagResponse.get(id).copy(); - return req.responseTags; + + Tag tempTag; + List index = new ArrayList(); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, parentGuid, sequence, name" + +" from Tag"); + if (!check) { + logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); + logger.log(logger.EXTREME, query.lastError()); + } + while (query.next()) { + tempTag = new Tag(); + tempTag.setGuid(query.valueString(0)); + if (query.valueString(1) != null) + tempTag.setParentGuid(query.valueString(1)); + else + tempTag.setParentGuid(null); + int sequence = new Integer(query.valueString(2)).intValue(); + tempTag.setUpdateSequenceNum(sequence); + tempTag.setName(query.valueString(3)); + index.add(tempTag); + } + + return index; } public Tag getTag(String guid) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.string1 = new String(guid); - request.type = TagRequest.Get_Tag; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - TagRequest req = Global.dbRunner.tagResponse.get(id).copy(); - return req.responseTag; + Tag tempTag = new Tag(); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + if (!query.prepare("Select guid, parentGuid, sequence, name" + +" from Tag where guid=:guid")) + logger.log(logger.EXTREME, "Tag select by guid SQL prepare has failed."); + + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Tag select by guid SQL exec has failed."); + + if (!query.next()) { + return tempTag; + } + tempTag.setGuid(query.valueString(0)); + tempTag.setParentGuid(query.valueString(1)); + int sequence = new Integer(query.valueString(2)).intValue(); + tempTag.setUpdateSequenceNum(sequence); + tempTag.setName(query.valueString(3)); + return tempTag; } // Update a tag public void updateTag(Tag tempTag, boolean isDirty) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Update_Tag; - request.tag = tempTag.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); - + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update Tag set parentGuid=:parentGuid, sequence=:sequence, "+ + "hashCode=:hashCode, name=:name, isDirty=:isDirty " + +"where guid=:guid"); + + if (!check) { + logger.log(logger.EXTREME, "Tag SQL update prepare has failed."); + logger.log(logger.EXTREME, query.lastError()); + } + query.bindValue(":parentGuid", tempTag.getParentGuid()); + query.bindValue(":sequence", tempTag.getUpdateSequenceNum()); + query.bindValue(":hashCode", tempTag.hashCode()); + query.bindValue(":name", tempTag.getName()); + query.bindValue(":isDirty", isDirty); + query.bindValue(":guid", tempTag.getGuid()); + + check = query.exec(); + if (!check) + logger.log(logger.MEDIUM, "Tag Table update failed."); + } // Delete a tag public void expungeTag(String guid, boolean needsSync) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.string1 = new String(guid); - request.bool1 = needsSync; - request.type = TagRequest.Expunge_Tag; - Global.dbRunner.addWork(request); + boolean check; + + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.prepare("delete from Tag " + +"where guid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "Tag SQL delete prepare has failed."); + logger.log(logger.EXTREME, query.lastError()); + } + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) + logger.log(logger.MEDIUM, "Tag delete failed."); + + check = query.prepare("delete from NoteTags " + +"where tagGuid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "NoteTags SQL delete prepare has failed."); + logger.log(logger.EXTREME, query.lastError()); + } + + query.bindValue(":guid", guid); + check = query.exec(); + if (!check) + logger.log(logger.MEDIUM, "NoteTags delete failed."); + + // Add the work to the parent queue + if (needsSync) { + DeletedTable del = new DeletedTable(logger, db); + del.addDeletedItem(guid, "Tag"); + } } // Save a tag public void addTag(Tag tempTag, boolean isDirty) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Add_Tag; - request.tag = tempTag.deepCopy(); - request.bool1 = isDirty; - Global.dbRunner.addWork(request); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Insert Into Tag (guid, parentGuid, sequence, hashCode, name, isDirty)" + +" Values(:guid, :parentGuid, :sequence, :hashCode, :name, :isDirty)"); + if (!check) { + logger.log(logger.EXTREME, "Tag SQL insert prepare has failed."); + logger.log(logger.EXTREME, query.lastError()); + } + query.bindValue(":guid", tempTag.getGuid()); + query.bindValue(":parentGuid", tempTag.getParentGuid()); + query.bindValue(":sequence", tempTag.getUpdateSequenceNum()); + query.bindValue(":hashCode", tempTag.hashCode()); + query.bindValue(":name", tempTag.getName()); + query.bindValue(":isDirty", isDirty); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Tag Table insert failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } // Update a tag's parent public void updateTagParent(String guid, String parentGuid) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Update_Parent; - request.string1 = new String(guid); - request.string2 = new String(parentGuid); - Global.dbRunner.addWork(request); + boolean check; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + check = query.prepare("Update Tag set parentGuid=:parentGuid where guid=:guid"); + if (!check) { + logger.log(logger.EXTREME, "Tag SQL tag parent update prepare has failed."); + logger.log(logger.EXTREME, query.lastError()); + } + + query.bindValue(":parentGuid", parentGuid); + query.bindValue(":guid", guid); + + check = query.exec(); + if (!check) { + logger.log(logger.MEDIUM, "Tag parent update failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } //Save tags from Evernote public void saveTags(List tags) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Save_Tags; - for (int i=0; i getDirty() { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Get_Dirty; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - TagRequest req = Global.dbRunner.tagResponse.get(id).copy(); - return req.responseTags; + Tag tempTag; + List index = new ArrayList(); + boolean check; + + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + check = query.exec("Select guid, parentGuid, sequence, name" + +" from Tag where isDirty = true"); + if (!check) + logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); + while (query.next()) { + tempTag = new Tag(); + tempTag.setGuid(query.valueString(0)); + tempTag.setParentGuid(query.valueString(1)); + int sequence = new Integer(query.valueString(2)).intValue(); + tempTag.setUpdateSequenceNum(sequence); + tempTag.setName(query.valueString(3)); + if (tempTag.getParentGuid() != null && tempTag.getParentGuid().equals("")) + tempTag.setParentGuid(null); + index.add(tempTag); + } + return index; } // Find a guid based upon the name public String findTagByName(String name) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Find_Tag_By_Name; - request.string1 = new String(name); - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - TagRequest req = Global.dbRunner.tagResponse.get(id).copy(); - return req.responseString; + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Select guid from tag where name=:name"); + query.bindValue(":name", name); + if (!query.exec()) + logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); + String val = null; + if (query.next()) + val = query.valueString(0); + return val; } // given a guid, does the tag exist public boolean exists(String guid) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.string1 = new String(guid); - request.type = TagRequest.Exists; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - TagRequest req = Global.dbRunner.tagResponse.get(id).copy(); - return req.responseBool; - + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Select guid from tag where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); + boolean retval = query.next(); + return retval; } // This is a convience method to check if a tag exists & update/create based upon it public void syncTag(Tag tag, boolean isDirty) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.tag = tag.deepCopy(); - request.bool1 = isDirty; - request.type = TagRequest.Sync_Tag; - Global.dbRunner.addWork(request); + if (exists(tag.getGuid())) + updateTag(tag, isDirty); + else + addTag(tag, isDirty); } public void resetDirtyFlag(String guid) { - TagRequest request = new TagRequest(); - request.requestor_id = id; - request.type = TagRequest.Reset_Dirty_Flag; - request.string1 = new String(guid); - Global.dbRunner.addWork(request); + + NSqlQuery query = new NSqlQuery(db.getConnection()); + + query.prepare("Update tag set isdirty=false where guid=:guid"); + query.bindValue(":guid", guid); + if (!query.exec()) + logger.log(logger.EXTREME, "Error resetting tag dirty field."); } } diff --git a/src/cx/fbn/nevernote/sql/runners/WatchFolderRecord.java b/src/cx/fbn/nevernote/sql/WatchFolderRecord.java similarity index 92% rename from src/cx/fbn/nevernote/sql/runners/WatchFolderRecord.java rename to src/cx/fbn/nevernote/sql/WatchFolderRecord.java index 144b3cc..0dfa47f 100644 --- a/src/cx/fbn/nevernote/sql/runners/WatchFolderRecord.java +++ b/src/cx/fbn/nevernote/sql/WatchFolderRecord.java @@ -17,7 +17,7 @@ * */ -package cx.fbn.nevernote.sql.runners; +package cx.fbn.nevernote.sql; public class WatchFolderRecord { public String folder; diff --git a/src/cx/fbn/nevernote/sql/WatchFolderTable.java b/src/cx/fbn/nevernote/sql/WatchFolderTable.java index d802b6f..32caa0a 100644 --- a/src/cx/fbn/nevernote/sql/WatchFolderTable.java +++ b/src/cx/fbn/nevernote/sql/WatchFolderTable.java @@ -20,77 +20,98 @@ package cx.fbn.nevernote.sql; +import java.util.ArrayList; import java.util.List; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.WatchFolderRequest; -import cx.fbn.nevernote.sql.runners.WatchFolderRecord; +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; import cx.fbn.nevernote.utilities.ListManager; public class WatchFolderTable { ListManager parent; - int id; + private final ApplicationLogger logger; + private final DatabaseConnection db; + // Constructor - public WatchFolderTable(int i) { - id = i; + public WatchFolderTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.type = WatchFolderRequest.Create_Tables; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table WatchFolder..."); + if (!query.exec("Create table WatchFolders (folder varchar primary key, notebook varchar," + + "keep boolean, depth integer)")); + logger.log(logger.HIGH, "Table WatchFolders creation FAILED!!!"); } // Drop the table public void dropTable() { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.type = WatchFolderRequest.Drop_Tables; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Drop table WatchFolders"); } - // Add an item to the deleted table + // Add an folder public void addWatchFolder(String folder, String notebook, boolean keep, int depth) { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.string1 = folder; - request.string2 = notebook; - request.bool1 = keep; - request.int1 = depth; - request.type = WatchFolderRequest.Add_Watch_Folder; - Global.dbRunner.addWork(request); - } - public List getAll() { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.type = WatchFolderRequest.Get_All; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - WatchFolderRequest req = Global.dbRunner.watchFolderResponse.get(id).copy(); - return req.responseWatchFolders; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Insert Into WatchFolders (folder, notebook, keep, depth) " + + "values (:folder, :notebook, :keep, :depth)"); + query.bindValue(":folder", folder); + query.bindValue(":notebook", notebook); + query.bindValue(":keep", keep); + query.bindValue(":depth", depth); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Insert into WatchFolder failed."); + } } - public void expungeFolder(String folder) { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.string1 = folder; - request.type = WatchFolderRequest.Expunge_Folder; - Global.dbRunner.addWork(request); + // remove an folder + public void expungeWatchFolder(String folder) { + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("delete from WatchFolders where folder=:folder"); + query.bindValue(":folder", folder); + if (!query.exec()) { + logger.log(logger.MEDIUM, "Expunge WatchFolder failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } public void expungeAll() { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.type = WatchFolderRequest.Expunge_All; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + if (!query.exec("delete from WatchFolders")) { + logger.log(logger.MEDIUM, "Expunge all WatchFolder failed."); + logger.log(logger.MEDIUM, query.lastError()); + } } - public String getNotebook(String dir) { - WatchFolderRequest request = new WatchFolderRequest(); - request.requestor_id = id; - request.type = WatchFolderRequest.Get_Notebook; - request.string1 = dir; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - WatchFolderRequest req = Global.dbRunner.watchFolderResponse.get(id).copy(); - return req.responseString; + public List getAll() { + logger.log(logger.HIGH, "Entering RWatchFolders.getAll"); + + List list = new ArrayList(); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("Select folder, (select name from notebook where guid = notebook), keep, depth from WatchFolders"); + while (query.next()) { + WatchFolderRecord record = new WatchFolderRecord(); + record.folder = query.valueString(0); + record.notebook = query.valueString(1); + record.keep = new Boolean(query.valueString(2)); + record.depth = new Integer(query.valueString(3)); + list.add(record); + } + logger.log(logger.HIGH, "Leaving RWatchFolders.getAll"); + return list; + } + + public String getNotebook(String dir) { + logger.log(logger.HIGH, "Entering RWatchFolders.getNotebook"); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.prepare("Select notebook from WatchFolders where folder=:dir"); + query.bindValue(":dir", dir); + query.exec(); + String response = null; + while (query.next()) { + response = query.valueString(0); + } + logger.log(logger.HIGH, "Leaving RWatchFolders.getNotebook"); + return response; + } } diff --git a/src/cx/fbn/nevernote/sql/WordsTable.java b/src/cx/fbn/nevernote/sql/WordsTable.java index c7ca076..28f7b7b 100644 --- a/src/cx/fbn/nevernote/sql/WordsTable.java +++ b/src/cx/fbn/nevernote/sql/WordsTable.java @@ -20,48 +20,50 @@ package cx.fbn.nevernote.sql; -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.sql.requests.WordRequest; - +import cx.fbn.nevernote.sql.driver.NSqlQuery; +import cx.fbn.nevernote.utilities.ApplicationLogger; public class WordsTable { - private final int id; + private final ApplicationLogger logger; + private final DatabaseConnection db; + // Constructor - public WordsTable(int i) { - id = i; + public WordsTable(ApplicationLogger l, DatabaseConnection d) { + logger = l; + db = d; } // Create the table public void createTable() { - WordRequest request = new WordRequest(); - request.requestor_id = id; - request.type = WordRequest.Create_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "Creating table WORDS ..."); + if (!query.exec("create table words (word varchar, guid varchar, source varchar, weight int, primary key (word, guid, source));")) { + logger.log(logger.HIGH, "Table WORDS creation FAILED!!!"); + logger.log(logger.HIGH, query.lastError()); + } } // Drop the table public void dropTable() { - WordRequest request = new WordRequest(); - request.requestor_id = id; - request.type = WordRequest.Drop_Table; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("drop table words"); } // Count unindexed notes public int getWordCount() { - WordRequest request = new WordRequest(); - request.requestor_id = id; - request.type = WordRequest.Get_Word_Count; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); - WordRequest req = Global.dbRunner.wordResponse.get(id).copy(); - return req.responseInt; + NSqlQuery query = new NSqlQuery(db.getConnection()); + query.exec("select count(*) from words"); + query.next(); + int returnValue = new Integer(query.valueString(0)); + return returnValue; } // Clear out the word index table public void clearWordIndex() { - WordRequest request = new WordRequest(); - request.requestor_id = id; - request.type = WordRequest.Clear_Word_Index; - Global.dbRunner.addWork(request); + NSqlQuery query = new NSqlQuery(db.getConnection()); + logger.log(logger.HIGH, "DELETE FROM WORDS"); + + boolean check = query.exec("DELETE FROM WORDS"); + if (!check) + logger.log(logger.HIGH, "Table WORDS clear has FAILED!!!"); } //******************************************************************************** @@ -70,24 +72,65 @@ public class WordsTable { //******************************************************************************** //******************************************************************************** public void expungeFromWordIndex(String guid, String type) { - WordRequest request = new WordRequest(); - request.requestor_id = id; - request.type = WordRequest.Expunge_From_Word_Index; - request.string1 = guid; - request.string2 = type; - Global.dbRunner.addWork(request); + NSqlQuery deleteWords = new NSqlQuery(db.getConnection()); + if (!deleteWords.prepare("delete from words where guid=:guid and source=:source")) { + logger.log(logger.EXTREME, "Note SQL select prepare deleteWords has failed."); + logger.log(logger.MEDIUM, deleteWords.lastError()); + } + + deleteWords.bindValue(":guid", guid); + deleteWords.bindValue(":source", type); + deleteWords.exec(); + } // Reindex a note public synchronized void addWordToNoteIndex(String guid, String word, String type, Integer weight) { - WordRequest request = new WordRequest(); - request.requestor_id = id; - request.type = WordRequest.Add_Word_To_Note_Index; - request.string1 = guid; - request.string2 = word; - request.string3 = type; - request.int1 = weight; - Global.dbRunner.addWork(request); - Global.dbClientWait(id); + NSqlQuery findWords = new NSqlQuery(db.getConnection()); + if (!findWords.prepare("Select weight from words where guid=:guid and source=:type and word=:word")) { + logger.log(logger.MEDIUM, "Prepare failed in addWordToNoteIndex()"); + logger.log(logger.MEDIUM, findWords.lastError()); + } + + findWords.bindValue(":guid", guid); + findWords.bindValue(":type", type); + findWords.bindValue(":word", word); + + boolean addNeeded = true; + findWords.exec(); + // If we have a match, find out which has the heigher weight & update accordingly + if (findWords.next()) { + int recordWeight = new Integer(findWords.valueString(0)); + addNeeded = false; + if (recordWeight < weight) { + NSqlQuery updateWord = new NSqlQuery(db.getConnection()); + if (!updateWord.prepare("Update words set weight=:weight where guid=:guid and source=:type and word=:word")) { + logger.log(logger.MEDIUM, "Prepare failed for find words in addWordToNoteIndex()"); + logger.log(logger.MEDIUM, findWords.lastError()); + } + + updateWord.bindValue(":weight", weight); + updateWord.bindValue(":guid", guid); + updateWord.bindValue(":type", type); + updateWord.bindValue(":word",word); + updateWord.exec(); + } + } + + + if (!addNeeded) + return; + + NSqlQuery insertWords = new NSqlQuery(db.getConnection()); + if (!insertWords.prepare("Insert Into Words (word, guid, weight, source)" + +" Values(:word, :guid, :weight, :type )")) { + logger.log(logger.EXTREME, "Note SQL select prepare checkWords has failed."); + logger.log(logger.MEDIUM, insertWords.lastError()); + } + insertWords.bindValue(":word", word); + insertWords.bindValue(":guid", guid); + insertWords.bindValue(":weight", weight); + insertWords.bindValue(":type", type); + insertWords.exec(); } diff --git a/src/cx/fbn/nevernote/sql/requests/DBRunnerRequest.java b/src/cx/fbn/nevernote/sql/requests/DBRunnerRequest.java deleted file mode 100644 index febf81b..0000000 --- a/src/cx/fbn/nevernote/sql/requests/DBRunnerRequest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.requests; - - -public class DBRunnerRequest { - public static int GENERIC = 1; - public static int DATABASE = 2; - public static int DELETED_ITEM = 3; - public static int NOTEBOOK = 4; - public static int TAG = 5; - public static int SAVED_SEARCH = 6; - public static int NOTE = 7; - public static int RESOURCE = 8; - public static int NOTE_TAGS = 9; - public static int ENSEARCH = 10; - public static int WATCH_FOLDER = 11; - public static int WORD = 12; - public static int Invalid_XML = 13; - public static int Sync = 14; - - public volatile int requestor_id; - public volatile int category; - public volatile int type; - public volatile String request; -} diff --git a/src/cx/fbn/nevernote/sql/requests/DatabaseRequest.java b/src/cx/fbn/nevernote/sql/requests/DatabaseRequest.java deleted file mode 100644 index 826de36..0000000 --- a/src/cx/fbn/nevernote/sql/requests/DatabaseRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - -package cx.fbn.nevernote.sql.requests; - - -public class DatabaseRequest extends DBRunnerRequest { - // NFC TODO: change to use an Enum and clarify distinction with constants on DBRunnerRequest - public static int Create_Tables = 1; - public static int Drop_Tables = 2; - public static int Shutdown = 4; - public static int Compact = 5; - public static int Execute_Sql = 6; - public static int Execute_Sql_Index = 7; - public static int Backup_Database = 8; - - public volatile String string1; - public volatile String string2; - public volatile int int1; - public volatile long long1; - - public DatabaseRequest() { - category = DATABASE; - } - - public DatabaseRequest copy() { - DatabaseRequest request = new DatabaseRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - request.long1 = long1; - request.int1 = int1; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - - return request; - } - -} \ No newline at end of file diff --git a/src/cx/fbn/nevernote/sql/requests/DeletedItemRequest.java b/src/cx/fbn/nevernote/sql/requests/DeletedItemRequest.java deleted file mode 100644 index 2335070..0000000 --- a/src/cx/fbn/nevernote/sql/requests/DeletedItemRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.requests; - -import java.util.ArrayList; -import java.util.List; - -import cx.fbn.nevernote.sql.runners.DeletedItemRecord; - -public class DeletedItemRequest extends DBRunnerRequest { - public static int Create_Table = 1; - public static int Drop_Table = 2; - public static int Add_Deleted_Item = 3; - public static int Expunge_All = 4; - public static int Get_All = 5; - public static int Expunge_Record = 6; - - public volatile String string1; - public volatile String string2; - - public List responseDeletedRecords; - - public DeletedItemRequest() { - category = DELETED_ITEM; - } - - public DeletedItemRequest copy() { - DeletedItemRequest request = new DeletedItemRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - - if (responseDeletedRecords != null) { - request.responseDeletedRecords = new ArrayList(); - for (int i=0; i tags; - public volatile int int1; - public volatile int int2; - public volatile List responseNotes; - public volatile List responseStrings; - - - public EnSearchRequest() { - category = ENSEARCH; - } - - public EnSearchRequest copy() { - EnSearchRequest request = new EnSearchRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.int1 = int1; - request.int2 = int2; - request.category = category; - if (string1 != null) - request.string1 = new String(string1); - if (tags!=null) { - request.tags = new ArrayList(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i responseList; - public ArrayList responseArrayList; - - public InvalidXMLRequest() { - category = Invalid_XML; - } - - public InvalidXMLRequest copy() { - InvalidXMLRequest req = new InvalidXMLRequest(); - req.type = type; - req.category = category; - req.requestor_id = requestor_id; - - if (string1 != null) - req.string1 = new String(string1); - if (string2 != null) - req.string2 = new String(string2); - if (string3 != null) - req.string3 = new String(string3); - if (responseString1 != null) - req.responseString1 = new String(responseString1); - - if (responseList != null) { - req.responseList = new ArrayList(); - for (int i=0; i(); - for (int i=0; i notes; - public volatile QByteArray bytes; - - public volatile List responseNotes; - public volatile Note responseNote; - public volatile String responseString; - public volatile boolean responseBoolean; - public volatile List responseStrings; - public volatile int responseInt; - public volatile List> responsePair; - public volatile QByteArray responseBytes; - - public NoteRequest() { - category = NOTE; - } - - public NoteRequest copy() { - NoteRequest request = new NoteRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - request.bool1 = bool1; - request.bool2 = bool2; - request.bool3 = bool3; - request.bool4 = bool4; - request.bool5 = bool5; - request.int1 = int1; - - if (date != null) - request.date = new QDateTime(date); - - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - if (string3 != null) - request.string3 = new String(string3); - - if (note != null) - request.note = note.deepCopy(); - - if (notes != null) { - request.notes = new ArrayList(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i>(); - for (int i=0; i p = new Pair(); - p.setFirst(responsePair.get(i).getFirst()); - p.setSecond(responsePair.get(i).getSecond()); - request.responsePair.add(p); - } - } - if (responseBytes != null) - request.responseBytes = new QByteArray(responseBytes); - - return request; - } - -} \ No newline at end of file diff --git a/src/cx/fbn/nevernote/sql/requests/NoteTagsRequest.java b/src/cx/fbn/nevernote/sql/requests/NoteTagsRequest.java deleted file mode 100644 index 064b0e0..0000000 --- a/src/cx/fbn/nevernote/sql/requests/NoteTagsRequest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.requests; - -import java.util.ArrayList; -import java.util.List; - -import cx.fbn.nevernote.sql.runners.NoteTagsRecord; -import cx.fbn.nevernote.utilities.Pair; - -public class NoteTagsRequest extends DBRunnerRequest { - public static int Create_Table = 1; - public static int Drop_Table = 2; - public static int Get_Note_Tags = 3; - public static int Get_All_Note_Tags = 4; - public static int Check_Note_Note_Tags = 5; - public static int Save_Note_Tag = 6; - public static int Delete_Note_Tag = 7; - public static int Tag_Counts = 8; - - - public volatile String string1; - public volatile String string2; - - public volatile List responseStrings; - public volatile List responseNoteTagsRecord; - public volatile boolean responseBoolean; - public volatile List> responseCounts; - - public NoteTagsRequest() { - category = NOTE_TAGS; - } - - public NoteTagsRequest copy() { - NoteTagsRequest request = new NoteTagsRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - - if (responseStrings != null) { - request.responseStrings = new ArrayList(); - for (int i=0; i(); - for (int i=0; i>(); - for (int i=0; i newPair = new Pair(); - newPair.setFirst(responseCounts.get(i).getFirst()); - newPair.setSecond(responseCounts.get(i).getSecond()); - request.responseCounts.add(newPair); - } - } - - return request; - } - -} \ No newline at end of file diff --git a/src/cx/fbn/nevernote/sql/requests/NotebookRequest.java b/src/cx/fbn/nevernote/sql/requests/NotebookRequest.java deleted file mode 100644 index f9d850d..0000000 --- a/src/cx/fbn/nevernote/sql/requests/NotebookRequest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - -package cx.fbn.nevernote.sql.requests; - -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.Notebook; - -import cx.fbn.nevernote.utilities.Pair; - -public class NotebookRequest extends DBRunnerRequest { - public static int Create_Table = 1; - public static int Drop_Table = 2; -// public static int Get_Notebook = 3; -// public static int Get_All_Notebooks = 4; - public static int Add_Notebook = 5; - public static int Expunge_Notebook = 6; - public static int Find_Note_By_Name = 7; - public static int Get_All = 8; - public static int Get_Dirty = 9; - public static int Is_Notebook_Local = 10; - public static int Reset_Dirty = 11; -// public static int Save_Notebooks = 12; - public static int Set_Archived = 13; - public static int Sync_Notebook = 14; - public static int Update_Notebook = 15; - public static int Update_Notebook_Guid = 16; - public static int Update_Notebook_Sequence = 17; - public static int Get_All_Local = 18; - public static int Get_All_Archived = 19; - public static int Notebook_Counts = 20; - - public volatile boolean bool1; - public volatile boolean bool2; - public volatile String string1; - public volatile String string2; - public volatile Notebook notebook; - public volatile int int1; - - public volatile List responseNotebooks; - public volatile boolean responseBoolean; - public volatile String responseString; - public volatile List> responseCounts; - - public NotebookRequest() { - category = NOTEBOOK; - } - - public NotebookRequest copy() { - NotebookRequest request = new NotebookRequest(); - - request.category = category; - request.requestor_id = requestor_id; - request.type = type; - request.bool1 = bool1; - request.bool2 = bool2; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - if (notebook != null) - request.notebook = notebook.deepCopy(); - if (responseNotebooks != null) { - request.responseNotebooks = new ArrayList(); - for (int i=0; i>(); - for (int i=0; i newPair = new Pair(); - newPair.setFirst(responseCounts.get(i).getFirst()); - newPair.setSecond(responseCounts.get(i).getSecond()); - request.responseCounts.add(newPair); - } - } - - - return request; - } - -} \ No newline at end of file diff --git a/src/cx/fbn/nevernote/sql/requests/ResourceRequest.java b/src/cx/fbn/nevernote/sql/requests/ResourceRequest.java deleted file mode 100644 index 3e84bba..0000000 --- a/src/cx/fbn/nevernote/sql/requests/ResourceRequest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - -package cx.fbn.nevernote.sql.requests; - -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.Resource; - -public class ResourceRequest extends DBRunnerRequest { - public static int Create_Table = 1; - public static int Drop_Table = 2; - public static int Reset_Dirty_Flag = 3; - public static int Get_Next_Unindexed = 4; - public static int Set_Index_Needed = 5; - public static int Save_Note_Resource = 6; - public static int Expunge_Note_Resource = 7; - public static int Get_Note_Resource_Guid_By_Hash_Hex = 8; - public static int Get_Note_Resource_Data_Body_By_Hash_Hex = 9; - public static int Get_Note_Resource = 10; - public static int Get_Note_Resources = 11; - public static int Get_Note_Resources_Recognition = 12; - public static int Get_Note_Resource_Recognition = 13; - public static int Update_Note_Resource = 14; - public static int Reindex_All = 15; - public static int Get_Resource_Count = 16; - public static int Update_Note_Resource_Guid = 17; - public static int Reset_Update_Sequence_Number = 18; - - - - public volatile String string1; - public volatile String string2; - public volatile Resource resource; - public volatile boolean bool1; - public volatile int int1; - - public volatile List responseStrings; - public volatile String responseString; - public volatile List responseResources; - public volatile Resource responseResource; - public volatile int responseInteger; - - public ResourceRequest() { - category = RESOURCE; - } - - public ResourceRequest copy() { - ResourceRequest request = new ResourceRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - if (resource != null) - request.resource = resource.deepCopy(); - request.bool1 = bool1; - request.int1 = int1; - - if (responseStrings != null) { - request.responseStrings = new ArrayList(); - for (int i=0; i(); - for (int i=0; i savedSearches; - - public volatile List responseSavedSearches; - public volatile SavedSearch responseSavedSearch; - public volatile String responseString; - public volatile boolean responseBoolean; - - public SavedSearchRequest() { - category = SAVED_SEARCH; - } - - public SavedSearchRequest copy() { - SavedSearchRequest request = new SavedSearchRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - request.bool1 = bool1; - - if (responseString != null) - request.responseString = new String(responseString); - - if (savedSearch != null) - request.savedSearch = savedSearch.deepCopy(); - if (savedSearches != null) - for (int i=0; i(); - for (int i=0; i tags; - - public volatile List responseTags; - public volatile Tag responseTag; - public volatile String responseString; - public volatile boolean responseBool; - - public TagRequest() { - category = TAG; - } - - public TagRequest copy() { - TagRequest request = new TagRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - request.int1 = int1; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - if (tag != null) - request.tag = tag; - request.bool1 = bool1; - if (tags != null && tags.size() > 0) { - for (int i=0; i(); - for (int i =0; i responseWatchFolders; - public String responseString; - - public WatchFolderRequest() { - category = WATCH_FOLDER; - } - - public WatchFolderRequest copy() { - WatchFolderRequest request = new WatchFolderRequest(); - - request.requestor_id = requestor_id; - request.type = type; - request.category = category; - if (string1 != null) - request.string1 = new String(string1); - if (string2 != null) - request.string2 = new String(string2); - if (int1 != null) - request.int1 = new Integer(int1); - if (bool1 != null) - request.bool1 = new Boolean(bool1); - - if (responseWatchFolders != null) { - request.responseWatchFolders = new ArrayList(); - for (int i=0; i getAllDeleted() { - logger.log(logger.HIGH, "Entering DeletedTable.getAllDeleted"); - List list = new ArrayList(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Select guid, type from DeletedItems"); - while (query.next()) { - DeletedItemRecord record = new DeletedItemRecord(); - record.guid = query.valueString(0); - record.type = query.valueString(1); - list.add(record); - } - logger.log(logger.HIGH, "Leaving DeletedTable.getAllDeleted"); - return list; - - } - public void expungeAllDeletedRecords() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("delete from DeletedItems"); - } - -} diff --git a/src/cx/fbn/nevernote/sql/runners/RInvalidXMLTable.java b/src/cx/fbn/nevernote/sql/runners/RInvalidXMLTable.java deleted file mode 100644 index b1fe1fd..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RInvalidXMLTable.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import java.util.ArrayList; -import java.util.List; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; -import cx.fbn.nevernote.utilities.ListManager; - -public class RInvalidXMLTable { - ListManager parent; - private final ApplicationLogger logger; - private final RDatabaseConnection db; - - - // Constructor - public RInvalidXMLTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); -// query.exec("drop table InvalidXML"); - logger.log(logger.HIGH, "Creating table InvalidXML..."); - if (!query.exec("Create table InvalidXML (type varchar, element varchar, attribute varchar,primary key(type, element,attribute) );")) - logger.log(logger.HIGH, "Table InvalidXML creation FAILED!!!"); -// query.clear(); - - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'button', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'embed', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'fieldset', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'form', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'input', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'label', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'legend', '');"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'o:p', '')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'option', '')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'script', '')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'select', '')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ELEMENT', 'wbr', '')"); - - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'done')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'id')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'onclick')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'a', 'onmousedown')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'div', 'id')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'dl', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'dl', 'id')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'dt', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h1', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h2', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h3', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h4', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'h5', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'img', 'gptag')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'li', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'ol', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'ol', 'id')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'p', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'p', 'id')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'p', 'span')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'accesskey')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'action')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'alt')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'bgcolor')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'checked')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'flashvars')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'for')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'height')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'id')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'maxlength')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'method')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'name')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'onblur')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'onchange')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'aclick')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'onsubmit')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'quality')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'selected')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'src')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'target')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'type')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'value')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'width')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'span', 'wmode')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'table', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'td', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'tr', 'class')"); - query.exec("Insert into InvalidXML (type, element, attribute) values ('ATTRIBUTE', 'ul', 'class')"); - - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table InvalidXML"); - } - // Add an item to the table - public void addAttribute(String element, String attribute) { - if (attributeExists(element,attribute)) - return; - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Insert Into InvalidXML (type, element, attribute) Values('ATTRIBUTE', :element, :attribute)"); - query.bindValue(":element", element); - query.bindValue(":attribute", attribute); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Insert Attribute into invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Add an item to the table - public void addElement(String element) { - if (elementExists(element)) - return; - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Insert Into InvalidXML (type, element) Values('ELEMENT', :element)"); - query.bindValue(":element", element); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Insert Element into invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // get invalid elements - public List getInvalidElements() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("Select element from InvalidXML where type = 'ELEMENT'")) { - logger.log(logger.MEDIUM, "getInvalidElement from invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - List elements = new ArrayList(); - while (query.next()) { - elements.add(query.valueString(0)); - } - return elements; - } - - // get invalid elements - public List getInvalidAttributeElements() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("Select distinct element from InvalidXML where type = 'ATTRIBUTE'")) { - logger.log(logger.MEDIUM, "getInvalidElement from invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - List elements = new ArrayList(); - while (query.next()) { - elements.add(query.valueString(0)); - } - return elements; - } - // get invalid attributes for a given element - public ArrayList getInvalidAttributes(String element) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select attribute from InvalidXML where type = 'ATTRIBUTE' and element = :element"); - query.bindValue(":element", element); - if (!query.exec()) { - logger.log(logger.MEDIUM, "getInvalidElement from invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - ArrayList elements = new ArrayList(); - while (query.next()) { - elements.add(query.valueString(0)); - } - return elements; - } - - // Determine if an element already is in the table - public boolean elementExists(String element) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select element from InvalidXML where type='ELEMENT' and element=:element"); - query.bindValue(":element", element); - if (!query.exec()) { - logger.log(logger.MEDIUM, "elementExists in invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - if (query.next()) - return true; - else - return false; - } - - // Determine if an element already is in the table - public boolean attributeExists(String element, String attribute) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select element from InvalidXML where type='ATTRIBUTE' and element=:element and attribute=:attribute"); - query.bindValue(":element", element); - query.bindValue(":attribute", attribute); - if (!query.exec()) { - logger.log(logger.MEDIUM, "attributeExists in invalidXML failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - if (query.next()) - return true; - else - return false; - } -} diff --git a/src/cx/fbn/nevernote/sql/runners/RNoteResourceTable.java b/src/cx/fbn/nevernote/sql/runners/RNoteResourceTable.java deleted file mode 100644 index ef48e6c..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RNoteResourceTable.java +++ /dev/null @@ -1,569 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.Data; -import com.evernote.edam.type.Resource; -import com.evernote.edam.type.ResourceAttributes; -import com.trolltech.qt.core.QByteArray; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; - - - -public class RNoteResourceTable { - /** - * - */ - private static final long serialVersionUID = 1L; - private final ApplicationLogger logger; - private final RDatabaseConnection db; - - // Constructor - public RNoteResourceTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - // Create the NoteResource table - logger.log(logger.HIGH, "Creating table NoteResource..."); - if (!query.exec("Create table NoteResources (guid varchar primary key, " + - "noteGuid varchar, updateSequenceNumber integer, dataHash varchar, "+ - "dataSize integer, dataBinary blob, "+ - "mime varchar, width integer, height integer, duration integer, "+ - "active integer, recognitionHash varchar, recognitionSize integer, " + - "recognitionBinary varchar, attributeSourceUrl varchar, attributeTimestamp timestamp, " + - "attributeLatitude double, attributeLongitude double, "+ - "attributeAltitude double, attributeCameraMake varchar, attributeCameraModel varchar, " - +"attributeClientWillIndex varchar, attributeRecoType varchar, attributeFileName varchar,"+ - "attributeAttachment boolean, isDirty boolean, indexNeeded boolean)")) - logger.log(logger.HIGH, "Table NoteResource creation FAILED!!!"); - if (!query.exec("CREATE INDEX unindexed_resources on noteresources (indexneeded desc, guid);")) - logger.log(logger.HIGH, "Noteresources unindexed_resources index creation FAILED!!!"); - if (!query.exec("CREATE INDEX resources_dataheshhex on noteresources (datahash, guid);")) - logger.log(logger.HIGH, "Noteresources resources_datahash index creation FAILED!!!"); - - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table NoteResources"); - } - // Reset the dirty flag - public void resetDirtyFlag(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Update noteresources set isdirty=false where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Error resetting noteresource dirty field. " +query.lastError()); - } - // Set if the resource should be indexed - public void setIndexNeeded(String guid, Boolean indexNeeded) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Update noteresources set indexNeeded=:needed where guid=:guid"); - query.bindValue(":needed", indexNeeded); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Error setting noteresource indexneeded field: " +query.lastError()); - } - // get any unindexed resource - public List getNextUnindexed(int limit) { - List guids = new ArrayList(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - - if (!query.exec("Select guid from NoteResources where indexNeeded = true limit " +limit)) - logger.log(logger.EXTREME, "NoteResources SQL retrieve has failed on getNextUnindexed(): " +query.lastError()); - - // Get a list of the notes - String guid; - while (query.next()) { - guid = new String(); - guid = query.valueString(0); - guids.add(guid); - } - return guids; - } - - - - public void saveNoteResource(Resource r, boolean isDirty) { - logger.log(logger.HIGH, "Entering DBRunner.saveNoteResources"); - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - check = query.prepare("Insert Into NoteResources (" - +"guid, noteGuid, dataHash, dataSize, dataBinary, updateSequenceNumber, " - +"mime, width, height, duration, active, recognitionHash, " - +"recognitionSize, recognitionBinary, attributeSourceUrl, attributeTimestamp, " - +"attributeLatitude, attributeLongitude, attributeAltitude, attributeCameraMake, " - +"attributeCameraModel, " - +"attributeClientWillIndex, attributeRecoType, attributeFileName, attributeAttachment, isDirty, " - +"indexNeeded) Values(" - +":guid, :noteGuid, :dataHash,:dataSize, :dataBody, :updateSequenceNumber, " - +":mime, :width, :height, :duration, :active, :recognitionHash, " - +":recognitionSize, :recognitionBody, :attributeSourceUrl, :attributeTimestamp, " - +":attributeLatitude, :attributeLongitude, :attributeAltitude, :attributeCameraMake, " - +":attributeCameraModel, " - +":attributeClientWillIndex, :attributeRecoType, :attributeFileName, :attributeAttachment, " - +":isDirty, true)"); - if (!check) { - logger.log(logger.EXTREME, "NoteResource SQL insert prepare has failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - - query.bindValue(":guid", r.getGuid()); - query.bindValue(":noteGuid", r.getNoteGuid()); - if (r.getData() != null) { -// query.bindValue(":dataHash", new QByteArray(r.getData().getBodyHash()).toHex()); -// query.bindValue(":dataHash", ""); - query.bindValue(":dataHash", byteArrayToHexString(r.getData().getBodyHash())); - query.bindValue(":dataSize", r.getData().getSize()); - query.bindBlob(":dataBody", r.getData().getBody()); - } - query.bindValue(":updateSequenceNumber", r.getUpdateSequenceNum()); - query.bindValue(":mime", r.getMime()); - query.bindValue(":width", new Integer(r.getWidth())); - query.bindValue(":height", new Integer(r.getHeight())); - query.bindValue(":duration", new Integer(r.getDuration())); - query.bindValue(":active", r.isActive()); - if (r.getRecognition() != null) { - query.bindValue(":recognitionHash", r.getRecognition().getBodyHash()); - query.bindValue(":recognitionSize", r.getRecognition().getSize()); - if (r.getRecognition().getBody() != null) - query.bindValue(":recognitionBody", new String(r.getRecognition().getBody())); - else - query.bindValue(":recognitionBody", ""); - } else { - query.bindValue(":recognitionHash", ""); - query.bindValue(":recognitionSize", 0); - query.bindValue(":recognitionBody", ""); - } - if (r.getAttributes() != null) { - query.bindValue(":attributeSourceUrl", r.getAttributes().getSourceURL()); - StringBuilder ts = new StringBuilder(simple.format(r.getAttributes().getTimestamp())); - query.bindValue(":attributeTimestamp", ts.toString()); - query.bindValue(":attributeLatitude", r.getAttributes().getLatitude()); - query.bindValue(":attributeLongitude", r.getAttributes().getLongitude()); - query.bindValue(":attributeAltitude", r.getAttributes().getAltitude()); - query.bindValue(":attributeCameraMake", r.getAttributes().getCameraMake()); - query.bindValue(":attributeCameraModel", r.getAttributes().getCameraModel()); - query.bindValue(":attributeClientWillIndex", r.getAttributes().isClientWillIndex()); - query.bindValue(":attributeRecoType", r.getAttributes().getRecoType()); - query.bindValue(":attributeFileName", r.getAttributes().getFileName()); - query.bindValue(":attributeAttachment", r.getAttributes().isAttachment()); - } - query.bindValue(":isDirty", isDirty); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "*** NoteResource Table insert failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - - - logger.log(logger.HIGH, "Leaving DBRunner.saveNoteResources"); - } - // delete an old resource - public void expungeNoteResource(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("delete from NoteResources where guid=:guid"); - query.bindValue(":guid", guid); - query.exec(); - } - - - // Get a note resource from the database by it's hash value - public String getNoteResourceGuidByHashHex(String noteGuid, String hash) { - logger.log(logger.HIGH, "Entering DBRunner.getNoteResourceGuidByHashHex"); - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Select guid from NoteResources " + - "where noteGuid=:noteGuid and dataHash=:hash"); - if (check) - logger.log(logger.EXTREME, "NoteResource SQL select prepare was successful."); - else { - logger.log(logger.EXTREME, "NoteResource SQL select prepare has failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - query.bindValue(":noteGuid", noteGuid); - query.bindValue(":hash", hash); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "dbRunner.getNoteResourceGuidByHashHex Select failed." + - "Note Guid:" +noteGuid+ - "Data Body Hash:" +hash); - logger.log(logger.MEDIUM, query.lastError()); - } - if (!query.next()) { - logger.log(logger.MEDIUM, "Note Resource not found."); - return null; - } - return query.valueString(0); - } - - // Get a note resource from the database by it's hash value - public Resource getNoteResourceDataBodyByHashHex(String noteGuid, String hash) { - logger.log(logger.HIGH, "Entering DBRunner.getNoteResourceDataBodyByHash"); - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Select guid, mime, from NoteResources " + - "where noteGuid=:noteGuid and dataHash=:hash"); - if (!check) { - logger.log(logger.EXTREME, "NoteResource SQL select prepare has failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - query.bindValue(":noteGuid", noteGuid); - query.bindValue(":hash", hash); - - if (!query.exec()) { - logger.log(logger.MEDIUM, "NoteResource Select failed." + - "Note Guid:" +noteGuid+ - "Data Body Hash:" +hash); - logger.log(logger.MEDIUM, query.lastError()); - } - if (!query.next()) { - logger.log(logger.MEDIUM, "Note Resource not found."); - return null; - } - - Resource r = new Resource(); - r.setGuid(query.valueString(0)); - r.setMime(query.valueString(1)); - - NSqlQuery binary = new NSqlQuery(db.getConnection()); - if (!binary.prepare("Select databinary from NoteResources " + - "where guid=:guid")) { - logger.log(logger.MEDIUM, "Prepare for NoteResources Binary failed"); - logger.log(logger.MEDIUM, binary.lastError()); - } - - if (!binary.exec()) { - logger.log(logger.MEDIUM, "NoteResources Binary Select failed." + - "Note Guid:" +noteGuid+ - "Data Body Hash:" +hash); - logger.log(logger.MEDIUM, binary.lastError()); - } - if (!binary.next()) { - logger.log(logger.MEDIUM, "Note Resource Binary not found."); - return null; - } - - Data d = new Data(); - r.setData(d); - d.setBody(binary.valueString(0).getBytes()); - logger.log(logger.HIGH, "Leaving DBRunner.getNoteResourceDataBodyByHash"); - return r; - } - - - // Get a note's resourcesby Guid - public Resource getNoteResource(String guid, boolean withBinary) { - if (guid == null) - return null; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - String queryString; - queryString = new String("Select guid, noteGuid, mime, width, height, duration, " - +"active, updateSequenceNumber, dataHash, dataSize, " - +"recognitionHash, recognitionSize, " - +"attributeLatitude, attributeLongitude, attributeAltitude, " - +"attributeCameraMake, attributeCameraModel, attributeClientWillIndex, " - +"attributeRecoType, attributeFileName, attributeAttachment, recognitionBinary " - +" from NoteResources where guid=:guid"); - - - query.prepare(queryString); - - query.bindValue(":guid", guid); - if (!query.exec()) { - logger.log(logger.EXTREME, "NoteResources SQL select has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - Resource r = null; - if (query.next()) { - - r = new Resource(); - r.setGuid(query.valueString(0)); // Resource Guid - r.setNoteGuid(query.valueString(1)); // note Guid - r.setMime(query.valueString(2)); // Mime Type - r.setWidth(new Short(query.valueString(3))); // Width - r.setHeight(new Short(query.valueString(4))); // Height - r.setDuration(new Short(query.valueString(5))); // Duration - r.setActive(new Boolean(query.valueString(6))); // active - r.setUpdateSequenceNum(new Integer(query.valueString(7))); // update sequence number - - Data d = new Data(); - byte[] h = query.valueString(8).getBytes(); // data hash - QByteArray hData = new QByteArray(h); - QByteArray bData = new QByteArray(QByteArray.fromHex(hData)); - d.setBodyHash(bData.toByteArray()); - d.setSize(new Integer(query.valueString(9))); - r.setData(d); - - Data rec = new Data(); - if (query.valueObject(10) != null) - rec.setBodyHash(query.valueString(10).getBytes()); // Recognition Hash - if (query.valueObject(11) != null) - rec.setSize(new Integer(query.valueString(11))); - else - rec.setSize(0); - r.setRecognition(rec); - - ResourceAttributes a = new ResourceAttributes(); - if (!query.valueString(12).equals("")) // Latitude - a.setLatitude(new Float(query.valueString(12))); - if (!query.valueString(13).equals("")) // Longitude - a.setLongitude(new Float(query.valueString(13))); - if (!query.valueString(14).equals("")) // Altitude - a.setAltitude(new Float(query.valueString(14))); - a.setCameraMake(stringValue(query.valueString(15))); // Camera Make - a.setCameraModel(stringValue(query.valueString(16))); - a.setClientWillIndex(booleanValue(query.valueString(17).toString(),false)); // Camera Model - a.setRecoType(stringValue(query.valueString(18))); // Recognition Type - a.setFileName(stringValue(query.valueString(19))); // File Name - a.setAttachment(booleanValue(query.valueString(20).toString(),false)); - r.setAttributes(a); - - if (withBinary) { - - query.prepare("Select dataBinary from NoteResources where guid=:guid"); - query.bindValue(":guid", r.getGuid()); - query.exec(); - if (query.next()) { - byte[] b = query.getBlob(0); - r.getData().setBody(b); - } - } - } - return r; - } - - - // Get a note's resourcesby Guid - public List getNoteResources(String noteGuid, boolean withBinary) { - if (noteGuid == null) - return null; - List res = new ArrayList(); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select guid" - +" from NoteResources where noteGuid = :noteGuid"); - query.bindValue(":noteGuid", noteGuid); - if (!query.exec()) { - logger.log(logger.EXTREME, "NoteResources SQL select has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - while (query.next()) { - String guid = (query.valueString(0)); - res.add(getNoteResource(guid, withBinary)); - } - return res; - } - // Get all of a note's recognition data by the note guid - public List getNoteResourcesRecognition(String noteGuid) { - if (noteGuid == null) - return null; - boolean check; - List res = new ArrayList(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Select " - +"recognitionHash, recognitionSize, recognitionBinary " - +" from NoteResources where noteGuid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "NoteTable.getNoteRecognition SQL prepare has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - query.bindValue(":guid", noteGuid); - if (!check) { - logger.log(logger.EXTREME, "NoteTable.getNoteRecognition exec has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - while (query.next()) { - Resource r = new Resource(); - res.add(r); - Data rec = new Data(); - rec.setBodyHash(query.valueString(0).getBytes()); - String x = new String(query.valueString(1)); - if (!x.equals("")) { - rec.setSize(new Integer(x)); - rec.setBody(query.valueString(2).getBytes()); - } else - rec.setSize(0); - r.setRecognition(rec); - } - return res; - } - - - // Get a note's recognition data by it's guid. - public Resource getNoteResourceRecognition(String guid) { - if (guid == null) - return null; - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Select " - +"recognitionHash, recognitionSize, recognitionBinary, noteGuid " - +" from NoteResources where guid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "NoteTable.getNoteRecognition SQL prepare has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - query.bindValue(":guid", guid); - query.exec(); - if (!check) { - logger.log(logger.EXTREME, "NoteTable.getNoteRecognition exec has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - Resource r = null; - while (query.next()) { - - r = new Resource(); - Data rec = new Data(); - rec.setBodyHash(query.valueString(0).getBytes()); - String x = new String(query.valueString(1)); - if (!x.equals("")) { - rec.setSize(new Integer(x)); - rec.setBody(query.valueString(2).getBytes()); - } else - rec.setSize(0); - r.setRecognition(rec); - r.setNoteGuid(query.valueString(3)); - } - return r; - } - - // Save Note Resource - public void updateNoteResource(Resource r, boolean isDirty) { - logger.log(logger.HIGH, "Entering ListManager.updateNoteResource"); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("delete from NoteResources where guid=:recGuid"); - query.bindValue(":recGuid", r.getGuid()); - query.exec(); - saveNoteResource(r, isDirty); - logger.log(logger.HIGH, "Leaving RNoteResourceTable.updateNoteResource"); - } - // Update note resource GUID - public void updateNoteResourceGuid(String oldGuid, String newGuid, boolean isDirty) { - logger.log(logger.HIGH, "Entering RNoteResourceTable.updateNoteResourceGuid"); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("update NoteResources set guid=:newGuid, isDirty=:isDirty where guid=:oldGuid"); - query.bindValue(":newGuid", newGuid); - query.bindValue(":isDirty", isDirty); - query.bindValue(":oldGuid", oldGuid); - query.exec(); - logger.log(logger.HIGH, "Leaving RNoteResourceTable.updateNoteResourceGuid"); - } - // Update note resource GUID - public void resetUpdateSequenceNumber(String guid, boolean isDirty) { - logger.log(logger.HIGH, "Entering RNoteResourceTable.updateNoteResourceGuid"); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("update NoteResources set updateSequenceNumber=0, isDirty=:isDirty where guid=:guid"); - query.bindValue(":isDirty", isDirty); - query.bindValue(":guid", guid); - query.exec(); - logger.log(logger.HIGH, "Leaving RNoteResourceTable.updateNoteResourceGuid"); - } - - // Drop the table - public void reindexAll() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Update NoteResources set indexneeded=true"); - } - // Count unindexed notes - public int getResourceCount() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("select count(*) from noteresources"); - query.next(); - int returnValue = new Integer(query.valueString(0)); - return returnValue; - } - - //******************************************** - //** 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(); - } - - - private String stringValue(Object value) { - if (value != null && value.toString() != null) - return value.toString(); - else - return null; - } - - private boolean booleanValue(Object value, boolean unknown) { - if (value != null && value.toString() != null) { - try { - if ((Integer)value > 0) - return true; - else - return false; - } catch (java.lang.ClassCastException e) { - try { - String stringValue = (String)value; - if (stringValue.equalsIgnoreCase("true")) - return true; - else - return false; - } catch (java.lang.ClassCastException e1) { - return unknown; - } - } - } - else - return unknown; - } - -} diff --git a/src/cx/fbn/nevernote/sql/runners/RNoteTable.java b/src/cx/fbn/nevernote/sql/runners/RNoteTable.java deleted file mode 100644 index 0d84a18..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RNoteTable.java +++ /dev/null @@ -1,1110 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.Note; -import com.evernote.edam.type.NoteAttributes; -import com.evernote.edam.type.Resource; -import com.evernote.edam.type.Tag; -import com.trolltech.qt.core.QByteArray; -import com.trolltech.qt.core.QDateTime; -import com.trolltech.qt.core.QTextCodec; - -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.evernote.EnmlConverter; -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; -import cx.fbn.nevernote.utilities.Pair; - -public class RNoteTable { - private final ApplicationLogger logger; - public final RNoteTagsTable noteTagsTable; - public RNoteResourceTable noteResourceTable; - private final RDatabaseConnection db; - int id; - - // Prepared Queries to improve speed - private NSqlQuery getQueryWithContent; - private NSqlQuery getQueryWithoutContent; - private NSqlQuery getAllQueryWithoutContent; - - // Constructor - public RNoteTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - id = 0; - noteResourceTable = new RNoteResourceTable(logger, db); - noteTagsTable = new RNoteTagsTable(logger, db); - getQueryWithContent = null; - getQueryWithoutContent = null; - - } - // Create the table - public void createTable() { - getQueryWithContent = new NSqlQuery(db.getConnection()); - getQueryWithoutContent = new NSqlQuery(db.getConnection()); - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table Note..."); - if (!query.exec("Create table Note (guid varchar primary key, " + - "updateSequenceNumber integer, title varchar, content varchar, contentHash varchar, "+ - "contentLength integer, created timestamp, updated timestamp, deleted timestamp, " - +"active integer, notebookGuid varchar, attributeSubjectDate timestamp, "+ - "attributeLatitude double, attributeLongitude double, attributeAltitude double,"+ - "attributeAuthor varchar, attributeSource varchar, attributeSourceUrl varchar, "+ - "attributeSourceApplication varchar, indexNeeded boolean, isExpunged boolean, " + - "isDirty boolean)")) - logger.log(logger.HIGH, "Table Note creation FAILED!!!"); - if (!query.exec("CREATE INDEX unindexed_notess on note (indexneeded desc, guid);")) - logger.log(logger.HIGH, "Note unindexed_notes index creation FAILED!!!"); - if (!query.exec("CREATE INDEX unsynchronized_notes on note (isDirty desc, guid);")) - logger.log(logger.HIGH, "note unsynchronized_notes index creation FAILED!!!"); - noteTagsTable.createTable(); - noteResourceTable.createTable(); - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table Note"); - noteTagsTable.dropTable(); - noteResourceTable.dropTable(); - } - // Save Note List from Evernote - public void addNote(Note n, boolean isDirty) { - logger.log(logger.EXTREME, "Inside addNote"); - if (n == null) - return; - - SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Insert Into Note (" - +"guid, updateSequenceNumber, title, content, " - +"contentHash, contentLength, created, updated, deleted, active, notebookGuid, " - +"attributeSubjectDate, attributeLatitude, attributeLongitude, attributeAltitude, " - +"attributeAuthor, attributeSource, attributeSourceUrl, attributeSourceApplication, " - +"indexNeeded, isExpunged, isDirty, titlecolor, thumbnailneeded" - +") Values(" - +":guid, :updateSequenceNumber, :title, :content, " - +":contentHash, :contentLength, :created, :updated, :deleted, :active, :notebookGuid, " - +":attributeSubjectDate, :attributeLatitude, :attributeLongitude, :attributeAltitude, " - +":attributeAuthor, :attributeSource, :attributeSourceUrl, :attributeSourceApplication, " - +":indexNeeded, :isExpunged, :isDirty, -1, true) "); - - StringBuilder created = new StringBuilder(simple.format(n.getCreated())); - StringBuilder updated = new StringBuilder(simple.format(n.getUpdated())); - StringBuilder deleted = new StringBuilder(simple.format(n.getDeleted())); - - EnmlConverter enml = new EnmlConverter(logger); - - query.bindValue(":guid", n.getGuid()); - query.bindValue(":updateSequenceNumber", n.getUpdateSequenceNum()); - query.bindValue(":title", n.getTitle()); - query.bindValue(":content", enml.fixEnXMLCrap(enml.fixEnMediaCrap(n.getContent()))); - query.bindValue(":contentHash", n.getContentHash()); - query.bindValue(":contentLength", n.getContentLength()); - query.bindValue(":created", created.toString()); - query.bindValue(":updated", updated.toString()); - query.bindValue(":deleted", deleted.toString()); - query.bindValue(":active", n.isActive()); - query.bindValue(":notebookGuid", n.getNotebookGuid()); - - if (n.getAttributes() != null) { - created = new StringBuilder(simple.format(n.getAttributes().getSubjectDate())); - query.bindValue(":attributeSubjectDate", created.toString()); - query.bindValue(":attributeLatitude", n.getAttributes().getLatitude()); - query.bindValue(":attributeLongitude", n.getAttributes().getLongitude()); - query.bindValue(":attributeAltitude", n.getAttributes().getAltitude()); - query.bindValue(":attributeAuthor", n.getAttributes().getAuthor()); - query.bindValue(":attributeSource", n.getAttributes().getSource()); - query.bindValue(":attributeSourceUrl", n.getAttributes().getSourceURL()); - query.bindValue(":attributeSourceApplication", n.getAttributes().getSourceApplication()); - } - query.bindValue(":indexNeeded", true); - query.bindValue(":isExpunged", false); - query.bindValue(":isDirty", isDirty); - - - if (!query.exec()) - logger.log(logger.MEDIUM, query.lastError()); - - // Save the note tags - if (n.getTagGuids() != null) { - for (int i=0; i " +query.lastError().toString()); - logger.log(logger.EXTREME, " -> " +query.lastError()); - return null; - } - Note n = mapNoteFromQuery(query, loadContent, loadResources, loadRecognition, loadBinary, loadTags); - n.setContent(fixCarriageReturn(n.getContent())); - return n; - } - // Get a note by Guid - public Note mapNoteFromQuery(NSqlQuery query, boolean loadContent, boolean loadResources, boolean loadRecognition, boolean loadBinary, boolean loadTags) { - DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); -// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); - - - Note n = new Note(); - NoteAttributes na = new NoteAttributes(); - n.setAttributes(na); - - n.setGuid(query.valueString(0)); - n.setUpdateSequenceNum(new Integer(query.valueString(1))); - n.setTitle(query.valueString(2)); - - try { - n.setCreated(indfm.parse(query.valueString(3)).getTime()); - n.setUpdated(indfm.parse(query.valueString(4)).getTime()); - n.setDeleted(indfm.parse(query.valueString(5)).getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - - n.setActive(query.valueBoolean(6,true)); - n.setNotebookGuid(query.valueString(7)); - - try { - String attributeSubjectDate = query.valueString(8); - if (!attributeSubjectDate.equals("")) - na.setSubjectDate(indfm.parse(attributeSubjectDate).getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - na.setLatitude(new Float(query.valueString(9))); - na.setLongitude(new Float(query.valueString(10))); - na.setAltitude(new Float(query.valueString(11))); - na.setAuthor(query.valueString(12)); - na.setSource(query.valueString(13)); - na.setSourceURL(query.valueString(14)); - na.setSourceApplication(query.valueString(15)); - - if (loadTags) { - n.setTagGuids(noteTagsTable.getNoteTags(n.getGuid())); - List tagNames = new ArrayList(); - RTagTable tagTable = new RTagTable(logger, db); - for (int i=0; i resources = noteResourceTable.getNoteResourcesRecognition(n.getGuid()); - n.setResources(resources); - } else { - // We need to merge the recognition resources with the note resources retrieved earlier - for (int i=0; i getDirty() { - String guid; - Note tempNote; - List notes = new ArrayList(); - List index = new ArrayList(); - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid from Note where isDirty = true and notebookGuid not in (select guid from notebook where local = true)"); - if (!check) - logger.log(logger.EXTREME, "Note SQL retrieve has failed: " +query.lastError().toString()); - - // Get a list of the notes - while (query.next()) { - guid = new String(); - guid = query.valueString(0); - index.add(guid); - } - - // Start getting notes - for (int i=0; i getUnsynchronizedGUIDs() { - String guid; - List index = new ArrayList(); - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid from Note where isDirty = true"); - if (!check) - logger.log(logger.EXTREME, "Note SQL retrieve has failed: " +query.lastError().toString()); - - // Get a list of the notes - while (query.next()) { - guid = new String(); - guid = query.valueString(0); - index.add(guid); - } - return index; - } - // Reset the dirty bit - public void resetDirtyFlag(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Update note set isdirty=false where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Error resetting note dirty field."); - } - // Get all notes - public List getAllGuids() { - List notes = new ArrayList(); - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid from Note"); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL retrieve has failed: "+query.lastError()); - - // Get a list of the notes - while (query.next()) { - notes.add(new String(query.valueString(0))); - } - return notes; - } - // Get all notes - public List getAllNotes() { - List notes = new ArrayList(); - prepareQueries(); - boolean check; - NSqlQuery query = getAllQueryWithoutContent; - check = query.exec(); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL retrieve has failed: "+query.lastError()); - // Get a list of the notes - while (query.next()) { - notes.add(mapNoteFromQuery(query, false, false, false, false, true)); - } - return notes; - } - // Count unindexed notes - public int getUnindexedCount() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("select count(*) from note where indexneeded=true and isExpunged = false"); - query.next(); - int returnValue = new Integer(query.valueString(0)); - return returnValue; - } - // Count unsynchronized notes - public int getDirtyCount() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("select count(*) from note where isDirty=true and isExpunged = false"); - query.next(); - int returnValue = new Integer(query.valueString(0)); - return returnValue; - } - // Count notes - public int getNoteCount() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("select count(*) from note where isExpunged = false"); - query.next(); - int returnValue = new Integer(query.valueString(0)); - return returnValue; - } - // Count deleted notes - public int getDeletedCount() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("select count(*) from note where isExpunged = false and active = false"); - if (!query.next()) - return 0; - int returnValue = new Integer(query.valueString(0)); - return returnValue; - } - // Reset a note sequence number to zero. This is useful for moving conflicting notes - public void resetNoteSequence(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - boolean check = query.prepare("Update Note set updateSequenceNumber=0, isDirty=true where guid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "Update note ResetSequence sql prepare has failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) { - logger.log(logger.EXTREME, "Update note sequence number has failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - - - // Update a note resource by the hash - public void updateNoteResourceGuidbyHash(String noteGuid, String resGuid, String hash) { - NSqlQuery query = new NSqlQuery(db.getConnection()); -/* query.prepare("Select guid from NoteResources where noteGuid=:noteGuid and datahash=:hex"); - query.bindValue(":noteGuid", noteGuid); - query.bindValue(":hex", hash); - query.exec(); - if (!query.next()) { - logger.log(logger.LOW, "Error finding note resource in RNoteTable.updateNoteResourceGuidbyHash. GUID="+noteGuid +" resGuid="+ resGuid+" hash="+hash); - return; - } - String guid = query.valueString(0); -*/ - query.prepare("update noteresources set guid=:guid where noteGuid=:noteGuid and datahash=:hex"); - query.bindValue(":guid", resGuid); - query.bindValue(":noteGuid", noteGuid); - query.bindValue(":hex", hash); - if (!query.exec()) { - logger.log(logger.EXTREME, "Note Resource Update by Hash failed"); - logger.log(logger.EXTREME, query.lastError().toString()); - } - } - - // Fix CRLF problem that is on some notes - private String fixCarriageReturn(String note) { - if (note == null || !Global.enableCarriageReturnFix) - return note; - QByteArray a0Hex = new QByteArray("a0"); - String a0 = QByteArray.fromHex(a0Hex).toString(); - note = note.replace("
    "+a0+"
    ", "
     
    "); - return note.replace("
    ", "
     
    "); - } - - - - //******************************************************************************** - //******************************************************************************** - //* Indexing Functions - //******************************************************************************** - //******************************************************************************** - // set/unset a note to be reindexed - public void setIndexNeeded(String guid, Boolean flag) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Update Note set indexNeeded=:flag where guid=:guid"); - - if (flag) - query.bindValue(":flag", 1); - else - query.bindValue(":flag", 0); - query.bindValue(":guid", guid); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Note indexNeeded update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Set all notes to be reindexed - public void reindexAllNotes() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("Update Note set indexNeeded=true")) { - logger.log(logger.MEDIUM, "Note reindexAllNotes update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - - // Get all unindexed notes - public List getUnindexed() { - String guid; - List index = new ArrayList(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - - if (!query.exec("Select guid from Note where isExpunged = false and indexNeeded = true and DATEDIFF('MINUTE',updated,CURRENT_TIMESTAMP)>5")) - logger.log(logger.EXTREME, "Note SQL retrieve has failed on getUnindexed()."); - - // Get a list of the notes - while (query.next()) { - guid = new String(); - guid = query.valueString(0); - index.add(guid); - } - return index; - } - public List getNextUnindexed(int limit) { - List guids = new ArrayList(); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - if (!query.exec("Select guid from Note where isExpunged = false and indexNeeded = true and DATEDIFF('MINUTE',Updated,CURRENT_TIMESTAMP)>5 limit " +limit)) - logger.log(logger.EXTREME, "Note SQL retrieve has failed on getUnindexed()."); - - // Get a list of the notes - String guid; - while (query.next()) { - guid = new String(); - guid = query.valueString(0); - guids.add(guid); - } - return guids; - } - - - //********************************************************************************** - //* Title color functions - //********************************************************************************** - // Get the title color of all notes - public List> getNoteTitleColors() { - List> returnValue = new ArrayList>(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - - if (!query.exec("Select guid,titleColor from Note where titleColor != -1")) - logger.log(logger.EXTREME, "Note SQL retrieve has failed on getUnindexed()."); - - String guid; - Integer color; - - // Get a list of the notes - while (query.next()) { - Pair pair = new Pair(); - guid = query.valueString(0); - color = query.valueInteger(1); - pair.setFirst(guid); - pair.setSecond(color); - returnValue.add(pair); - } - - - - return returnValue; - } - // Set a title color - // Reset the dirty bit - public void setNoteTitleColor(String guid, int color) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Update note set titlecolor=:color where guid=:guid"); - query.bindValue(":guid", guid); - query.bindValue(":color", color); - if (!query.exec()) - logger.log(logger.EXTREME, "Error updating title color."); - } - - - - //********************************************************************************** - //* Thumbnail functions - //********************************************************************************** - // Set if a new thumbnail is needed - public void setThumbnailNeeded(String guid, boolean needed) { - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Update note set thumbnailneeded = :needed where guid=:guid"); - query.bindValue(":guid", guid); - query.bindValue(":needed", needed); - check = query.exec(); - if (!check) - logger.log(logger.EXTREME, "Note SQL set thumbail needed failed: " +query.lastError().toString()); - - } - // Is a thumbail needed for this guid? - public boolean isThumbnailNeeded(String guid) { - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("select thumbnailneeded from note where guid=:guid"); - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) - logger.log(logger.EXTREME, "Note SQL isThumbnailNeeded query failed: " +query.lastError().toString()); - - boolean returnValue; - // Get a list of the notes - if (query.next()) - returnValue = query.valueBoolean(0, false); - else - returnValue = false; - - return returnValue; - } - // Set if a new thumbnail is needed - public void setThumbnail(String guid, QByteArray thumbnail) { - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Update note set thumbnail = :thumbnail where guid=:guid"); - query.bindValue(":guid", guid); - query.bindValue(":thumbnail", thumbnail.toByteArray()); - check = query.exec(); - if (!check) - logger.log(logger.EXTREME, "Note SQL set thumbail failed: " +query.lastError().toString()); - - } - // Set if a new thumbnail is needed - public QByteArray getThumbnail(String guid) { - - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Select thumbnail from note where guid=:guid"); - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) - logger.log(logger.EXTREME, "Note SQL get thumbail failed: " +query.lastError().toString()); - // Get a list of the notes - if (query.next()) - if (query.getBlob(0) != null) - return new QByteArray(query.getBlob(0)); - return null; - } - - - // Update a note content's hash. This happens if a resource is edited outside of NN - public void updateResourceContentHash(String guid, String oldHash, String newHash) { - Note n = getNote(guid, true, false, false, false,false); - int position = n.getContent().indexOf("-1;) { - endPos = n.getContent().indexOf(">", position+1); - String oldSegment = n.getContent().substring(position,endPos); - int hashPos = oldSegment.indexOf("hash=\""); - int hashEnd = oldSegment.indexOf("\"", hashPos+7); - String hash = oldSegment.substring(hashPos+6, hashEnd); - if (hash.equalsIgnoreCase(oldHash)) { - String newSegment = oldSegment.replace(oldHash, newHash); - String content = n.getContent().substring(0,position) + - newSegment + - n.getContent().substring(endPos); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("update note set isdirty=true, content=:content where guid=:guid"); - query.bindValue(":content", content); - query.bindValue(":guid", n.getGuid()); - query.exec(); - } - - position = n.getContent().indexOf(" getNoteTags(String noteGuid) { - if (noteGuid == null) - return null; - boolean check; - List tags = new ArrayList(); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.exec("Select " - +"TagGuid from NoteTags where noteGuid = '" +noteGuid +"'"); - if (!check) { - logger.log(logger.EXTREME, "NoteTags SQL select has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - while (query.next()) { - tags.add(query.valueString(0)); - } - return tags; - } - // Get a note tags by the note's Guid - public List getAllNoteTags() { - List tags = new ArrayList(); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("Select TagGuid, NoteGuid from NoteTags")) { - logger.log(logger.EXTREME, "NoteTags SQL select has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - while (query.next()) { - NoteTagsRecord record = new NoteTagsRecord(); - record.tagGuid = query.valueString(0); - record.noteGuid = query.valueString(1); - tags.add(record); - } - return tags; - } - // Check if a note has a specific tag already - public boolean checkNoteNoteTags(String noteGuid, String tagGuid) { - if (noteGuid == null || tagGuid == null) - return false; - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Select " - +"NoteGuid, TagGuid from NoteTags where noteGuid = :noteGuid and tagGuid = :tagGuid"); - if (!check) - logger.log(logger.EXTREME, "checkNoteTags SQL prepare has failed."); - - query.bindValue(":noteGuid", noteGuid); - query.bindValue(":tagGuid", tagGuid); - query.exec(); - - if (!check) { - logger.log(logger.EXTREME, "checkNoteTags SQL select has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return false; - } - - if (query.next()) { - return true; - } - return false; - } - // Save Note Tags - public void saveNoteTag(String noteGuid, String tagGuid) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Insert Into NoteTags (noteGuid, tagGuid) " - +"Values(" - +":noteGuid, :tagGuid)"); - if (!check) - logger.log(logger.EXTREME, "Note SQL insert prepare has failed."); - - query.bindValue(":noteGuid", noteGuid); - query.bindValue(":tagGuid", tagGuid); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "NoteTags Table insert failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - check = query.prepare("Update Note set isDirty=1 where guid=:guid"); - if (!check) - logger.log(logger.EXTREME, "RNoteTagsTable.saveNoteTag prepare has failed."); - query.bindValue(":guid", noteGuid); - if (!check) { - logger.log(logger.MEDIUM, "RNoteTagsTable.saveNoteTag has failed to set note as dirty."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Delete a note's tags - public void deleteNoteTag(String noteGuid) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Delete from NoteTags where noteGuid = :noteGuid"); - if (!check) - logger.log(logger.EXTREME, "Note SQL delete prepare has failed."); - - query.bindValue(":noteGuid", noteGuid); - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "NoteTags Table delete failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - - } - // Get a note tag counts - public List> getTagCounts() { - List> counts = new ArrayList>(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("select tagguid, count(noteguid) from notetags group by tagguid;")) { - logger.log(logger.EXTREME, "NoteTags SQL getTagCounts has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - while (query.next()) { - Pair newCount = new Pair(); - newCount.setFirst(query.valueString(0)); - newCount.setSecond(query.valueInteger(1)); - counts.add(newCount); - } - return counts; - } -} diff --git a/src/cx/fbn/nevernote/sql/runners/RNotebookTable.java b/src/cx/fbn/nevernote/sql/runners/RNotebookTable.java deleted file mode 100644 index 2bb9b5b..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RNotebookTable.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.Notebook; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.sql.requests.NotebookRequest; -import cx.fbn.nevernote.utilities.ApplicationLogger; -import cx.fbn.nevernote.utilities.Pair; - -public class RNotebookTable { - - private final ApplicationLogger logger; - RDatabaseConnection db; - public NotebookRequest mainResponse; - - // Constructor - public RNotebookTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - mainResponse = new NotebookRequest(); - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table Notebook..."); - if (!query.exec("Create table Notebook (guid varchar primary key, " + - "sequence integer, name varchar, defaultNotebook varchar, "+ - "serviceCreated timestamp, serviceUpdated timestamp, published boolean, isDirty boolean, "+ - "autoEncrypt boolean, local boolean, archived boolean)")) - logger.log(logger.HIGH, "Table Notebook creation FAILED!!!"); - Notebook newnote = new Notebook(); - newnote.setDefaultNotebook(true); - newnote.setName("My Notebook"); - newnote.setPublished(false); - newnote.setGuid("1"); - addNotebook(newnote, true, false); - - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table Notebook"); - } - // Save an individual notebook - public void addNotebook(Notebook tempNotebook, boolean isDirty, boolean local) { - boolean check; - - SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Insert Into Notebook (guid, sequence, name, defaultNotebook, " - +"serviceCreated, serviceUpdated, published, " - + "isDirty, autoEncrypt," - + "local, archived) Values(" - +":guid, :sequence, :name, :defaultNotebook, " - +":serviceCreated, :serviceUpdated, :published, " - +":isDirty, :autoEncrypt, " - +":local, false)"); - query.bindValue(":guid", tempNotebook.getGuid()); - query.bindValue(":sequence", tempNotebook.getUpdateSequenceNum()); - query.bindValue(":name", tempNotebook.getName()); - query.bindValue(":defaultNotebook", tempNotebook.isDefaultNotebook()); - - StringBuilder serviceCreated = new StringBuilder(simple.format(tempNotebook.getServiceCreated())); - StringBuilder serviceUpdated = new StringBuilder(simple.format(tempNotebook.getServiceUpdated())); - if (serviceUpdated.toString() == null) - serviceUpdated = serviceCreated; - query.bindValue(":serviceCreated", serviceCreated.toString()); - query.bindValue(":serviceUpdated", serviceCreated.toString()); - query.bindValue(":published",tempNotebook.isPublished()); - - if (isDirty) - query.bindValue(":isDirty", true); - else - query.bindValue(":isDirty", false); - query.bindValue(":autoEncrypt", false); - query.bindValue(":local", local); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Notebook Table insert failed."); - logger.log(logger.MEDIUM, query.lastError().toString()); - } - } - // Delete the notebook based on a guid - public void expungeNotebook(String guid, boolean needsSync) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("delete from Notebook " - +"where guid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "Notebook SQL delete prepare has failed."); - logger.log(logger.EXTREME, query.lastError().toString()); - } - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) - logger.log(logger.MEDIUM, "Notebook delete failed."); - - // Signal the parent that work needs to be done - if (needsSync) { - RDeletedTable deletedTable = new RDeletedTable(logger, db); - deletedTable.addDeletedItem(guid, "Notebook"); - } - } - // Update a notebook - public void updateNotebook(Notebook tempNotebook, boolean isDirty) { - boolean check; - - SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update Notebook set sequence=:sequence, name=:name, defaultNotebook=:defaultNotebook, " + - "serviceCreated=:serviceCreated, serviceUpdated=:serviceUpdated, "+ - "published=:published, isDirty=:isDirty where guid=:guid "); - query.bindValue(":sequence", tempNotebook.getUpdateSequenceNum()); - query.bindValue(":name", tempNotebook.getName()); - query.bindValue(":defaultNotebook", tempNotebook.isDefaultNotebook()); - - StringBuilder serviceCreated = new StringBuilder(simple.format(tempNotebook.getServiceCreated())); - StringBuilder serviceUpdated = new StringBuilder(simple.format(tempNotebook.getServiceUpdated())); - query.bindValue(":serviceCreated", serviceCreated.toString()); - query.bindValue(":serviceUpdated", serviceUpdated.toString()); - - query.bindValue(":published", tempNotebook.isPublished()); - query.bindValue(":isDirty", isDirty); - query.bindValue(":guid", tempNotebook.getGuid()); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Notebook Table update failed."); - logger.log(logger.MEDIUM, query.lastError().toString()); - } - } - // Load notebooks from the database - public List getAll() { - Notebook tempNotebook; - List index = new ArrayList(); - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, sequence, name, defaultNotebook, " + - "serviceCreated, "+ - "serviceUpdated, "+ - "published, defaultNotebook from Notebook order by name"); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); - while (query.next()) { - tempNotebook = new Notebook(); - tempNotebook.setGuid(query.valueString(0)); - int sequence = new Integer(query.valueString(1)).intValue(); - tempNotebook.setUpdateSequenceNum(sequence); - tempNotebook.setName(query.valueString(2)); - DateFormat indfm = null; - try { - indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); -// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); - } catch (Exception e) { } - try { - tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); - tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - tempNotebook.setPublished(new Boolean(query.valueString(6))); - tempNotebook.setDefaultNotebook(new Boolean(query.valueString(7))); - index.add(tempNotebook); - } - return index; - } - public List getAllLocal() { - Notebook tempNotebook; - List index = new ArrayList(); - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, sequence, name, defaultNotebook, " + - "serviceCreated, serviceUpdated, published from Notebook where local=true order by name"); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); - while (query.next()) { - tempNotebook = new Notebook(); - tempNotebook.setGuid(query.valueString(0)); - int sequence = new Integer(query.valueString(1)).intValue(); - tempNotebook.setUpdateSequenceNum(sequence); - tempNotebook.setName(query.valueString(2)); - - DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); -// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); - try { - tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); - tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - index.add(tempNotebook); - } - return index; - } - // Archive or un-archive a notebook - public void setArchived(String guid, boolean val) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update notebook set archived=:archived where guid=:guid"); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL archive update has failed."); - query.bindValue(":guid", guid); - query.bindValue(":archived", val); - query.exec(); - } - // Load non-archived notebooks from the database - public List getAllArchived() { - Notebook tempNotebook; - List index = new ArrayList(); - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, sequence, name, defaultNotebook, " + - "serviceCreated, serviceUpdated, published from Notebook where archived=true order by name"); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); - while (query.next()) { - tempNotebook = new Notebook(); - tempNotebook.setGuid(query.valueString(0)); - int sequence = new Integer(query.valueString(1)).intValue(); - tempNotebook.setUpdateSequenceNum(sequence); - tempNotebook.setName(query.valueString(2)); - - DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); -// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); - try { - tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); - tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - tempNotebook.setPublished(new Boolean(query.valueString(6))); - index.add(tempNotebook); - } - return index; - } - // Check for a local/remote notebook - public boolean isNotebookLocal(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Select local from Notebook where guid=:guid"); - query.bindValue(":guid", guid); - query.exec(); - if (!query.next()) { - return false; - } - boolean returnValue = false; - String returnVal = query.valueString(0); - if (returnVal.equals("false")) - returnValue = false; - else - returnValue = true; - return returnValue; - } - // Update a notebook sequence number - public void updateNotebookSequence(String guid, int sequence) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update Notebook set sequence=:sequence where guid=:guid"); - query.bindValue(":guid", guid); - query.bindValue(":sequence", sequence); - query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Notebook sequence update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Update a notebook GUID number - public void updateNotebookGuid(String oldGuid, String newGuid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Update Notebook set guid=:newGuid where guid=:oldGuid"); - query.bindValue(":oldGuid", oldGuid); - query.bindValue(":newGuid", newGuid); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Notebook guid update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - - // Update any notes containing the notebook guid - query.prepare("Update Note set notebookGuid=:newGuid where notebookGuid=:oldGuid"); - query.bindValue(":oldGuid", oldGuid); - query.bindValue(":newGuid", newGuid); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Notebook guid update for note failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - - // Update any watch folders with the new guid - query = new NSqlQuery(db.getConnection()); - query.prepare("Update WatchFolders set notebook=:newGuid where notebook=:oldGuid"); - query.bindValue(":oldGuid", oldGuid); - query.bindValue(":newGuid", newGuid); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Update WatchFolder notebook failed."); - logger.log(logger.MEDIUM, query.lastError().toString()); - } - } - // Get a list of notes that need to be updated - public List getDirty() { - Notebook tempNotebook; - List index = new ArrayList(); - boolean check; - - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, sequence, name, defaultNotebook, " + - "serviceCreated, serviceUpdated, published from Notebook where isDirty = true and local=false"); - if (!check) - logger.log(logger.EXTREME, "Notebook SQL retrieve has failed."); - while (query.next()) { - tempNotebook = new Notebook(); - tempNotebook.setGuid(query.valueString(0)); - int sequence = new Integer(query.valueString(1)).intValue(); - tempNotebook.setUpdateSequenceNum(sequence); - tempNotebook.setName(query.valueString(2)); - - DateFormat indfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); -// indfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); - try { - tempNotebook.setServiceCreated(indfm.parse(query.valueString(4)).getTime()); - tempNotebook.setServiceUpdated(indfm.parse(query.valueString(5)).getTime()); - } catch (ParseException e) { - e.printStackTrace(); - } - tempNotebook.setPublished(new Boolean(query.valueString(6))); - index.add(tempNotebook); - } - return index; - } - // This is a convience method to check if a tag exists & update/create based upon it - public void syncNotebook(Notebook notebook, boolean isDirty) { - if (!exists(notebook.getGuid())) { - addNotebook(notebook, isDirty, isDirty); - return; - } - updateNotebook(notebook, isDirty); - } - // does a record exist? - private boolean exists(String guid) { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Select guid from notebook where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "notebook SQL retrieve has failed."); - boolean retval = query.next(); - return retval; - } - // Reset the dirty flag. Typically done after a sync. - public void resetDirtyFlag(String guid) { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Update notebook set isdirty='false' where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Error resetting notebook dirty field."); - } - - - - - // does a record exist? - public String findNotebookByName(String newname) { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Select guid from notebook where name=:newname"); - query.bindValue(":newname", newname); - if (!query.exec()) - logger.log(logger.EXTREME, "notebook SQL retrieve has failed."); - String val = null; - if (query.next()) - val = query.valueString(0); - return val; - } - // Get a note tag counts - public List> getNotebookCounts() { - List> counts = new ArrayList>(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("select notebookGuid, count(guid) from note where active=1 group by notebookguid;")) { - logger.log(logger.EXTREME, "NoteTags SQL getTagCounts has failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - while (query.next()) { - Pair newCount = new Pair(); - newCount.setFirst(query.valueString(0)); - newCount.setSecond(query.valueInteger(1)); - counts.add(newCount); - } - return counts; - } - -} - diff --git a/src/cx/fbn/nevernote/sql/runners/RSavedSearchTable.java b/src/cx/fbn/nevernote/sql/runners/RSavedSearchTable.java deleted file mode 100644 index 5e20bd2..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RSavedSearchTable.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.QueryFormat; -import com.evernote.edam.type.SavedSearch; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; - -public class RSavedSearchTable { - private final ApplicationLogger logger; - private final RDatabaseConnection db; - - - // Constructor - public RSavedSearchTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table SavedSearch..."); - if (!query.exec("Create table SavedSearch (guid varchar primary key, " + - "name varchar, query varchar, format integer, sequence integer, isDirty boolean)")) - logger.log(logger.HIGH, "Table SavedSearch creation FAILED!!!"); - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table SavedSearch"); - } - // get all tags - public List getAll() { - SavedSearch tempSearch; - List index = new ArrayList(); - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, name, query, format, sequence" - +" from SavedSearch"); - if (!check) - logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in getAll()."); - while (query.next()) { - tempSearch = new SavedSearch(); - tempSearch.setGuid(query.valueString(0)); - tempSearch.setName(query.valueString(1)); - tempSearch.setQuery(query.valueString(2)); - int fmt = new Integer(query.valueString(3)); - if (fmt == 1) - tempSearch.setFormat(QueryFormat.USER); - else - tempSearch.setFormat(QueryFormat.SEXP); - int sequence = new Integer(query.valueString(4)).intValue(); - tempSearch.setUpdateSequenceNum(sequence); - index.add(tempSearch); - } - return index; - } - public SavedSearch getSavedSearch(String guid) { - SavedSearch tempSearch = null; - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("Select guid, name, query, format, sequence" - +" from SavedSearch where guid=:guid"); - if (!check) - logger.log(logger.EXTREME, "SavedSearch SQL prepare has failed in getSavedSearch."); - query.bindValue(":guid", guid); - query.exec(); - if (!check) - logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in getSavedSearch."); - if (query.next()) { - tempSearch = new SavedSearch(); - tempSearch.setGuid(query.valueString(0)); - tempSearch.setName(query.valueString(1)); - tempSearch.setQuery(query.valueString(2)); - int fmt = new Integer(query.valueString(3)); - if (fmt == 1) - tempSearch.setFormat(QueryFormat.USER); - else - tempSearch.setFormat(QueryFormat.SEXP); - int sequence = new Integer(query.valueString(4)).intValue(); - tempSearch.setUpdateSequenceNum(sequence); - } - return tempSearch; - } - // Update a tag - public void updateSavedSearch(SavedSearch search, boolean isDirty) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update SavedSearch set sequence=:sequence, "+ - "name=:name, isDirty=:isDirty, query=:query, format=:format " - +"where guid=:guid"); - - if (!check) { - logger.log(logger.EXTREME, "SavedSearch SQL update prepare has failed."); - logger.log(logger.EXTREME, query.lastError().toString()); - } - query.bindValue(":sequence", search.getUpdateSequenceNum()); - query.bindValue(":name", search.getName()); - query.bindValue(":isDirty", isDirty); - query.bindValue(":query", search.getQuery()); - if (search.getFormat() == QueryFormat.USER) - query.bindValue(":format", 1); - else - query.bindValue(":format", 2); - - query.bindValue(":guid", search.getGuid()); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Tag Table update failed."); - logger.log(logger.EXTREME, query.lastError().toString()); - } - } - // Delete a tag - public void expungeSavedSearch(String guid, boolean needsSync) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("delete from SavedSearch " - +"where guid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "SavedSearch SQL delete prepare has failed."); - logger.log(logger.EXTREME, query.lastError().toString()); - } - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Saved Search delete failed."); - logger.log(logger.EXTREME, query.lastError().toString()); - } - - // Add the work to the parent queue - if (needsSync) { - RDeletedTable del = new RDeletedTable(logger, db); - del.addDeletedItem(guid, "SavedSearch"); - } - } - // Save a tag - public void addSavedSearch(SavedSearch search, boolean isDirty) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Insert Into SavedSearch (guid, query, sequence, format, name, isDirty)" - +" Values(:guid, :query, :sequence, :format, :name, :isDirty)"); - if (!check) { - logger.log(logger.EXTREME, "Search SQL insert prepare has failed."); - logger.log(logger.EXTREME, query.lastError().toString()); - } - query.bindValue(":guid", search.getGuid()); - query.bindValue(":query", search.getQuery()); - query.bindValue(":sequence", search.getUpdateSequenceNum()); - if (search.getFormat() == QueryFormat.USER) - query.bindValue(":format", 1); - else - query.bindValue(":format", 2); - query.bindValue(":name", search.getName()); - query.bindValue(":isDirty", isDirty); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Search Table insert failed."); - logger.log(logger.MEDIUM, query.lastError().toString()); - } - } - // Update a tag sequence number - public void updateSavedSearchSequence(String guid, int sequence) { - boolean check; - ; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update SavedSearch set sequence=:sequence where guid=:guid"); - query.bindValue(":sequence", sequence); - query.bindValue(":guid", guid); - query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "SavedSearch sequence update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Update a tag sequence number - public void updateSavedSearchGuid(String oldGuid, String newGuid) { - boolean check; - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update SavedSearch set guid=:newGuid where guid=:oldGuid"); - query.bindValue(":newGuid", newGuid); - query.bindValue(":oldGuid", oldGuid); - query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "SavedSearch guid update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Get dirty tags - public List getDirty() { - SavedSearch search; - List index = new ArrayList(); - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, query, sequence, name, format" - +" from SavedSearch where isDirty = true"); - if (!check) - logger.log(logger.EXTREME, "SavedSearch getDirty prepare has failed."); - while (query.next()) { - search = new SavedSearch(); - search.setGuid(query.valueString(0)); - search.setQuery(query.valueString(1)); - int sequence = new Integer(query.valueString(2)).intValue(); - search.setUpdateSequenceNum(sequence); - search.setName(query.valueString(3)); - int fmt = new Integer(query.valueString(4)).intValue(); - if (fmt == 1) - search.setFormat(QueryFormat.USER); - else - search.setFormat(QueryFormat.SEXP); - index.add(search); - } - return index; - } - // Find a guid based upon the name - public String findSavedSearchByName(String name) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Select guid from SavedSearch where name=:name"); - query.bindValue(":name", name); - if (!query.exec()) - logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in findSavedSearchByName()."); - String val = null; - if (query.next()) - val = query.valueString(0); - return val; - } - // given a guid, does the tag exist - public boolean exists(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select guid from SavedSearch where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "SavedSearch SQL retrieve has failed in exists()."); - boolean retval = query.next(); - return retval; - } - // This is a convience method to check if a tag exists & update/create based upon it - public void syncSavedSearch(SavedSearch search, boolean isDirty) { - if (exists(search.getGuid())) - updateSavedSearch(search, isDirty); - else - addSavedSearch(search, isDirty); - } - public void resetDirtyFlag(String guid) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Update SavedSearch set isdirty=false where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Error resetting SavedSearch dirty field in resetDirtyFlag()."); - } -} diff --git a/src/cx/fbn/nevernote/sql/runners/RSyncTable.java b/src/cx/fbn/nevernote/sql/runners/RSyncTable.java deleted file mode 100644 index 184dde2..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RSyncTable.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; -import cx.fbn.nevernote.utilities.ListManager; - -public class RSyncTable { - ListManager parent; - private final ApplicationLogger logger; - private final RDatabaseConnection db; - - - // Constructor - public RSyncTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table Sync..."); - if (!query.exec("Create table Sync (key varchar primary key, value varchar);")) - logger.log(logger.HIGH, "Table Sync creation FAILED!!!"); - addRecord("LastSequenceDate","0"); - addRecord("UpdateSequenceNumber", "0"); - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table Sync"); - } - // Add an item to the table - public void addRecord(String key, String value) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Insert Into Sync (key, value) values (:key, :value);"); - query.bindValue(":key", key); - query.bindValue(":value", value); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Add to into Sync failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Set a key field - public String getRecord(String key) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select value from Sync where key=:key"); - query.bindValue(":key", key); - if (!query.exec()) { - logger.log(logger.MEDIUM, "getRecord from sync failed."); - logger.log(logger.MEDIUM, query.lastError()); - return null; - } - if (query.next()) { - return (query.valueString(0)); - } - return null; - } - // Set a key field - public void setRecord(String key, String value) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Update Sync set value=:value where key=:key"); - query.bindValue(":key", key); - query.bindValue(":value", value); - if (!query.exec()) { - logger.log(logger.MEDIUM, "setRecord from sync failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - return; - } - - - - -} diff --git a/src/cx/fbn/nevernote/sql/runners/RTagTable.java b/src/cx/fbn/nevernote/sql/runners/RTagTable.java deleted file mode 100644 index 5824cf6..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RTagTable.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import java.util.ArrayList; -import java.util.List; - -import com.evernote.edam.type.Tag; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; - -public class RTagTable { - private final ApplicationLogger logger; - RDatabaseConnection db; - - public RTagTable (ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table Tag..."); - if (!query.exec("Create table Tag (guid varchar primary key, " + - "parentGuid varchar, sequence integer, hashCode integer, name varchar, isDirty boolean)")) - logger.log(logger.HIGH, "Table TAG creation FAILED!!!"); - - } - // Drop the table - public void dropTable() { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table Tag"); - - } - // get all tags - public List getAll() { - - Tag tempTag; - List index = new ArrayList(); - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, parentGuid, sequence, name" - +" from Tag"); - if (!check) { - logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); - logger.log(logger.EXTREME, query.lastError()); - } - while (query.next()) { - tempTag = new Tag(); - tempTag.setGuid(query.valueString(0)); - if (query.valueString(1) != null) - tempTag.setParentGuid(query.valueString(1)); - else - tempTag.setParentGuid(null); - int sequence = new Integer(query.valueString(2)).intValue(); - tempTag.setUpdateSequenceNum(sequence); - tempTag.setName(query.valueString(3)); - index.add(tempTag); - } - - return index; - } - public Tag getTag(String guid) { - Tag tempTag = new Tag(); - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - if (!query.prepare("Select guid, parentGuid, sequence, name" - +" from Tag where guid=:guid")) - logger.log(logger.EXTREME, "Tag select by guid SQL prepare has failed."); - - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Tag select by guid SQL exec has failed."); - - if (!query.next()) { - return tempTag; - } - tempTag.setGuid(query.valueString(0)); - tempTag.setParentGuid(query.valueString(1)); - int sequence = new Integer(query.valueString(2)).intValue(); - tempTag.setUpdateSequenceNum(sequence); - tempTag.setName(query.valueString(3)); - return tempTag; - } - // Update a tag - public void updateTag(Tag tempTag, boolean isDirty) { - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update Tag set parentGuid=:parentGuid, sequence=:sequence, "+ - "hashCode=:hashCode, name=:name, isDirty=:isDirty " - +"where guid=:guid"); - - if (!check) { - logger.log(logger.EXTREME, "Tag SQL update prepare has failed."); - logger.log(logger.EXTREME, query.lastError()); - } - query.bindValue(":parentGuid", tempTag.getParentGuid()); - query.bindValue(":sequence", tempTag.getUpdateSequenceNum()); - query.bindValue(":hashCode", tempTag.hashCode()); - query.bindValue(":name", tempTag.getName()); - query.bindValue(":isDirty", isDirty); - query.bindValue(":guid", tempTag.getGuid()); - - check = query.exec(); - if (!check) - logger.log(logger.MEDIUM, "Tag Table update failed."); - - } - // Delete a tag - public void expungeTag(String guid, boolean needsSync) { - boolean check; - - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.prepare("delete from Tag " - +"where guid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "Tag SQL delete prepare has failed."); - logger.log(logger.EXTREME, query.lastError()); - } - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) - logger.log(logger.MEDIUM, "Tag delete failed."); - - check = query.prepare("delete from NoteTags " - +"where tagGuid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "NoteTags SQL delete prepare has failed."); - logger.log(logger.EXTREME, query.lastError()); - } - - query.bindValue(":guid", guid); - check = query.exec(); - if (!check) - logger.log(logger.MEDIUM, "NoteTags delete failed."); - - // Add the work to the parent queue - if (needsSync) { - RDeletedTable del = new RDeletedTable(logger, db); - del.addDeletedItem(guid, "Tag"); - } - } - // Save a tag - public void addTag(Tag tempTag, boolean isDirty) { - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Insert Into Tag (guid, parentGuid, sequence, hashCode, name, isDirty)" - +" Values(:guid, :parentGuid, :sequence, :hashCode, :name, :isDirty)"); - if (!check) { - logger.log(logger.EXTREME, "Tag SQL insert prepare has failed."); - logger.log(logger.EXTREME, query.lastError()); - } - query.bindValue(":guid", tempTag.getGuid()); - query.bindValue(":parentGuid", tempTag.getParentGuid()); - query.bindValue(":sequence", tempTag.getUpdateSequenceNum()); - query.bindValue(":hashCode", tempTag.hashCode()); - query.bindValue(":name", tempTag.getName()); - query.bindValue(":isDirty", isDirty); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Tag Table insert failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - // Update a tag's parent - public void updateTagParent(String guid, String parentGuid) { - boolean check; - - NSqlQuery query = new NSqlQuery(db.getConnection()); - check = query.prepare("Update Tag set parentGuid=:parentGuid where guid=:guid"); - if (!check) { - logger.log(logger.EXTREME, "Tag SQL tag parent update prepare has failed."); - logger.log(logger.EXTREME, query.lastError()); - } - - query.bindValue(":parentGuid", parentGuid); - query.bindValue(":guid", guid); - - check = query.exec(); - if (!check) { - logger.log(logger.MEDIUM, "Tag parent update failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - //Save tags from Evernote - public void saveTags(List tags) { - Tag tempTag; - for (int i=0; i getDirty() { - Tag tempTag; - List index = new ArrayList(); - boolean check; - - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - check = query.exec("Select guid, parentGuid, sequence, name" - +" from Tag where isDirty = true"); - if (!check) - logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); - while (query.next()) { - tempTag = new Tag(); - tempTag.setGuid(query.valueString(0)); - tempTag.setParentGuid(query.valueString(1)); - int sequence = new Integer(query.valueString(2)).intValue(); - tempTag.setUpdateSequenceNum(sequence); - tempTag.setName(query.valueString(3)); - if (tempTag.getParentGuid() != null && tempTag.getParentGuid().equals("")) - tempTag.setParentGuid(null); - index.add(tempTag); - } - return index; - } - // Find a guid based upon the name - public String findTagByName(String name) { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Select guid from tag where name=:name"); - query.bindValue(":name", name); - if (!query.exec()) - logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); - String val = null; - if (query.next()) - val = query.valueString(0); - return val; - } - // given a guid, does the tag exist - public boolean exists(String guid) { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Select guid from tag where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Tag SQL retrieve has failed."); - boolean retval = query.next(); - return retval; - } - // This is a convience method to check if a tag exists & update/create based upon it - public void syncTag(Tag tag, boolean isDirty) { - if (exists(tag.getGuid())) - updateTag(tag, isDirty); - else - addTag(tag, isDirty); - } - public void resetDirtyFlag(String guid) { - - NSqlQuery query = new NSqlQuery(db.getConnection()); - - query.prepare("Update tag set isdirty=false where guid=:guid"); - query.bindValue(":guid", guid); - if (!query.exec()) - logger.log(logger.EXTREME, "Error resetting tag dirty field."); - } -} diff --git a/src/cx/fbn/nevernote/sql/runners/RWatchFolderTable.java b/src/cx/fbn/nevernote/sql/runners/RWatchFolderTable.java deleted file mode 100644 index 7eb169c..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RWatchFolderTable.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import java.util.ArrayList; -import java.util.List; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; -import cx.fbn.nevernote.utilities.ListManager; - -public class RWatchFolderTable { - ListManager parent; - private final ApplicationLogger logger; - private final RDatabaseConnection db; - - - // Constructor - public RWatchFolderTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table WatchFolder..."); - if (!query.exec("Create table WatchFolders (folder varchar primary key, notebook varchar," + - "keep boolean, depth integer)")); - logger.log(logger.HIGH, "Table WatchFolders creation FAILED!!!"); - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Drop table WatchFolders"); - } - // Add an folder - public void addWatchFolder(String folder, String notebook, boolean keep, int depth) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Insert Into WatchFolders (folder, notebook, keep, depth) " + - "values (:folder, :notebook, :keep, :depth)"); - query.bindValue(":folder", folder); - query.bindValue(":notebook", notebook); - query.bindValue(":keep", keep); - query.bindValue(":depth", depth); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Insert into WatchFolder failed."); - } - } - // remove an folder - public void expungeWatchFolder(String folder) { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("delete from WatchFolders where folder=:folder"); - query.bindValue(":folder", folder); - if (!query.exec()) { - logger.log(logger.MEDIUM, "Expunge WatchFolder failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - public void expungeAll() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - if (!query.exec("delete from WatchFolders")) { - logger.log(logger.MEDIUM, "Expunge all WatchFolder failed."); - logger.log(logger.MEDIUM, query.lastError()); - } - } - public List getAll() { - logger.log(logger.HIGH, "Entering RWatchFolders.getAll"); - - List list = new ArrayList(); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("Select folder, (select name from notebook where guid = notebook), keep, depth from WatchFolders"); - while (query.next()) { - WatchFolderRecord record = new WatchFolderRecord(); - record.folder = query.valueString(0); - record.notebook = query.valueString(1); - record.keep = new Boolean(query.valueString(2)); - record.depth = new Integer(query.valueString(3)); - list.add(record); - } - logger.log(logger.HIGH, "Leaving RWatchFolders.getAll"); - return list; - - } - - public String getNotebook(String dir) { - logger.log(logger.HIGH, "Entering RWatchFolders.getNotebook"); - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.prepare("Select notebook from WatchFolders where folder=:dir"); - query.bindValue(":dir", dir); - query.exec(); - String response = null; - while (query.next()) { - response = query.valueString(0); - } - logger.log(logger.HIGH, "Leaving RWatchFolders.getNotebook"); - return response; - - } -} diff --git a/src/cx/fbn/nevernote/sql/runners/RWordsTable.java b/src/cx/fbn/nevernote/sql/runners/RWordsTable.java deleted file mode 100644 index 1608956..0000000 --- a/src/cx/fbn/nevernote/sql/runners/RWordsTable.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - - -package cx.fbn.nevernote.sql.runners; - -import cx.fbn.nevernote.sql.driver.NSqlQuery; -import cx.fbn.nevernote.utilities.ApplicationLogger; - -public class RWordsTable { - private final ApplicationLogger logger; - private final RDatabaseConnection db; - - - // Constructor - public RWordsTable(ApplicationLogger l, RDatabaseConnection d) { - logger = l; - db = d; - } - // Create the table - public void createTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "Creating table WORDS ..."); - if (!query.exec("create table words (word varchar, guid varchar, source varchar, weight int, primary key (word, guid, source));")) { - logger.log(logger.HIGH, "Table WORDS creation FAILED!!!"); - logger.log(logger.HIGH, query.lastError()); - } - } - // Drop the table - public void dropTable() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("drop table words"); - } - // Count unindexed notes - public int getWordCount() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - query.exec("select count(*) from words"); - query.next(); - int returnValue = new Integer(query.valueString(0)); - return returnValue; - } - - // Clear out the word index table - public void clearWordIndex() { - NSqlQuery query = new NSqlQuery(db.getConnection()); - logger.log(logger.HIGH, "DELETE FROM WORDS"); - - boolean check = query.exec("DELETE FROM WORDS"); - if (!check) - logger.log(logger.HIGH, "Table WORDS clear has FAILED!!!"); - } - - //******************************************************************************** - //******************************************************************************** - //* Support adding & deleting index words - //******************************************************************************** - //******************************************************************************** - public void expungeFromWordIndex(String guid, String type) { - NSqlQuery deleteWords = new NSqlQuery(db.getConnection()); - if (!deleteWords.prepare("delete from words where guid=:guid and source=:source")) { - logger.log(logger.EXTREME, "Note SQL select prepare deleteWords has failed."); - logger.log(logger.MEDIUM, deleteWords.lastError()); - } - - deleteWords.bindValue(":guid", guid); - deleteWords.bindValue(":source", type); - deleteWords.exec(); - - } - // Reindex a note - public synchronized void addWordToNoteIndex(String guid, String word, String type, Integer weight) { - NSqlQuery findWords = new NSqlQuery(db.getConnection()); - if (!findWords.prepare("Select weight from words where guid=:guid and source=:type and word=:word")) { - logger.log(logger.MEDIUM, "Prepare failed in addWordToNoteIndex()"); - logger.log(logger.MEDIUM, findWords.lastError()); - } - - findWords.bindValue(":guid", guid); - findWords.bindValue(":type", type); - findWords.bindValue(":word", word); - - boolean addNeeded = true; - findWords.exec(); - // If we have a match, find out which has the heigher weight & update accordingly - if (findWords.next()) { - int recordWeight = new Integer(findWords.valueString(0)); - addNeeded = false; - if (recordWeight < weight) { - NSqlQuery updateWord = new NSqlQuery(db.getConnection()); - if (!updateWord.prepare("Update words set weight=:weight where guid=:guid and source=:type and word=:word")) { - logger.log(logger.MEDIUM, "Prepare failed for find words in addWordToNoteIndex()"); - logger.log(logger.MEDIUM, findWords.lastError()); - } - - updateWord.bindValue(":weight", weight); - updateWord.bindValue(":guid", guid); - updateWord.bindValue(":type", type); - updateWord.bindValue(":word",word); - updateWord.exec(); - } - } - - - if (!addNeeded) - return; - - NSqlQuery insertWords = new NSqlQuery(db.getConnection()); - if (!insertWords.prepare("Insert Into Words (word, guid, weight, source)" - +" Values(:word, :guid, :weight, :type )")) { - logger.log(logger.EXTREME, "Note SQL select prepare checkWords has failed."); - logger.log(logger.MEDIUM, insertWords.lastError()); - } - insertWords.bindValue(":word", word); - insertWords.bindValue(":guid", guid); - insertWords.bindValue(":weight", weight); - insertWords.bindValue(":type", type); - insertWords.exec(); - } - - -} diff --git a/src/cx/fbn/nevernote/threads/CounterRunner.java b/src/cx/fbn/nevernote/threads/CounterRunner.java index 6ad627d..6e1b81c 100644 --- a/src/cx/fbn/nevernote/threads/CounterRunner.java +++ b/src/cx/fbn/nevernote/threads/CounterRunner.java @@ -37,7 +37,6 @@ import cx.fbn.nevernote.signals.NotebookSignal; import cx.fbn.nevernote.signals.TagSignal; import cx.fbn.nevernote.signals.TrashSignal; import cx.fbn.nevernote.sql.DatabaseConnection; -import cx.fbn.nevernote.sql.runners.NoteTagsRecord; import cx.fbn.nevernote.utilities.ApplicationLogger; import cx.fbn.nevernote.utilities.Pair; @@ -64,17 +63,21 @@ public class CounterRunner extends QObject implements Runnable { public boolean ready = false; public boolean abortCount = false; + private final DatabaseConnection conn; + private volatile LinkedBlockingQueue readyQueue = new LinkedBlockingQueue(); //********************************************* //* Constructor * //********************************************* - public CounterRunner(String logname, int t) { + public CounterRunner(String logname, int t, String u, String uid, String pswd, String cpswd) { type = t; + threadLock = new QMutex(); logger = new ApplicationLogger(logname); // setAutoDelete(false); + conn = new DatabaseConnection(logger, u, uid, pswd, cpswd); keepRunning = true; notebookSignal = new NotebookSignal(); tagSignal = new TagSignal(); @@ -116,6 +119,7 @@ public class CounterRunner extends QObject implements Runnable { threadLock.unlock(); } catch (InterruptedException e) {} } + conn.dbShutdown(); } @@ -166,7 +170,6 @@ public class CounterRunner extends QObject implements Runnable { logger.log(logger.EXTREME, "Entering ListManager.countNotebookResults"); if (abortCount) return; - DatabaseConnection conn = new DatabaseConnection(Global.tagCounterThreadId); List nCounter = new ArrayList(); if (abortCount) return; @@ -245,7 +248,6 @@ public class CounterRunner extends QObject implements Runnable { private void countTagResults() { logger.log(logger.EXTREME, "Entering ListManager.countTagResults"); - DatabaseConnection conn = new DatabaseConnection(Global.tagCounterThreadId); List counter = new ArrayList(); List allTags = conn.getTagTable().getAll(); @@ -287,7 +289,7 @@ public class CounterRunner extends QObject implements Runnable { if (abortCount) return; - List tags = conn.getNoteTable().noteTagsTable.getAllNoteTags(); + List tags = conn.getNoteTable().noteTagsTable.getAllNoteTags(); for (int i=noteIndex.size()-1; i>=0; i--) { if (abortCount) return; @@ -315,7 +317,6 @@ public class CounterRunner extends QObject implements Runnable { private void countTrashResults() { logger.log(logger.EXTREME, "Entering CounterRunner.countTrashResults()"); - DatabaseConnection conn = new DatabaseConnection(Global.trashCounterThreadId); if (abortCount) return; diff --git a/src/cx/fbn/nevernote/threads/DBRunner.java b/src/cx/fbn/nevernote/threads/DBRunner.java deleted file mode 100644 index 58b3df1..0000000 --- a/src/cx/fbn/nevernote/threads/DBRunner.java +++ /dev/null @@ -1,936 +0,0 @@ -/* - * This file is part of NeverNote - * Copyright 2009 Randy Baumgarte - * - * This file may be licensed under the terms of of the - * GNU General Public License Version 2 (the ``GPL''). - * - * Software distributed under the License is distributed - * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either - * express or implied. See the GPL for the specific language - * governing rights and limitations. - * - * You should have received a copy of the GPL along with this - * program. If not, go to http://www.gnu.org/licenses/gpl.html - * or write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * -*/ - -package cx.fbn.nevernote.threads; - -import java.util.Vector; -import java.util.concurrent.LinkedBlockingQueue; - -import com.trolltech.qt.core.QObject; - -import cx.fbn.nevernote.Global; -import cx.fbn.nevernote.config.InitializationException; -import cx.fbn.nevernote.signals.DBRunnerSignal; -import cx.fbn.nevernote.sql.requests.DBRunnerRequest; -import cx.fbn.nevernote.sql.requests.DatabaseRequest; -import cx.fbn.nevernote.sql.requests.DeletedItemRequest; -import cx.fbn.nevernote.sql.requests.EnSearchRequest; -import cx.fbn.nevernote.sql.requests.InvalidXMLRequest; -import cx.fbn.nevernote.sql.requests.NoteRequest; -import cx.fbn.nevernote.sql.requests.NoteTagsRequest; -import cx.fbn.nevernote.sql.requests.NotebookRequest; -import cx.fbn.nevernote.sql.requests.ResourceRequest; -import cx.fbn.nevernote.sql.requests.SavedSearchRequest; -import cx.fbn.nevernote.sql.requests.SyncRequest; -import cx.fbn.nevernote.sql.requests.TagRequest; -import cx.fbn.nevernote.sql.requests.WatchFolderRequest; -import cx.fbn.nevernote.sql.requests.WordRequest; -import cx.fbn.nevernote.sql.runners.RDatabaseConnection; -import cx.fbn.nevernote.sql.runners.REnSearch; -import cx.fbn.nevernote.utilities.ApplicationLogger; - -public class DBRunner extends QObject implements Runnable { - private final ApplicationLogger logger; - - private final RDatabaseConnection conn; - - // NFC TODO: why do these need to be volatile? - private volatile LinkedBlockingQueue workQueue; - - public volatile Vector genericResponse; - public volatile Vector deletedItemResponse; - public volatile Vector notebookResponse; - public volatile Vector tagResponse; - public volatile Vector savedSearchResponse; - public volatile Vector noteResponse; - public volatile Vector resourceResponse; - public volatile Vector noteTagsResponse; - public volatile Vector enSearchResponse; - public volatile Vector watchFolderResponse; - public volatile Vector wordResponse; - public volatile Vector invalidXMLResponse; - public volatile Vector syncResponse; - - // priority queues - public volatile Vector user; - public volatile Vector background; - public volatile Vector discretionary; - private static int MAX_EMPTY_QUEUE_COUNT = 100; - private static int MAX_QUEUED_WAITING = 100; - - - public DBRunnerSignal dbSignal; - public boolean keepRunning; - private final String url; - private final String cypherPassword; - private final String userid; - private final String userPassword; - - /** - * @throws InitializationException when opening the database fails - */ - public DBRunner(String u, String id, String pass, String cypher) throws InitializationException { - workQueue=new LinkedBlockingQueue(MAX_QUEUED_WAITING); - - url=u; - userid = id; - userPassword = pass; - cypherPassword=cypher; - - //*********************************************** - //* These are the priority queues. - //*********************************************** - user = new Vector(); - background = new Vector(); - discretionary = new Vector(); - - //*********************************************** - //* These are database response queues. Each - //* thread has its own individual queue and - //* will access it based upod it's individual - //* thread ID - //*********************************************** - - genericResponse = new Vector(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i(); - for (int i=0; i 0 || background.size() > 0 || discretionary.size() > 0) { - if (workQueue.size() > 0) - emptyQueue(); - if (user.size() > 0) - releaseWork(user); - else - if (background.size() > 0) - releaseWork(background); - else - releaseWork(discretionary); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - return; - } - - @SuppressWarnings("unused") - private void releaseThread() { - Global.dbContinue(); - } - - private void emptyQueue() { - logger.log(logger.EXTREME, "Draining queue. Current size " +workQueue.size()); - int count = 0; - while (workQueue.size() > 0 && count > MAX_EMPTY_QUEUE_COUNT) { - DBRunnerRequest request; - try { - request = workQueue.take(); - prioritizeWork(request); - } catch (InterruptedException e) {} - count++; - logger.log(logger.EXTREME, "Draining queue - counter is now " +count); - } - } - - private void releaseWork(Vector queue) { - logger.log(logger.EXTREME, "Releasing work. Current size is " +workQueue.size()); - while(queue.size() > 0) { - DBRunnerRequest request = queue.get(0); - queue.remove(0); - doWork(request); - } - logger.log(logger.EXTREME, "Leaving release queue."); - } - - private void prioritizeWork(DBRunnerRequest request) { - logger.log(logger.EXTREME, "Entering prioritizeWork()"); - if (request.requestor_id == Global.mainThreadId || - (request.category == DBRunnerRequest.NOTE) && (request.type == NoteRequest.Set_Index_Needed)) { - user.add(request); - return; - } - if (request.requestor_id == Global.tagCounterThreadId) { - background.add(request); - return; - } - if (request.requestor_id == Global.trashCounterThreadId) { - discretionary.add(request); - return; - } - - // Anything remaining should be index threads, put - // them in a low priority - discretionary.add(request); - logger.log(logger.EXTREME, "Leaving prioritizeWork()"); - } - - - public synchronized void addWork(DBRunnerRequest s) { - while(!workQueue.offer(s)); - } - - private void doWork(DBRunnerRequest s) { - if (s.category == DBRunnerRequest.GENERIC){ - String work = new String(s.request); - if (work.equalsIgnoreCase("shutdown")) - keepRunning = false; - } else if (s.category == DBRunnerRequest.DATABASE){ - DatabaseRequest work = copyDatabaseRequest(s); - doDatabaseRequest(work); - } else if (s.category == DBRunnerRequest.DELETED_ITEM) { - DeletedItemRequest work = copyDeletedItemRequest(s); - Global.dbrunnerWorkLock.unlock(); - doDeletedItemRequest(work); - } else if (s.category == DBRunnerRequest.NOTEBOOK) { - NotebookRequest work = copyNotebookRequest(s); - doNotebookRequest(work); - } else if (s.category == DBRunnerRequest.TAG ) { - TagRequest work = copyTagRequest(s); - doTagRequest(work); - } else if (s.category == DBRunnerRequest.SAVED_SEARCH ) { - SavedSearchRequest work = copySavedSearchRequest(s); - doSavedSearchRequest(work); - } else if (s.category == DBRunnerRequest.NOTE) { - NoteRequest work = copyNoteRequest(s); - doNoteRequest(work); - } else if (s.category == DBRunnerRequest.RESOURCE) { - ResourceRequest work = copyResourceRequest(s); - doResourceRequest(work); - } else if (s.category == DBRunnerRequest.NOTE_TAGS) { - NoteTagsRequest work = copyNoteTagsRequest(s); - doNoteTagsRequest(work); - } else if (s.category == DBRunnerRequest.ENSEARCH) { - EnSearchRequest work = copyEnSearchRequest(s); - doEnSearchRequest(work); - } else if (s.category == DBRunnerRequest.WATCH_FOLDER) { - WatchFolderRequest work = copyWatchFolderRequest(s); - doWatchFolderRequest(work); - } else if (s.category == DBRunnerRequest.WORD) { - WordRequest work = copyWordRequest(s); - doWordRequest(work); - } else if (s.category == DBRunnerRequest.Invalid_XML) { - InvalidXMLRequest work = copyInvalidXMLRequest(s); - doInvalidXMLRequest(work); - } else if (s.category == DBRunnerRequest.Sync) { - SyncRequest work = copySyncRequest(s); - doSyncRequest(work); - } - return; - } - - - //********************************************* - //* If the requestor is expecting a response, * - //* release them so they can pick it up. * - //********************************************* - private void release(int i) { - logger.log(logger.EXTREME, "Releasing "+i); - Global.dbClientContinue(i); -// if (i == Global.mainThreadId) -// Global.mainThreadWait.wakeOne(); - } - - //************************************** - //* Copy items off the work queue so * - //* the resources can be freed up. * - //************************************** - private DatabaseRequest copyDatabaseRequest(DBRunnerRequest n) { - DatabaseRequest request = new DatabaseRequest(); - DatabaseRequest old = (DatabaseRequest) n; - request = old.copy(); - return request; - } - private SyncRequest copySyncRequest(DBRunnerRequest n) { - SyncRequest request = new SyncRequest(); - SyncRequest old = (SyncRequest) n; - request = old.copy(); - return request; - } - private DeletedItemRequest copyDeletedItemRequest(DBRunnerRequest r) { - DeletedItemRequest request = new DeletedItemRequest(); - DeletedItemRequest old = (DeletedItemRequest) r; - request = old.copy(); - return request; - } - private NotebookRequest copyNotebookRequest(DBRunnerRequest n) { - NotebookRequest request = new NotebookRequest(); - NotebookRequest old = (NotebookRequest) n; - request = old.copy(); - return request; - } - private TagRequest copyTagRequest(DBRunnerRequest n) { - TagRequest request = new TagRequest(); - TagRequest old = (TagRequest) n; - request = old.copy(); - return request; - } - private SavedSearchRequest copySavedSearchRequest(DBRunnerRequest n) { - SavedSearchRequest request = new SavedSearchRequest(); - SavedSearchRequest old = (SavedSearchRequest) n; - request = old.copy(); - return request; - } - private NoteRequest copyNoteRequest(DBRunnerRequest n) { - NoteRequest request = new NoteRequest(); - NoteRequest old = (NoteRequest) n; - request = old.copy(); - return request; - } - private ResourceRequest copyResourceRequest(DBRunnerRequest n) { - ResourceRequest request = new ResourceRequest(); - ResourceRequest old = (ResourceRequest) n; - request = old.copy(); - return request; - } - private NoteTagsRequest copyNoteTagsRequest(DBRunnerRequest n) { - NoteTagsRequest request = new NoteTagsRequest(); - NoteTagsRequest old = (NoteTagsRequest) n; - request = old.copy(); - return request; - } - private EnSearchRequest copyEnSearchRequest(DBRunnerRequest n) { - EnSearchRequest request = new EnSearchRequest(); - EnSearchRequest old = (EnSearchRequest) n; - request = old.copy(); - return request; - } - private WatchFolderRequest copyWatchFolderRequest(DBRunnerRequest n) { - WatchFolderRequest request = new WatchFolderRequest(); - WatchFolderRequest old = (WatchFolderRequest) n; - request = old.copy(); - return request; - } - private WordRequest copyWordRequest(DBRunnerRequest n) { - WordRequest request = new WordRequest(); - WordRequest old = (WordRequest) n; - request = old.copy(); - return request; - } - private InvalidXMLRequest copyInvalidXMLRequest(DBRunnerRequest n) { - InvalidXMLRequest request = new InvalidXMLRequest(); - InvalidXMLRequest old = (InvalidXMLRequest) n; - request = old.copy(); - return request; - } - //************************************** - //* Database requests * - //************************************** - private void doDatabaseRequest(DatabaseRequest r) { - if (r.type == DatabaseRequest.Create_Tables) { - conn.createTables(); - release(r.requestor_id); - return; - } else if (r.type == DatabaseRequest.Drop_Tables) { - conn.dropTables(); - return; - } else if (r.type == DatabaseRequest.Compact) { - conn.compactDatabase(); - release(r.requestor_id); - return; - } else if (r.type == DatabaseRequest.Shutdown) { - conn.dbShutdown(); - keepRunning = false; - return; - } else if (r.type == DatabaseRequest.Execute_Sql) { - conn.executeSql(r.string1); - release(r.requestor_id); - return; - } else if (r.type == DatabaseRequest.Execute_Sql_Index) { - release(r.requestor_id); - return; - } else if (r.type == DatabaseRequest.Backup_Database) { - conn.backupDatabase(); - release(r.requestor_id); - return; - } - return; - } - - //************************************** - //* Notebook database requests * - //************************************** - private void doNotebookRequest(NotebookRequest n) { - logger.log(logger.EXTREME, "Notebook request: " +n.category + " " + n.type + " from " +n.requestor_id); - if (n.type == NotebookRequest.Create_Table) { - conn.getNotebookTable().createTable(); - } - if (n.type == NotebookRequest.Drop_Table) { - conn.getNotebookTable().dropTable(); - } - if (n.type == NotebookRequest.Expunge_Notebook) { - conn.getNotebookTable().expungeNotebook(n.string1, n.bool1); - } - if (n.type == NotebookRequest.Add_Notebook) { - conn.getNotebookTable().addNotebook(n.notebook, n.bool1, n.bool2); - } - if (n.type == NotebookRequest.Update_Notebook) { - conn.getNotebookTable().updateNotebook(n.notebook, n.bool1); - } - if (n.type == NotebookRequest.Sync_Notebook) { - conn.getNotebookTable().syncNotebook(n.notebook, n.bool1); - } - if (n.type == NotebookRequest.Get_All) { - notebookResponse.get(n.requestor_id).responseNotebooks = conn.getNotebookTable().getAll(); - release(n.requestor_id); - } - if (n.type == NotebookRequest.Get_All_Local) { - notebookResponse.get(n.requestor_id).responseNotebooks = conn.getNotebookTable().getAllLocal(); - release(n.requestor_id); - } - if (n.type == NotebookRequest.Get_All_Archived) { - notebookResponse.get(n.requestor_id).responseNotebooks = conn.getNotebookTable().getAllArchived(); - release(n.requestor_id); - } - if (n.type == NotebookRequest.Get_Dirty) { - notebookResponse.get(n.requestor_id).responseNotebooks = conn.getNotebookTable().getDirty(); - release(n.requestor_id); - } - if (n.type == NotebookRequest.Set_Archived) { - conn.getNotebookTable().setArchived(n.string1, n.bool1); - } - if (n.type == NotebookRequest.Is_Notebook_Local) { - notebookResponse.get(n.requestor_id).responseBoolean = conn.getNotebookTable().isNotebookLocal(n.string1); - release(n.requestor_id); - } - if (n.type == NotebookRequest.Reset_Dirty) { - conn.getNotebookTable().resetDirtyFlag(n.string1); - } - if (n.type == NotebookRequest.Find_Note_By_Name) { - notebookResponse.get(n.requestor_id).responseString = conn.getNotebookTable().findNotebookByName(n.string1); - release(n.requestor_id); - } - if (n.type == NotebookRequest.Update_Notebook_Guid) { - conn.getNotebookTable().updateNotebookGuid(n.string1, n.string2); - } - if (n.type == NotebookRequest.Update_Notebook_Sequence) { - conn.getNotebookTable().updateNotebookSequence(n.string1, n.int1); - } - if (n.type == NotebookRequest.Notebook_Counts) { - notebookResponse.get(n.requestor_id).responseCounts = conn.getNotebookTable().getNotebookCounts(); - release(n.requestor_id); - } - logger.log(logger.EXTREME, "End of Notebook request"); - return; - } - - //****************************************** - //* Deleted (expunged) database requests * - //****************************************** - private void doDeletedItemRequest(DeletedItemRequest r) { - logger.log(logger.EXTREME, "DeletedItem request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == DeletedItemRequest.Create_Table) { - conn.getDeletedTable().createTable(); - } else if (r.type == DeletedItemRequest.Drop_Table) { - conn.getDeletedTable().dropTable(); - } else if (r.type == DeletedItemRequest.Expunge_All) { - conn.getDeletedTable().expungeAllDeletedRecords(); - } else if (r.type == DeletedItemRequest.Add_Deleted_Item) { - conn.getDeletedTable().addDeletedItem(r.string1, r.string2); - } else if (r.type == DeletedItemRequest.Get_All) { - deletedItemResponse.get(r.requestor_id).responseDeletedRecords = conn.getDeletedTable().getAllDeleted(); - release(r.requestor_id); - } else if (r.type == DeletedItemRequest.Expunge_Record) { - conn.getDeletedTable().expungeDeletedItem(r.string1, r.string2); - release(r.requestor_id); - } - logger.log(logger.EXTREME, "End Of DeletedItem request"); - return; - } - - //****************************************** - //* Tag database requests * - //****************************************** - private void doTagRequest(TagRequest r) { - logger.log(logger.EXTREME, "Tag request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == TagRequest.Create_Table) { - conn.getTagTable().createTable(); - } else if (r.type == TagRequest.Drop_Table) { - conn.getTagTable().dropTable(); - } else if (r.type == TagRequest.Add_Tag) { - conn.getTagTable().addTag(r.tag, r.bool1); - } else if (r.type == TagRequest.Expunge_Tag) { - conn.getTagTable().expungeTag(r.string1, r.bool1); - } else if (r.type == TagRequest.Exists) { - tagResponse.get(r.requestor_id).responseBool = conn.getTagTable().exists(r.string1); - release(r.requestor_id); - } else if (r.type == TagRequest.Find_Tag_By_Name) { - tagResponse.get(r.requestor_id).responseString = conn.getTagTable().findTagByName(r.string1); - release(r.requestor_id); - } else if (r.type == TagRequest.Get_All) { - tagResponse.get(r.requestor_id).responseTags = conn.getTagTable().getAll(); - release(r.requestor_id); - } else if (r.type == TagRequest.Get_Dirty) { - tagResponse.get(r.requestor_id).responseTags = conn.getTagTable().getDirty(); - release(r.requestor_id); - } else if (r.type == TagRequest.Get_Tag) { - tagResponse.get(r.requestor_id).responseTag = conn.getTagTable().getTag(r.string1); - release(r.requestor_id); - } else if (r.type == TagRequest.Reset_Dirty_Flag) { - conn.getTagTable().resetDirtyFlag(r.string1); - } else if (r.type == TagRequest.Save_Tags) { - conn.getTagTable().saveTags(r.tags); - } else if (r.type == TagRequest.Sync_Tag) { - conn.getTagTable().syncTag(r.tag, r.bool1); - } else if (r.type == TagRequest.Update_Parent) { - conn.getTagTable().updateTagParent(r.string1, r.string2); - } else if (r.type == TagRequest.Update_Tag) { - conn.getTagTable().updateTag(r.tag, r.bool1); - } else if (r.type == TagRequest.Update_Tag_Guid) { - conn.getTagTable().updateTagGuid(r.string1, r.string2); - } else if (r.type == TagRequest.Update_Tag_Sequence) { - conn.getTagTable().updateTagSequence(r.string1, r.int1); - } - logger.log(logger.EXTREME, "End of tag request"); - return; - } - - //****************************************** - //* Saved Search database requests * - //****************************************** - private void doSavedSearchRequest(SavedSearchRequest r) { - logger.log(logger.EXTREME, "Saved Search request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == SavedSearchRequest.Create_Table) { - conn.getSavedSearchTable().createTable(); - } else if (r.type == SavedSearchRequest.Drop_Table) { - conn.getSavedSearchTable().dropTable(); - } else if (r.type == SavedSearchRequest.Add_Saved_Search) { - conn.getSavedSearchTable().addSavedSearch(r.savedSearch, r.bool1); - } else if (r.type == SavedSearchRequest.Exists) { - savedSearchResponse.get(r.requestor_id).responseBoolean = conn.getSavedSearchTable().exists(r.string1); - release(r.requestor_id); - } else if (r.type == SavedSearchRequest.Expunge_Saved_Search) { - conn.getSavedSearchTable().expungeSavedSearch(r.string1, r.bool1); - } else if (r.type == SavedSearchRequest.Find_Saved_Search_By_Name) { - savedSearchResponse.get(r.requestor_id).responseString = conn.getSavedSearchTable().findSavedSearchByName(r.string1); - release(r.requestor_id); - } else if (r.type == SavedSearchRequest.Get_All) { - savedSearchResponse.get(r.requestor_id).responseSavedSearches = conn.getSavedSearchTable().getAll(); - release(r.requestor_id); - } else if (r.type == SavedSearchRequest.Get_Dirty) { - savedSearchResponse.get(r.requestor_id).responseSavedSearches = conn.getSavedSearchTable().getDirty(); - release(r.requestor_id); - } else if (r.type == SavedSearchRequest.Get_Saved_Search) { - savedSearchResponse.get(r.requestor_id).responseSavedSearch = conn.getSavedSearchTable().getSavedSearch(r.string1); - release(r.requestor_id); - } else if (r.type == SavedSearchRequest.Sync_Saved_Search) { - conn.getSavedSearchTable().syncSavedSearch(r.savedSearch, r.bool1); - } else if (r.type == SavedSearchRequest.Update_Saved_Search) { - conn.getSavedSearchTable().updateSavedSearch(r.savedSearch, r.bool1); - } else if (r.type == SavedSearchRequest.Reset_Dirty_Flag) { - conn.getSavedSearchTable().resetDirtyFlag(r.string1); - } - logger.log(logger.EXTREME, "End of saved search request"); - return; - } - - //****************************************** - //* Saved Search database requests * - //****************************************** - private void doNoteRequest(NoteRequest r) { - logger.log(logger.EXTREME, "Note request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == NoteRequest.Create_Table) { - conn.getNoteTable().createTable(); - } else if (r.type == NoteRequest.Drop_Table) { - conn.getNoteTable().dropTable(); - } else if (r.type == NoteRequest.Get_Note) { - noteResponse.get(r.requestor_id).responseNote = conn.getNoteTable().getNote(r.string1, r.bool1, r.bool2, r.bool3, r.bool4, r.bool5); - logger.log(logger.EXTREME, "Releasing " +r.requestor_id); - release(r.requestor_id); - } else if (r.type == NoteRequest.Add_Note) { - conn.getNoteTable().addNote(r.note, r.bool1); - } else if (r.type == NoteRequest.Update_Note_Title) { - conn.getNoteTable().updateNoteTitle(r.string1, r.string2); - } else if (r.type == NoteRequest.Update_Note_Creation_Date) { - conn.getNoteTable().updateNoteCreatedDate(r.string1, r.date); - } else if (r.type == NoteRequest.Update_Note_Altered_Date) { - conn.getNoteTable().updateNoteAlteredDate(r.string1, r.date); - } else if (r.type == NoteRequest.Update_Note_Subject_Date) { - conn.getNoteTable().updateNoteSubjectDate(r.string1, r.date); - } else if (r.type == NoteRequest.Update_Note_Source_Url) { - conn.getNoteTable().updateNoteSourceUrl(r.string1, r.string2); - } else if (r.type == NoteRequest.Update_Note_Author) { - conn.getNoteTable().updateNoteAuthor(r.string1, r.string2); - } else if (r.type == NoteRequest.Update_Note_Notebook) { - conn.getNoteTable().updateNoteNotebook(r.string1, r.string2, r.bool1); - } else if (r.type == NoteRequest.Update_Note_Content) { - conn.getNoteTable().updateNoteContent(r.string1, r.string2); - } else if (r.type == NoteRequest.Delete_Note) { - conn.getNoteTable().deleteNote(r.string1); - } else if (r.type == NoteRequest.Restore_Note) { - conn.getNoteTable().restoreNote(r.string1); - } else if (r.type == NoteRequest.Expunge_Note) { - conn.getNoteTable().expungeNote(r.string1, r.bool1, r.bool2); - } else if (r.type == NoteRequest.Expunge_All_Deleted_Notes) { - conn.getNoteTable().expungeAllDeletedNotes(); - } else if (r.type == NoteRequest.Update_Note_Sequence) { - conn.getNoteTable().updateNoteSequence(r.string1, r.int1); - } else if (r.type == NoteRequest.Update_Note_Guid) { - conn.getNoteTable().updateNoteGuid(r.string1, r.string2); - } else if (r.type == NoteRequest.Update_Note) { - conn.getNoteTable().updateNote(r.note, r.bool1); - } else if (r.type == NoteRequest.Exists) { - noteResponse.get(r.requestor_id).responseBoolean =conn.getNoteTable().exists(r.string1); - release(r.requestor_id); - } else if (r.type == NoteRequest.Sync_Note) { - conn.getNoteTable().syncNote(r.note, r.bool1); - } else if (r.type == NoteRequest.Get_Dirty) { - noteResponse.get(r.requestor_id).responseNotes = conn.getNoteTable().getDirty(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_Unsynchronized_Guids) { - noteResponse.get(r.requestor_id).responseStrings = conn.getNoteTable().getUnsynchronizedGUIDs(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Reset_Dirty_Flag) { - conn.getNoteTable().resetDirtyFlag(r.string1); - } else if (r.type == NoteRequest.Get_All_Guids) { - noteResponse.get(r.requestor_id).responseStrings = conn.getNoteTable().getAllGuids(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_All_Notes) { - noteResponse.get(r.requestor_id).responseNotes = conn.getNoteTable().getAllNotes(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_Unindexed_Count) { - noteResponse.get(r.requestor_id).responseInt = conn.getNoteTable().getUnindexedCount(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_Dirty_Count) { - noteResponse.get(r.requestor_id).responseInt = conn.getNoteTable().getDirtyCount(); - logger.log(logger.EXTREME, "Note request: " +r.category + " " + r.type + " from " +r.requestor_id); - release(r.requestor_id); - } else if (r.type == NoteRequest.Update_Resource_Guid_By_Hash) { - conn.getNoteTable().updateNoteResourceGuidbyHash(r.string1, r.string2, r.string3); - } else if (r.type == NoteRequest.Set_Index_Needed) { - conn.getNoteTable().setIndexNeeded(r.string1, r.bool1); - } else if (r.type == NoteRequest.Reindex_All_Notes) { - conn.getNoteTable().reindexAllNotes(); - } else if (r.type == NoteRequest.Get_Unindexed) { - noteResponse.get(r.requestor_id).responseStrings = conn.getNoteTable().getUnindexed(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_Next_Unindexed) { - noteResponse.get(r.requestor_id).responseStrings = conn.getNoteTable().getNextUnindexed(r.int1); - release(r.requestor_id); - } else if (r.type == NoteRequest.Update_Resource_Content_Hash) { - conn.getNoteTable().updateResourceContentHash(r.string1, r.string2, r.string3); - } else if (r.type == NoteRequest.Get_Note_Count) { - noteResponse.get(r.requestor_id).responseInt = conn.getNoteTable().getNoteCount(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Reset_Note_Sequence) { - conn.getNoteTable().resetNoteSequence(r.string1); - } else if (r.type == NoteRequest.Get_Deleted_Count) { - noteResponse.get(r.requestor_id).responseInt = conn.getNoteTable().getDeletedCount(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Is_Note_Dirty) { - noteResponse.get(r.requestor_id).responseBoolean = conn.getNoteTable().isNoteDirty(r.string1); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_Note_Content_Binary) { - noteResponse.get(r.requestor_id).responseString = conn.getNoteTable().getNoteContentBinary(r.string1); - release(r.requestor_id); - } else if (r.type == NoteRequest.Get_Title_Colors) { - noteResponse.get(r.requestor_id).responsePair = conn.getNoteTable().getNoteTitleColors(); - release(r.requestor_id); - } else if (r.type == NoteRequest.Set_Title_Colors) { - conn.getNoteTable().setNoteTitleColor(r.string1, r.int1); - } else if (r.type == NoteRequest.Get_Thumbnail) { - noteResponse.get(r.requestor_id).responseBytes = conn.getNoteTable().getThumbnail(r.string1); - release(r.requestor_id); - } else if (r.type == NoteRequest.Is_Thumbail_Needed) { - noteResponse.get(r.requestor_id).responseBoolean = conn.getNoteTable().isThumbnailNeeded(r.string1); - release(r.requestor_id); - } else if (r.type == NoteRequest.Set_Thumbnail_Needed) { - conn.getNoteTable().setThumbnailNeeded(r.string1, r.bool1); - } else if (r.type == NoteRequest.Set_Thumbnail) { - conn.getNoteTable().setThumbnail(r.string1, r.bytes); - } - logger.log(logger.EXTREME, "End of Note request"); - return; - } - - //****************************************** - //* Note Resource database requests * - //****************************************** - private void doResourceRequest(ResourceRequest r) { - logger.log(logger.EXTREME, "Resource request: " +r.category + " " + r.type); - if (r.type == ResourceRequest.Create_Table) { - conn.getNoteTable().noteResourceTable.createTable(); - } else if (r.type == ResourceRequest.Drop_Table) { - conn.getNoteTable().noteResourceTable.dropTable(); - } if (r.type == ResourceRequest.Expunge_Note_Resource) { - conn.getNoteTable().noteResourceTable.expungeNoteResource(r.string1); - } else if (r.type == ResourceRequest.Get_Next_Unindexed) { - resourceResponse.get(r.requestor_id).responseStrings = conn.getNoteTable().noteResourceTable.getNextUnindexed(r.int1); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Note_Resource) { - resourceResponse.get(r.requestor_id).responseResource = conn.getNoteTable().noteResourceTable.getNoteResource(r.string1, r.bool1); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Note_Resource_Data_Body_By_Hash_Hex) { - resourceResponse.get(r.requestor_id).responseResource = conn.getNoteTable().noteResourceTable.getNoteResourceDataBodyByHashHex(r.string1, r.string2); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Note_Resource_Guid_By_Hash_Hex) { - resourceResponse.get(r.requestor_id).responseString = conn.getNoteTable().noteResourceTable.getNoteResourceGuidByHashHex(r.string1, r.string2); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Note_Resource_Recognition) { - resourceResponse.get(r.requestor_id).responseResource = conn.getNoteTable().noteResourceTable.getNoteResourceRecognition(r.string1); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Note_Resources) { - resourceResponse.get(r.requestor_id).responseResources = conn.getNoteTable().noteResourceTable.getNoteResources(r.string1, r.bool1); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Note_Resources_Recognition) { - resourceResponse.get(r.requestor_id).responseResources = conn.getNoteTable().noteResourceTable.getNoteResourcesRecognition(r.string1); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Get_Resource_Count) { - resourceResponse.get(r.requestor_id).responseInteger = conn.getNoteTable().noteResourceTable.getResourceCount(); - release(r.requestor_id); - } else if (r.type == ResourceRequest.Reindex_All) { - conn.getNoteTable().noteResourceTable.reindexAll(); - } else if (r.type == ResourceRequest.Reset_Dirty_Flag) { - conn.getNoteTable().noteResourceTable.resetDirtyFlag(r.string1); - } else if (r.type == ResourceRequest.Save_Note_Resource) { - conn.getNoteTable().noteResourceTable.saveNoteResource(r.resource, r.bool1); - } else if (r.type == ResourceRequest.Set_Index_Needed) { - conn.getNoteTable().noteResourceTable.setIndexNeeded(r.string1, r.bool1); - } else if (r.type == ResourceRequest.Update_Note_Resource) { - conn.getNoteTable().noteResourceTable.updateNoteResource(r.resource, r.bool1); - } else if (r.type == ResourceRequest.Update_Note_Resource_Guid) { - conn.getNoteTable().noteResourceTable.updateNoteResourceGuid(r.string1, r.string2, r.bool1); - } else if (r.type == ResourceRequest.Reset_Update_Sequence_Number) { - conn.getNoteTable().noteResourceTable.resetUpdateSequenceNumber(r.string1, r.bool1); - } - logger.log(logger.EXTREME, "End of note resource request"); - } - - - //****************************************** - //* Note Resource database requests * - //****************************************** - private void doNoteTagsRequest(NoteTagsRequest r) { - logger.log(logger.EXTREME, "Note Tags request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == NoteTagsRequest.Create_Table) { - conn.getNoteTable().noteTagsTable.createTable(); - } else if (r.type == NoteTagsRequest.Drop_Table) { - conn.getNoteTable().noteTagsTable.dropTable(); - } else if (r.type == NoteTagsRequest.Check_Note_Note_Tags) { - noteTagsResponse.get(r.requestor_id).responseBoolean = conn.getNoteTable().noteTagsTable.checkNoteNoteTags(r.string1, r.string2); - release(r.requestor_id); - } if (r.type == NoteTagsRequest.Delete_Note_Tag) { - conn.getNoteTable().noteTagsTable.deleteNoteTag(r.string1); - } if (r.type == NoteTagsRequest.Get_All_Note_Tags) { - noteTagsResponse.get(r.requestor_id).responseNoteTagsRecord = conn.getNoteTable().noteTagsTable.getAllNoteTags(); - release(r.requestor_id); - } if (r.type == NoteTagsRequest.Get_Note_Tags) { - noteTagsResponse.get(r.requestor_id).responseStrings = conn.getNoteTable().noteTagsTable.getNoteTags(r.string1); - release(r.requestor_id); - } if (r.type == NoteTagsRequest.Save_Note_Tag) { - conn.getNoteTable().noteTagsTable.saveNoteTag(r.string1, r.string2); - } - if (r.type == NoteTagsRequest.Tag_Counts) { - noteTagsResponse.get(r.requestor_id).responseCounts = conn.getNoteTable().noteTagsTable.getTagCounts(); - release(r.requestor_id); - } - logger.log(logger.EXTREME, "End of note tags request"); - } - - - //************************************** - //* Search requests * - //************************************** - private void doEnSearchRequest(EnSearchRequest r) { - logger.log(logger.EXTREME, "EnSearch request: " +r.category + " " + r.type + " from " +r.requestor_id); - REnSearch s = new REnSearch(conn, logger, conn, r.string1, r.tags, r.int1, r.int2); - enSearchResponse.get(r.requestor_id).responseNotes = s.matchWords(); - enSearchResponse.get(r.requestor_id).responseStrings = s.getWords(); - release(r.requestor_id); - logger.log(logger.EXTREME, "End of EnSearch request"); - return; - } - - - - //************************************** - //* Watch Folders * - //************************************** - private void doWatchFolderRequest(WatchFolderRequest r) { - logger.log(logger.EXTREME, "Watch folder request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == WatchFolderRequest.Create_Tables) { - conn.getWatchFolderTable().createTable(); - } else if (r.type == WatchFolderRequest.Add_Watch_Folder) { - conn.getWatchFolderTable().addWatchFolder(r.string1, r.string2, r.bool1, r.int1); - } else if (r.type == WatchFolderRequest.Get_All) { - watchFolderResponse.get(r.requestor_id).responseWatchFolders = conn.getWatchFolderTable().getAll(); - release(r.requestor_id); - } - else if (r.type == WatchFolderRequest.Get_Notebook) { - watchFolderResponse.get(r.requestor_id).responseString = conn.getWatchFolderTable().getNotebook(r.string1); - release(r.requestor_id); - } else if (r.type == WatchFolderRequest.Drop_Tables) { - conn.getWatchFolderTable().dropTable(); - } else if (r.type == WatchFolderRequest.Expunge_Folder) { - conn.getWatchFolderTable().expungeWatchFolder(r.string1); - } else if (r.type == WatchFolderRequest.Expunge_All) { - conn.getWatchFolderTable().expungeAll(); - } - logger.log(logger.EXTREME, "End of watch folder request"); - } - - //************************************** - //* Word Index * - //************************************** - private void doWordRequest(WordRequest r) { - logger.log(logger.EXTREME, "Word request: " +r.category + " " + r.type + " from " +r.requestor_id); - if (r.type == WordRequest.Create_Table) { - conn.getWordsTable().createTable(); - } else if (r.type == WordRequest.Drop_Table) { - conn.getWordsTable().dropTable(); - } else if (r.type == WordRequest.Expunge_From_Word_Index) { - conn.getWordsTable().expungeFromWordIndex(r.string1, r.string2); - } else if (r.type == WordRequest.Clear_Word_Index) { - conn.getWordsTable().clearWordIndex(); - } else if (r.type == WordRequest.Get_Word_Count) { - wordResponse.get(r.requestor_id).responseInt = conn.getWordsTable().getWordCount(); - release(r.requestor_id); - } else if (r.type == WordRequest.Add_Word_To_Note_Index) { - conn.getWordsTable().addWordToNoteIndex(r.string1, r.string2, r.string3, r.int1); - release(r.requestor_id); - } - logger.log(logger.EXTREME, "End of word request"); - } - - - //************************************** - //* Invalid XML * - //************************************** - private void doInvalidXMLRequest(InvalidXMLRequest r) { - if (r.type == InvalidXMLRequest.Create_Table) { - conn.getInvalidXMLTable().createTable(); - return; - } else if (r.type == InvalidXMLRequest.Drop_Table) { - conn.getInvalidXMLTable().dropTable(); - return; - } else if (r.type == InvalidXMLRequest.Get_Invalid_Elements) { - invalidXMLResponse.get(r.requestor_id).responseList = conn.getInvalidXMLTable().getInvalidElements(); - release(r.requestor_id); - return; - } else if (r.type == InvalidXMLRequest.Get_Invalid_Attributes) { - invalidXMLResponse.get(r.requestor_id).responseArrayList = conn.getInvalidXMLTable().getInvalidAttributes(r.string1); - release(r.requestor_id); - return; - } else if (r.type == InvalidXMLRequest.Get_Invalid_Attribute_Elements) { - invalidXMLResponse.get(r.requestor_id).responseList = conn.getInvalidXMLTable().getInvalidAttributeElements(); - release(r.requestor_id); - return; - } else if (r.type == InvalidXMLRequest.Add_Invalid_Element) { - conn.getInvalidXMLTable().addElement(r.string1); - return; - } else if (r.type == InvalidXMLRequest.Add_Invalid_Attribute) { - conn.getInvalidXMLTable().addAttribute(r.string1, r.string2); - return; - } - } - - - //************************************** - //* Sync database * - //************************************** - private void doSyncRequest(SyncRequest r) { - if (r.type == SyncRequest.Create_Table) { - conn.getInvalidXMLTable().createTable(); - return; - } else if (r.type == SyncRequest.Drop_Table) { - conn.getInvalidXMLTable().dropTable(); - return; - } else if (r.type == SyncRequest.Get_Record) { - syncResponse.get(r.requestor_id).responseValue = conn.getSyncTable().getRecord(r.key); - release(r.requestor_id); - return; - } else if (r.type == SyncRequest.Set_Record) { - conn.getSyncTable().setRecord(r.key,r.value); - return; - } - } -} \ No newline at end of file diff --git a/src/cx/fbn/nevernote/threads/IndexRunner.java b/src/cx/fbn/nevernote/threads/IndexRunner.java index 80d4615..76a50fb 100644 --- a/src/cx/fbn/nevernote/threads/IndexRunner.java +++ b/src/cx/fbn/nevernote/threads/IndexRunner.java @@ -50,19 +50,18 @@ public class IndexRunner extends QObject implements Runnable { private boolean keepRunning; // public volatile int ID; private final QDomDocument doc; - private final int threadID; private static String regex = Global.getWordRegex(); private final DatabaseConnection conn; private volatile LinkedBlockingQueue workQueue; // private static int MAX_EMPTY_QUEUE_COUNT = 1; private static int MAX_QUEUED_WAITING = 1000; + - public IndexRunner(String logname) { + public IndexRunner(String logname, String u, String uid, String pswd, String cpswd) { logger = new ApplicationLogger(logname); - threadID = Global.indexThreadId; - conn = new DatabaseConnection(threadID); + conn = new DatabaseConnection(logger, u, uid, pswd, cpswd); noteSignal = new NoteSignal(); resourceSignal = new NoteResourceSignal(); // threadSignal = new ThreadSignal(); @@ -120,6 +119,7 @@ public class IndexRunner extends QObject implements Runnable { e.printStackTrace(); } } + conn.dbShutdown(); } // Reindex a note diff --git a/src/cx/fbn/nevernote/threads/SaveRunner.java b/src/cx/fbn/nevernote/threads/SaveRunner.java index 510055f..9efbd1a 100644 --- a/src/cx/fbn/nevernote/threads/SaveRunner.java +++ b/src/cx/fbn/nevernote/threads/SaveRunner.java @@ -40,7 +40,6 @@ public class SaveRunner extends QObject implements Runnable { public QMutex threadLock; private final DatabaseConnection conn; private boolean idle; - private final int threadID; private volatile LinkedBlockingQueue> workQueue = new LinkedBlockingQueue>(); @@ -48,10 +47,9 @@ public class SaveRunner extends QObject implements Runnable { //********************************************* //* Constructor * //********************************************* - public SaveRunner(String logname) { + public SaveRunner(String logname, String u, String uid, String pswd, String cpswd) { logger = new ApplicationLogger(logname); - threadID = Global.saveThreadId; - conn = new DatabaseConnection(threadID); + conn = new DatabaseConnection(logger, u, uid, pswd, cpswd); threadLock = new QMutex(); keepRunning = true; } @@ -92,6 +90,7 @@ public class SaveRunner extends QObject implements Runnable { threadLock.unlock(); } catch (InterruptedException e) { } } + conn.dbShutdown(); } diff --git a/src/cx/fbn/nevernote/threads/SyncRunner.java b/src/cx/fbn/nevernote/threads/SyncRunner.java index 45b73ef..5ed5137 100644 --- a/src/cx/fbn/nevernote/threads/SyncRunner.java +++ b/src/cx/fbn/nevernote/threads/SyncRunner.java @@ -50,7 +50,6 @@ import com.evernote.edam.userstore.UserStore; import com.trolltech.qt.core.QObject; import com.trolltech.qt.gui.QMessageBox; -import cx.fbn.nevernote.Global; import cx.fbn.nevernote.signals.NoteIndexSignal; import cx.fbn.nevernote.signals.NoteResourceSignal; import cx.fbn.nevernote.signals.NoteSignal; @@ -60,13 +59,13 @@ 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.runners.DeletedItemRecord; +import cx.fbn.nevernote.sql.DeletedItemRecord; import cx.fbn.nevernote.utilities.ApplicationLogger; public class SyncRunner extends QObject implements Runnable { private final ApplicationLogger logger; - private final DatabaseConnection conn; + private DatabaseConnection conn; private boolean idle; private boolean error; public volatile boolean isConnected; @@ -113,10 +112,14 @@ public class SyncRunner extends QObject implements Runnable { private volatile LinkedBlockingQueue workQueue; // private static int MAX_EMPTY_QUEUE_COUNT = 1; private static int MAX_QUEUED_WAITING = 1000; + String dbuid; + String dburl; + String dbpswd; + String dbcpswd; - public SyncRunner(String logname) { + public SyncRunner(String logname, String u, String uid, String pswd, String cpswd) { logger = new ApplicationLogger(logname); noteSignal = new NoteSignal(); @@ -128,9 +131,12 @@ public class SyncRunner extends QObject implements Runnable { searchSignal = new SavedSearchSignal(); syncSignal = new SyncSignal(); resourceSignal = new NoteResourceSignal(); - + dbuid = uid; + dburl = u; + dbpswd = pswd; + dbcpswd = cpswd; // this.setAutoDelete(false); - conn = new DatabaseConnection(Global.syncThreadId); + isConnected = false; syncNeeded = false; authRefreshNeeded = false; @@ -147,6 +153,7 @@ public class SyncRunner extends QObject implements Runnable { public void run() { try { logger.log(logger.EXTREME, "Starting thread"); + conn = new DatabaseConnection(logger, dburl, dbuid, dbpswd, dbcpswd); while(keepRunning) { String work = workQueue.take(); logger.log(logger.EXTREME, "Work found: " +work); @@ -180,6 +187,7 @@ public class SyncRunner extends QObject implements Runnable { catch (InterruptedException e1) { e1.printStackTrace(); } + conn.dbShutdown(); } @@ -480,6 +488,9 @@ public class SyncRunner extends QObject implements Runnable { logger.log(logger.EXTREME, "Active dirty note found - non new"); if (enNote.getUpdateSequenceNum() > 0) { enNote = getNoteContent(enNote); + System.out.println("--------"); + System.out.println("Note:" +enNote); + System.out.println("--------"); logger.log(logger.MEDIUM, "Updating note : "+ enNote.getGuid() +" " +enNote.getTitle()+""); enNote = noteStore.updateNote(authToken, enNote); } else { @@ -1171,7 +1182,7 @@ public class SyncRunner extends QObject implements Runnable { conn.getNoteTable().noteResourceTable.updateNoteResourceGuid(oldResG, newResG, true); } - conn.getNoteTable().resetSequenceNumber(guid); + conn.getNoteTable().resetNoteSequence(guid); conn.getNoteTable().updateNoteGuid(guid, newGuid); conn.getNoteTable().updateNoteNotebook(newGuid, notebookGuid, true); diff --git a/src/cx/fbn/nevernote/utilities/ListManager.java b/src/cx/fbn/nevernote/utilities/ListManager.java index ea1073d..3694608 100644 --- a/src/cx/fbn/nevernote/utilities/ListManager.java +++ b/src/cx/fbn/nevernote/utilities/ListManager.java @@ -50,7 +50,6 @@ import cx.fbn.nevernote.signals.TagSignal; import cx.fbn.nevernote.signals.ThreadSignal; import cx.fbn.nevernote.signals.TrashSignal; import cx.fbn.nevernote.sql.DatabaseConnection; -import cx.fbn.nevernote.sql.runners.NoteTagsRecord; import cx.fbn.nevernote.threads.CounterRunner; import cx.fbn.nevernote.threads.SaveRunner; @@ -125,21 +124,21 @@ public class ListManager { reloadIndexes(); notebookSignal = new NotebookSignal(); - notebookCounterRunner = new CounterRunner("notebook_counter.log", CounterRunner.NOTEBOOK); + notebookCounterRunner = new CounterRunner("notebook_counter.log", CounterRunner.NOTEBOOK, Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); notebookCounterRunner.setNoteIndex(getNoteIndex()); notebookCounterRunner.notebookSignal.countsChanged.connect(this, "setNotebookCounter(List)"); notebookThread = new QThread(notebookCounterRunner, "Notebook Counter Thread"); notebookThread.start(); tagSignal = new TagSignal(); - tagCounterRunner = new CounterRunner("tag_counter.log", CounterRunner.TAG); + tagCounterRunner = new CounterRunner("tag_counter.log", CounterRunner.TAG, Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); tagCounterRunner.setNoteIndex(getNoteIndex()); tagCounterRunner.tagSignal.countsChanged.connect(this, "setTagCounter(List)"); tagThread = new QThread(tagCounterRunner, "Tag Counter Thread"); tagThread.start(); trashSignal = new TrashSignal(); - trashCounterRunner = new CounterRunner("trash_counter.log", CounterRunner.TRASH); + trashCounterRunner = new CounterRunner("trash_counter.log", CounterRunner.TRASH, Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); trashCounterRunner.trashSignal.countChanged.connect(this, "trashSignalReceiver(Integer)"); trashThread = new QThread(trashCounterRunner, "Trash Counter Thread"); trashThread.start(); @@ -149,7 +148,7 @@ public class ListManager { tagSignal = new TagSignal(); logger.log(logger.EXTREME, "Setting save thread"); - saveRunner = new SaveRunner("saveRunner.log"); + saveRunner = new SaveRunner("saveRunner.log", Global.getDatabaseUrl(), Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword); saveThread = new QThread(saveRunner, "Save Runner Thread"); saveThread.start(); @@ -219,7 +218,7 @@ public class ListManager { masterNoteIndex = conn.getNoteTable().getAllNotes(); // For performance reasons, we didn't get the tags for every note individually. We now need to // get them - List noteTags = conn.getNoteTable().noteTagsTable.getAllNoteTags(); + List noteTags = conn.getNoteTable().noteTagsTable.getAllNoteTags(); for (int i=0; i tags = new ArrayList(); List names = new ArrayList(); @@ -257,7 +256,7 @@ public class ListManager { // load saved search index setSavedSearchIndex(conn.getSavedSearchTable().getAll()); // Load search helper utility - enSearch = new EnSearch(id, "", getTagIndex(), Global.getMinimumWordLength(), Global.getRecognitionWeight()); + enSearch = new EnSearch(conn, logger, "", getTagIndex(), Global.getMinimumWordLength(), Global.getRecognitionWeight()); logger.log(logger.HIGH, "Building note index"); if (masterNoteIndex == null) { @@ -265,7 +264,7 @@ public class ListManager { } // For performance reasons, we didn't get the tags for every note individually. We now need to // get them - List noteTags = conn.getNoteTable().noteTagsTable.getAllNoteTags(); + List noteTags = conn.getNoteTable().noteTagsTable.getAllNoteTags(); for (int i=0; i tags = new ArrayList(); List names = new ArrayList(); @@ -389,7 +388,7 @@ public class ListManager { //*************************************************************** //*************************************************************** public void setEnSearch(String t) { - enSearch = new EnSearch(id, t, getTagIndex(), Global.getMinimumWordLength(), Global.getRecognitionWeight()); + enSearch = new EnSearch(conn,logger, t, getTagIndex(), Global.getMinimumWordLength(), Global.getRecognitionWeight()); enSearchChanged = true; } // Save search tags @@ -674,6 +673,33 @@ public class ListManager { conn.getNoteTable().updateNoteAuthor(guid, author); } // Author has changed + public void updateNoteGeoTag(String guid, Double lon, Double lat, Double alt) { + for (int i=0; i