Эх сурвалжийг харах

Add the ability to filter nodes and list nodes

Oz Nahum Tiram 10 жил өмнө
parent
commit
c9f4382aa3

+ 462 - 445
pwman/data/drivers/sqlite.py

@@ -1,445 +1,462 @@
-#============================================================================
-# This file is part of Pwman3.
-#
-# Pwman3 is free software; you can redistribute iut and/or modify
-# it under the terms of the GNU General Public License, version 2
-# as published by the Free Software Foundation;
-#
-# Pwman3 is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pwman3; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#============================================================================
-# Copyright (C) 2012, 2013, 2014 Oz Nahum Tiram <nahumoz@gmail.com>
-#============================================================================
-# Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
-#============================================================================
-
-"""SQLite Database implementation."""
-from pwman.data.database import Database, DatabaseException
-from pwman.data.database import __DB_FORMAT__
-from pwman.data.nodes import NewNode
-from pwman.data.tags import TagNew
-from pwman.util.crypto_engine import CryptoEngine
-import sqlite3 as sqlite
-import itertools
-
-
-class SQLiteDatabaseNewForm(Database):
-    """SQLite Database implementation"""
-
-    @classmethod
-    def check_db_version(cls, fname):
-        """
-        check the data base version.
-        """
-        con = sqlite.connect(fname)
-        cur = con.cursor()
-        cur.execute("PRAGMA TABLE_INFO(DBVERSION)")
-        row = cur.fetchone()
-        if not row:
-            return "0.3"  # pragma: no cover
-        try:
-            return row[-2]
-        except IndexError:  # pragma: no cover
-            raise DatabaseException("Something seems fishy with the DB")
-
-    def __init__(self, filename, dbformat=__DB_FORMAT__):
-        """Initialise SQLitePwmanDatabase instance."""
-        super(SQLiteDatabaseNewForm, self).__init__()
-        self._filename = filename
-        self.dbformat = dbformat
-
-    def _open(self):
-        try:
-            self._con = sqlite.connect(self._filename)
-            self._cur = self._con.cursor()
-            self._checktables()
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-
-    def close(self):
-        self._cur.close()
-        self._con.close()
-
-    def listtags(self, alltags=False):
-        sql = ''
-        params = []
-        if not self._filtertags or alltags:
-            sql = "SELECT DATA FROM TAGS ORDER BY DATA ASC"
-        else:
-            sql = ("SELECT TAGS.DATA FROM LOOKUP"
-                   " INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
-                   " WHERE NODE IN (")
-            first = True
-            for t in self._filtertags:
-                if not first:
-                    sql += " INTERSECT "  # pragma: no cover
-                else:
-                    first = False
-
-                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
-                        " TAGS.ID WHERE TAGS.DATA LIKE ?")
-                params.append(t._name.decode()+u'%')
-            sql += ") EXCEPT SELECT DATA FROM TAGS WHERE "
-            first = True
-            for t in self._filtertags:
-                if not first:
-                    sql += " OR "  # pragma: no cover
-                else:
-                    first = False
-                sql += "TAGS.DATA = ?"
-                params.append(t.name)
-
-        try:
-            self._cur.execute(sql, params)
-            tags = [str(t[0]) for t in self._cur.fetchall()]
-            return tags
-
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-        except sqlite.InterfaceError as e:  # pragma: no cover
-            raise e
-
-    def parse_node_string(self, string):
-        nodestring = string.split("##")
-        keyvals = {}
-        for pair in nodestring[:-1]:
-            key, val = pair.split(":")
-            keyvals[key.lstrip('##')] = val
-        tags = nodestring[-1]
-        tags = tags.split("tags:", 1)[1]
-        tags = tags.split("tag:")
-        tags = [tag.split('**endtag**')[0] for tag in tags]
-        return keyvals, tags
-
-    def getnodes(self, ids):
-        """
-        object should always be: (ipwman.data.nodes
-        """
-        nodes = []
-        for i in ids:
-                sql = "SELECT DATA FROM NODES WHERE ID = ?"
-                self._cur.execute(sql, [i])
-                row = self._cur.fetchone()
-                if row is not None:
-                    nodestring = str(row[0])
-                    args, tags = self.parse_node_string(nodestring)
-                    node = NewNode()
-                    node._password = args['password']
-                    node._username = args['username']
-                    node._url = args['url']
-                    node._notes = args['notes']
-                    node.tags = tags
-                    node._id = i
-                    nodes.append(node)
-        return nodes
-
-    def editnode(self, id, node):
-        try:
-            sql = "UPDATE NODES SET DATA = ? WHERE ID = ?"
-            self._cur.execute(sql, [node.dump_edit_to_db()[0], id])
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-        self._setnodetags(node)
-        self._checktags()
-        self._commit()
-
-    def addnodes(self, nodes):
-        """
-        This method writes the data as an ecrypted string to
-        the database
-        """
-        for n in nodes:
-            sql = "INSERT INTO NODES(DATA) VALUES(?)"
-            value = n.dump_edit_to_db()
-            try:
-                self._cur.execute(sql, value)
-            except sqlite.DatabaseError as e:  # pragma: no cover
-                raise DatabaseException("SQLite: %s" % (e))
-            idx = self._cur.lastrowid
-            n._id = idx
-            self._setnodetags(n)
-            self._commit()
-
-    def removenodes(self, nodes):
-        for n in nodes:
-            # if not isinstance(n, Node): raise DatabaseException(
-            #    "Tried to delete foreign object from database [%s]", n)
-            try:
-                sql = "DELETE FROM NODES WHERE ID = ?"
-                self._cur.execute(sql, [n._id])
-
-            except sqlite.DatabaseError as e:  # pragma: no cover
-                raise DatabaseException("SQLite: %s" % (e))
-            self._deletenodetags(n)
-
-        self._checktags()
-        self._commit()
-
-    def listnodes(self):
-        sql = ''
-        params = []
-        if not self._filtertags:
-            sql = "SELECT ID FROM NODES ORDER BY ID ASC"
-        else:
-            first = True
-            for t in self._filtertags:
-                if not first:
-                    sql += " INTERSECT "  # pragma: no cover
-                else:
-                    first = False
-                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
-                        " TAGS.ID WHERE TAGS.DATA LIKE ? ")
-                # this is correct if tags are ciphertext
-                p = t._name.strip()
-                # this is wrong, it will work when tags are stored as plain
-                # text
-                # p = t.name.strip()
-                p = '%'+p+'%'
-                params = [p]
-
-        try:
-            self._cur.execute(sql, params)
-            rows = self._cur.fetchall()
-            ids = [row[0] for row in rows]
-            return ids
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-
-    def _commit(self):
-        try:
-            self._con.commit()
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            self._con.rollback()
-            raise DatabaseException(
-                "SQLite: Error commiting data to db [%s]" % (e))
-
-    def _create_tag(self, tag):
-        """add tags to db"""
-        # sql = "INSERT OR REPLACE INTO TAGS(DATA) VALUES(?)"
-        sql = "INSERT OR IGNORE INTO TAGS(DATA) VALUES(?)"
-
-        if isinstance(tag, str):
-            self._cur.execute(sql, [tag])
-        elif isinstance(tag, TagNew):
-            self._cur.execute(sql, [tag._name])
-        else:
-            self._cur.execute(sql, [tag.decode()])
-
-    def _deletenodetags(self, node):
-        try:
-            sql = "DELETE FROM LOOKUP WHERE NODE = ?"
-            self._cur.execute(sql, [node._id])
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-        self._commit()
-
-    def _update_tag_lookup(self, node, tag_id):
-        sql = "INSERT OR REPLACE INTO LOOKUP VALUES(?, ?)"
-        params = [node._id, tag_id]
-        try:
-            self._cur.execute(sql, params)
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-
-    def _tagids(self, tags):
-        ids = []
-        sql = "SELECT ID FROM TAGS WHERE DATA LIKE ?"
-
-        for tag in tags:
-            try:
-                if isinstance(tag, str):
-                    enc = CryptoEngine.get()
-                    tag = enc.encrypt(tag)
-                    self._cur.execute(sql, [tag])
-                elif isinstance(tag, TagNew):
-                    self._cur.execute(sql, [tag._name.decode()+u'%'])
-                else:
-                    self._cur.execute(sql, [tag.decode()+u'%'])
-
-                values = self._cur.fetchall()
-                if values:  # tags already exist in the database
-                    ids.extend(list(itertools.chain(*values)))
-                else:
-                    self._create_tag(tag)
-                    ids.append(self._cur.lastrowid)
-            except sqlite.DatabaseError as e:  # pragma: no cover
-                raise DatabaseException("SQLite: %s" % (e))
-        return ids
-
-    def _setnodetags(self, node):
-        ids = self._tagids(node.tags)
-        for tagid in ids:
-            self._update_tag_lookup(node, tagid)
-        self._commit()
-
-    def _checktags(self):
-        try:
-            sql = "DELETE FROM TAGS WHERE ID NOT IN (SELECT TAG FROM" \
-                + " LOOKUP GROUP BY TAG)"
-            self._cur.execute(sql)
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            raise DatabaseException("SQLite: %s" % (e))
-        self._commit()
-
-    def _checktables(self):
-        """
-        Check if the Pwman tables exist.
-        TODO: This method should check the version of the
-        database. If it finds an old format it should
-        exis, and prompt the user to convert the database
-        to the new version with a designated script.
-        """
-        self._cur.execute("PRAGMA TABLE_INFO(NODES)")
-        if self._cur.fetchone() is None:
-            # table doesn't exist, create it
-            # SQLite does have constraints implemented at the moment
-            # so datatype will just be a string
-            self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
-                              " AUTOINCREMENT,DATA BLOB NOT NULL)")
-            self._cur.execute("CREATE TABLE TAGS"
-                              "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                              "DATA BLOB NOT NULL UNIQUE)")
-            self._cur.execute("CREATE TABLE LOOKUP"
-                              "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
-                              " PRIMARY KEY(NODE, TAG))")
-
-            self._cur.execute("CREATE TABLE KEY"
-                              "(THEKEY TEXT NOT NULL DEFAULT '')")
-            self._cur.execute("INSERT INTO KEY VALUES('')")
-            # create a table to hold DB version info
-            self._cur.execute("CREATE TABLE DBVERSION"
-                              "(DBVERSION TEXT NOT NULL DEFAULT '%s')" %
-                              self.dbformat)
-            self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
-                              self.dbformat)
-            try:
-                self._con.commit()
-            except DatabaseException as e:  # pragma: no cover
-                self._con.rollback()
-                raise e
-
-    def savekey(self, key):
-        """
-        This function is saving the key to table KEY.
-        The key already arrives as an encrypted string.
-        It is the same self._keycrypted from
-        crypto py (check with id(self._keycrypted) and
-        id(key) here.
-        """
-        sql = "UPDATE KEY SET THEKEY = ?"
-        values = [key]
-        self._cur.execute(sql, values)
-        try:
-            self._con.commit()
-        except sqlite.DatabaseError as e:  # pragma: no cover
-            self._con.rollback()
-            raise DatabaseException(
-                "SQLite: Error saving key [%s]" % (e))
-
-    def loadkey(self):
-        """
-        fetch the key to database. the key is also stored
-        encrypted.
-        """
-        self._cur.execute("SELECT THEKEY FROM KEY")
-        keyrow = self._cur.fetchone()
-        if (keyrow[0] == ''):
-            return None
-        else:
-            return keyrow[0]
-
-
-class SQLite(SQLiteDatabaseNewForm):
-
-    def __init__(self, filename, dbformat=0.6):
-        """Initialise SQLitePwmanDatabase instance."""
-        self._filename = filename
-        self.dbformat = dbformat
-
-    def _open(self):
-        self._con = sqlite.connect(self._filename)
-        self._cur = self._con.cursor()
-
-    def _create_tables(self):
-        self._cur.execute("PRAGMA TABLE_INFO(NODE)")
-        if self._cur.fetchone() is not None:
-            return
-
-        self._cur.execute("CREATE TABLE NODE (ID INTEGER PRIMARY KEY "
-                          "AUTOINCREMENT, "
-                          "USER TEXT NOT NULL, "
-                          "PASSWORD TEXT NOT NULL, "
-                          "URL TEXT NOT NULL,"
-                          "NOTES TEXT NOT NULL)")
-
-        self._cur.execute("CREATE TABLE TAG"
-                          "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                          "DATA BLOB NOT NULL UNIQUE)")
-
-        self._cur.execute("CREATE TABLE LOOKUP ("
-                          "nodeid INTEGER NOT NULL, "
-                          "tagid INTEGER NOT NULL, "
-                          "FOREIGN KEY(nodeid) REFERENCES NODE(ID),"
-                          "FOREIGN KEY(tagid) REFERENCES TAG(ID))")
-
-        self._cur.execute("CREATE TABLE CRYPTO"
-                          "(SEED TEXT,"
-                          " DIGEST TEXT)")
-
-        # create a table to hold DB version info
-        self._cur.execute("CREATE TABLE DBVERSION"
-                          "(DB VERSION TEXT NOT NULL DEFAULT '%s')" %
-                          self.dbformat)
-        self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
-                          self.dbformat)
-        try:
-            self._con.commit()
-        except DatabaseException as e:  # pragma: no cover
-            self._con.rollback()
-            raise e
-
-    def fetch_crypto_info(self):
-        self._cur.execute("SELECT * FROM CRYPTO")
-        keyrow = self._cur.fetchone()
-        return keyrow
-
-    def save_crypto_info(self, seed, digest):
-        """save the random seed and the digested key"""
-        self._cur.execute("DELETE  FROM CRYPTO")
-        self._cur.execute("INSERT INTO CRYPTO VALUES(?, ?)", [seed, digest])
-        self._con.commit()
-
-    def add_node(self, node):
-        sql = ("INSERT INTO NODE(USER, PASSWORD, URL, NOTES)"
-               "VALUES(?, ?, ?, ?)")
-        node_tags = list(node)
-        node, tags = node_tags[:4], node_tags[-1]
-        self._cur.execute(sql, (node))
-        self._setnodetags(self._cur.lastrowid, tags)
-        self._con.commit()
-
-    def _get_or_create_tag(self, tagcipher):
-        sql_search = "SELECT ID FROM TAG WHERE DATA LIKE (?)"
-        self._cur.execute(sql_search, ([tagcipher]))
-        rv = self._cur.fetchone()
-        if rv:
-            return rv[0]
-        else:
-            sql_insert = "INSERT INTO TAG(DATA) VALUES(?)"
-            self._cur.execute(sql_insert, ([tagcipher]))
-            return self._cur.lastrowid
-
-    def _update_tag_lookup(self, nodeid, tid):
-        sql_lookup = "INSERT INTO LOOKUP(nodeid, tagid) VALUES(?,?)"
-        self._cur.execute(sql_lookup, (nodeid, tid))
-
-    def _setnodetags(self, nodeid, tags):
-        for tag in tags:
-            tid = self._get_or_create_tag(tag)
-            self._update_tag_lookup(nodeid, tid)
+# ============================================================================
+# This file is part of Pwman3.
+#
+# Pwman3 is free software; you can redistribute iut and/or modify
+# it under the terms of the GNU General Public License, version 2
+# as published by the Free Software Foundation;
+#
+# Pwman3 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pwman3; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+# ============================================================================
+# Copyright (C) 2012, 2013, 2014 Oz Nahum Tiram <nahumoz@gmail.com>
+# ============================================================================
+# Copyright (C) 2006 Ivan Kelly <ivan@ivankelly.net>
+# ============================================================================
+
+"""SQLite Database implementation."""
+from pwman.data.database import Database, DatabaseException
+from pwman.data.database import __DB_FORMAT__
+from pwman.data.nodes import NewNode
+from pwman.data.tags import TagNew
+from pwman.util.crypto_engine import CryptoEngine
+import sqlite3 as sqlite
+import itertools
+
+
+class SQLiteDatabaseNewForm(Database):
+    """SQLite Database implementation"""
+
+    @classmethod
+    def check_db_version(cls, fname):
+        """
+        check the data base version.
+        """
+        con = sqlite.connect(fname)
+        cur = con.cursor()
+        cur.execute("PRAGMA TABLE_INFO(DBVERSION)")
+        row = cur.fetchone()
+        if not row:
+            return "0.3"  # pragma: no cover
+        try:
+            return row[-2]
+        except IndexError:  # pragma: no cover
+            raise DatabaseException("Something seems fishy with the DB")
+
+    def __init__(self, filename, dbformat=__DB_FORMAT__):
+        """Initialise SQLitePwmanDatabase instance."""
+        super(SQLiteDatabaseNewForm, self).__init__()
+        self._filename = filename
+        self.dbformat = dbformat
+
+    def _open(self):
+        try:
+            self._con = sqlite.connect(self._filename)
+            self._cur = self._con.cursor()
+            self._checktables()
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+
+    def close(self):
+        self._cur.close()
+        self._con.close()
+
+    def listtags(self, alltags=False):
+        sql = ''
+        params = []
+        if not self._filtertags or alltags:
+            sql = "SELECT DATA FROM TAGS ORDER BY DATA ASC"
+        else:
+            sql = ("SELECT TAGS.DATA FROM LOOKUP"
+                   " INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
+                   " WHERE NODE IN (")
+            first = True
+            for t in self._filtertags:
+                if not first:
+                    sql += " INTERSECT "  # pragma: no cover
+                else:
+                    first = False
+
+                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
+                        " TAGS.ID WHERE TAGS.DATA LIKE ?")
+                params.append(t._name.decode()+u'%')
+            sql += ") EXCEPT SELECT DATA FROM TAGS WHERE "
+            first = True
+            for t in self._filtertags:
+                if not first:
+                    sql += " OR "  # pragma: no cover
+                else:
+                    first = False
+                sql += "TAGS.DATA = ?"
+                params.append(t.name)
+
+        try:
+            self._cur.execute(sql, params)
+            tags = [str(t[0]) for t in self._cur.fetchall()]
+            return tags
+
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+        except sqlite.InterfaceError as e:  # pragma: no cover
+            raise e
+
+    def parse_node_string(self, string):
+        nodestring = string.split("##")
+        keyvals = {}
+        for pair in nodestring[:-1]:
+            key, val = pair.split(":")
+            keyvals[key.lstrip('##')] = val
+        tags = nodestring[-1]
+        tags = tags.split("tags:", 1)[1]
+        tags = tags.split("tag:")
+        tags = [tag.split('**endtag**')[0] for tag in tags]
+        return keyvals, tags
+
+    def getnodes(self, ids):
+        """
+        object should always be: (ipwman.data.nodes
+        """
+        nodes = []
+        for i in ids:
+                sql = "SELECT DATA FROM NODES WHERE ID = ?"
+                self._cur.execute(sql, [i])
+                row = self._cur.fetchone()
+                if row is not None:
+                    nodestring = str(row[0])
+                    args, tags = self.parse_node_string(nodestring)
+                    node = NewNode()
+                    node._password = args['password']
+                    node._username = args['username']
+                    node._url = args['url']
+                    node._notes = args['notes']
+                    node.tags = tags
+                    node._id = i
+                    nodes.append(node)
+        return nodes
+
+    def editnode(self, id, node):
+        try:
+            sql = "UPDATE NODES SET DATA = ? WHERE ID = ?"
+            self._cur.execute(sql, [node.dump_edit_to_db()[0], id])
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+        self._setnodetags(node)
+        self._checktags()
+        self._commit()
+
+    def addnodes(self, nodes):
+        """
+        This method writes the data as an ecrypted string to
+        the database
+        """
+        for n in nodes:
+            sql = "INSERT INTO NODES(DATA) VALUES(?)"
+            value = n.dump_edit_to_db()
+            try:
+                self._cur.execute(sql, value)
+            except sqlite.DatabaseError as e:  # pragma: no cover
+                raise DatabaseException("SQLite: %s" % (e))
+            idx = self._cur.lastrowid
+            n._id = idx
+            self._setnodetags(n)
+            self._commit()
+
+    def removenodes(self, nodes):
+        for n in nodes:
+            # if not isinstance(n, Node): raise DatabaseException(
+            #    "Tried to delete foreign object from database [%s]", n)
+            try:
+                sql = "DELETE FROM NODES WHERE ID = ?"
+                self._cur.execute(sql, [n._id])
+
+            except sqlite.DatabaseError as e:  # pragma: no cover
+                raise DatabaseException("SQLite: %s" % (e))
+            self._deletenodetags(n)
+
+        self._checktags()
+        self._commit()
+
+    def listnodes(self):
+        sql = ''
+        params = []
+        if not self._filtertags:
+            sql = "SELECT ID FROM NODES ORDER BY ID ASC"
+        else:
+            first = True
+            for t in self._filtertags:
+                if not first:
+                    sql += " INTERSECT "  # pragma: no cover
+                else:
+                    first = False
+                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
+                        " TAGS.ID WHERE TAGS.DATA LIKE ? ")
+                # this is correct if tags are ciphertext
+                p = t._name.strip()
+                # this is wrong, it will work when tags are stored as plain
+                # text
+                # p = t.name.strip()
+                p = '%'+p+'%'
+                params = [p]
+
+        try:
+            self._cur.execute(sql, params)
+            rows = self._cur.fetchall()
+            ids = [row[0] for row in rows]
+            return ids
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+
+    def _commit(self):
+        try:
+            self._con.commit()
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            self._con.rollback()
+            raise DatabaseException(
+                "SQLite: Error commiting data to db [%s]" % (e))
+
+    def _create_tag(self, tag):
+        """add tags to db"""
+        # sql = "INSERT OR REPLACE INTO TAGS(DATA) VALUES(?)"
+        sql = "INSERT OR IGNORE INTO TAGS(DATA) VALUES(?)"
+
+        if isinstance(tag, str):
+            self._cur.execute(sql, [tag])
+        elif isinstance(tag, TagNew):
+            self._cur.execute(sql, [tag._name])
+        else:
+            self._cur.execute(sql, [tag.decode()])
+
+    def _deletenodetags(self, node):
+        try:
+            sql = "DELETE FROM LOOKUP WHERE NODE = ?"
+            self._cur.execute(sql, [node._id])
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+        self._commit()
+
+    def _update_tag_lookup(self, node, tag_id):
+        sql = "INSERT OR REPLACE INTO LOOKUP VALUES(?, ?)"
+        params = [node._id, tag_id]
+        try:
+            self._cur.execute(sql, params)
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+
+    def _tagids(self, tags):
+        ids = []
+        sql = "SELECT ID FROM TAGS WHERE DATA LIKE ?"
+
+        for tag in tags:
+            try:
+                if isinstance(tag, str):
+                    enc = CryptoEngine.get()
+                    tag = enc.encrypt(tag)
+                    self._cur.execute(sql, [tag])
+                elif isinstance(tag, TagNew):
+                    self._cur.execute(sql, [tag._name.decode()+u'%'])
+                else:
+                    self._cur.execute(sql, [tag.decode()+u'%'])
+
+                values = self._cur.fetchall()
+                if values:  # tags already exist in the database
+                    ids.extend(list(itertools.chain(*values)))
+                else:
+                    self._create_tag(tag)
+                    ids.append(self._cur.lastrowid)
+            except sqlite.DatabaseError as e:  # pragma: no cover
+                raise DatabaseException("SQLite: %s" % (e))
+        return ids
+
+    def _setnodetags(self, node):
+        ids = self._tagids(node.tags)
+        for tagid in ids:
+            self._update_tag_lookup(node, tagid)
+        self._commit()
+
+    def _checktags(self):
+        try:
+            sql = "DELETE FROM TAGS WHERE ID NOT IN (SELECT TAG FROM" \
+                + " LOOKUP GROUP BY TAG)"
+            self._cur.execute(sql)
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            raise DatabaseException("SQLite: %s" % (e))
+        self._commit()
+
+    def _checktables(self):
+        """
+        Check if the Pwman tables exist.
+        TODO: This method should check the version of the
+        database. If it finds an old format it should
+        exis, and prompt the user to convert the database
+        to the new version with a designated script.
+        """
+        self._cur.execute("PRAGMA TABLE_INFO(NODES)")
+        if self._cur.fetchone() is None:
+            # table doesn't exist, create it
+            # SQLite does have constraints implemented at the moment
+            # so datatype will just be a string
+            self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
+                              " AUTOINCREMENT,DATA BLOB NOT NULL)")
+            self._cur.execute("CREATE TABLE TAGS"
+                              "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                              "DATA BLOB NOT NULL UNIQUE)")
+            self._cur.execute("CREATE TABLE LOOKUP"
+                              "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
+                              " PRIMARY KEY(NODE, TAG))")
+
+            self._cur.execute("CREATE TABLE KEY"
+                              "(THEKEY TEXT NOT NULL DEFAULT '')")
+            self._cur.execute("INSERT INTO KEY VALUES('')")
+            # create a table to hold DB version info
+            self._cur.execute("CREATE TABLE DBVERSION"
+                              "(DBVERSION TEXT NOT NULL DEFAULT '%s')" %
+                              self.dbformat)
+            self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
+                              self.dbformat)
+            try:
+                self._con.commit()
+            except DatabaseException as e:  # pragma: no cover
+                self._con.rollback()
+                raise e
+
+    def savekey(self, key):
+        """
+        This function is saving the key to table KEY.
+        The key already arrives as an encrypted string.
+        It is the same self._keycrypted from
+        crypto py (check with id(self._keycrypted) and
+        id(key) here.
+        """
+        sql = "UPDATE KEY SET THEKEY = ?"
+        values = [key]
+        self._cur.execute(sql, values)
+        try:
+            self._con.commit()
+        except sqlite.DatabaseError as e:  # pragma: no cover
+            self._con.rollback()
+            raise DatabaseException(
+                "SQLite: Error saving key [%s]" % (e))
+
+    def loadkey(self):
+        """
+        fetch the key to database. the key is also stored
+        encrypted.
+        """
+        self._cur.execute("SELECT THEKEY FROM KEY")
+        keyrow = self._cur.fetchone()
+        if (keyrow[0] == ''):
+            return None
+        else:
+            return keyrow[0]
+
+
+class SQLite(SQLiteDatabaseNewForm):
+
+    def __init__(self, filename, dbformat=0.6):
+        """Initialise SQLitePwmanDatabase instance."""
+        self._filename = filename
+        self.dbformat = dbformat
+
+    def _open(self):
+        self._con = sqlite.connect(self._filename)
+        self._cur = self._con.cursor()
+
+    def listnodes(self, filter=None):
+        if not filter:
+            sql_all = "SELECT ID FROM NODE"
+            self._cur.execute(sql_all)
+            ids = self._cur.fetchall()
+            return ids
+        else:
+            tagid = self._get_tag(filter)
+            sql_filter = "SELECT NODEID FROM LOOKUP WHERE TAGID = ? "
+            self._cur.execute(sql_filter, tagid)
+            ids = self._cur.fetchall()
+            return ids
+
+    def _create_tables(self):
+        self._cur.execute("PRAGMA TABLE_INFO(NODE)")
+        if self._cur.fetchone() is not None:
+            return
+
+        self._cur.execute("CREATE TABLE NODE (ID INTEGER PRIMARY KEY "
+                          "AUTOINCREMENT, "
+                          "USER TEXT NOT NULL, "
+                          "PASSWORD TEXT NOT NULL, "
+                          "URL TEXT NOT NULL,"
+                          "NOTES TEXT NOT NULL)")
+
+        self._cur.execute("CREATE TABLE TAG"
+                          "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                          "DATA BLOB NOT NULL UNIQUE)")
+
+        self._cur.execute("CREATE TABLE LOOKUP ("
+                          "nodeid INTEGER NOT NULL, "
+                          "tagid INTEGER NOT NULL, "
+                          "FOREIGN KEY(nodeid) REFERENCES NODE(ID),"
+                          "FOREIGN KEY(tagid) REFERENCES TAG(ID))")
+
+        self._cur.execute("CREATE TABLE CRYPTO"
+                          "(SEED TEXT,"
+                          " DIGEST TEXT)")
+
+        # create a table to hold DB version info
+        self._cur.execute("CREATE TABLE DBVERSION"
+                          "(DB VERSION TEXT NOT NULL DEFAULT '%s')" %
+                          self.dbformat)
+        self._cur.execute("INSERT INTO DBVERSION VALUES('%s')" %
+                          self.dbformat)
+        try:
+            self._con.commit()
+        except DatabaseException as e:  # pragma: no cover
+            self._con.rollback()
+            raise e
+
+    def fetch_crypto_info(self):
+        self._cur.execute("SELECT * FROM CRYPTO")
+        keyrow = self._cur.fetchone()
+        return keyrow
+
+    def save_crypto_info(self, seed, digest):
+        """save the random seed and the digested key"""
+        self._cur.execute("DELETE  FROM CRYPTO")
+        self._cur.execute("INSERT INTO CRYPTO VALUES(?, ?)", [seed, digest])
+        self._con.commit()
+
+    def add_node(self, node):
+        sql = ("INSERT INTO NODE(USER, PASSWORD, URL, NOTES)"
+               "VALUES(?, ?, ?, ?)")
+        node_tags = list(node)
+        node, tags = node_tags[:4], node_tags[-1]
+        self._cur.execute(sql, (node))
+        self._setnodetags(self._cur.lastrowid, tags)
+        self._con.commit()
+
+    def _get_tag(self, tagcipher):
+        sql_search = "SELECT ID FROM TAG WHERE DATA LIKE (?)"
+        self._cur.execute(sql_search, ([tagcipher]))
+        rv = self._cur.fetchone()
+        return rv
+
+    def _get_or_create_tag(self, tagcipher):
+        rv = self._get_tag(tagcipher)
+        if rv:
+            return rv[0]
+        else:
+            sql_insert = "INSERT INTO TAG(DATA) VALUES(?)"
+            self._cur.execute(sql_insert, ([tagcipher]))
+            return self._cur.lastrowid
+
+    def _update_tag_lookup(self, nodeid, tid):
+        sql_lookup = "INSERT INTO LOOKUP(nodeid, tagid) VALUES(?,?)"
+        self._cur.execute(sql_lookup, (nodeid, tid))
+
+    def _setnodetags(self, nodeid, tags):
+        for tag in tags:
+            tid = self._get_or_create_tag(tag)
+            self._update_tag_lookup(nodeid, tid)

+ 103 - 84
pwman/tests/test_sqlite.py

@@ -1,84 +1,103 @@
-#============================================================================
-# This file is part of Pwman3.
-#
-# Pwman3 is free software; you can redistribute iut and/or modify
-# it under the terms of the GNU General Public License, version 2
-# as published by the Free Software Foundation;
-#
-# Pwman3 is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Pwman3; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-#============================================================================
-# Copyright (C) 2012, 2013, 2014 Oz Nahum Tiram <nahumoz@gmail.com>
-#============================================================================
-import os
-import unittest
-from pwman.data.drivers.sqlite import SQLite
-from pwman.data.nodes import Node
-from pwman.util.crypto_engine import CryptoEngine
-
-
-class TestSQLite(unittest.TestCase):
-    def setUp(self):
-        self.db = SQLite('test.db')
-        self.db._open()
-
-    def test_1_create_tables(self):
-        self.db._create_tables()
-        self.db._con.commit()
-        # the method _open calls _create_tables
-        self.db.save_crypto_info("foo", "bar")
-        self.db._create_tables()
-
-    def test_1a_create_tables(self):
-        self.db._create_tables()
-
-    def test_2_crypto_info(self):
-        self.db._create_tables()
-        self.db.save_crypto_info("foo", "bar")
-        f = self.db.fetch_crypto_info()
-        self.assertListEqual([u'foo', u'bar'], list(f))
-
-    def test_3_add_node(self):
-        node = Node(clear_text=True,
-                    **{'username': u"alice", 'password': u"secret",
-                       'url': u"wonderland.com",
-                       'notes': u"a really great place",
-                       'tags': [u'foo', u'bar']})
-        self.db.add_node(node)
-        rv = self.db._cur.execute("select * from node")
-        # clearly this fails, while alice is not found in clear text in the
-        # database!
-        ce = CryptoEngine.get()
-        res = rv.fetchone()
-        self.assertIn(ce.encrypt(u'alice'), res[1])
-
-    def test_4_test_tags(self):
-        node = Node(clear_text=True,
-                    **{'username': u"alice", 'password': u"secret",
-                       'url': u"wonderland.com",
-                       'notes': u"a really great place",
-                       'tags': [u'foo', u'bar']})
-        ce = CryptoEngine.get()
-        self.db._get_or_create_tag(node._tags[0])
-        self.assertEqual(1, self.db._get_or_create_tag(node._tags[0]))
-        self.assertEqual(3, self.db._get_or_create_tag(ce.encrypt('baz')))
-    
-    def test_5_test_lookup(self):
-        self.db._cur.execute('SELECT * FROM LOOKUP')
-        rows = self.db._cur.fetchall()
-        self.assertEqual(2, len(rows))
-
-    def tearDown(self):
-        self.db.close()
-
-if __name__ == '__main__':
-    try:
-        unittest.main(verbosity=2)
-    except SystemExit:
-        os.remove('test.db')
+# ============================================================================
+# This file is part of Pwman3.
+#
+# Pwman3 is free software; you can redistribute iut and/or modify
+# it under the terms of the GNU General Public License, version 2
+# as published by the Free Software Foundation;
+#
+# Pwman3 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pwman3; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+# ============================================================================
+# Copyright (C) 2012, 2013, 2014 Oz Nahum Tiram <nahumoz@gmail.com>
+# ============================================================================
+import os
+import unittest
+from pwman.data.drivers.sqlite import SQLite
+from pwman.data.nodes import Node
+from pwman.util.crypto_engine import CryptoEngine
+
+
+class TestSQLite(unittest.TestCase):
+    def setUp(self):
+        self.db = SQLite('test.db')
+        self.db._open()
+
+    def test_1_create_tables(self):
+        self.db._create_tables()
+        self.db._con.commit()
+        # the method _open calls _create_tables
+        self.db.save_crypto_info("foo", "bar")
+        self.db._create_tables()
+
+    def test_1a_create_tables(self):
+        self.db._create_tables()
+
+    def test_2_crypto_info(self):
+        self.db._create_tables()
+        self.db.save_crypto_info("foo", "bar")
+        f = self.db.fetch_crypto_info()
+        self.assertListEqual([u'foo', u'bar'], list(f))
+
+    def test_3_add_node(self):
+        node = Node(clear_text=True,
+                    **{'username': u"alice", 'password': u"secret",
+                       'url': u"wonderland.com",
+                       'notes': u"a really great place",
+                       'tags': [u'foo', u'bar']})
+        self.db.add_node(node)
+        rv = self.db._cur.execute("select * from node")
+        # clearly this fails, while alice is not found in clear text in the
+        # database!
+        ce = CryptoEngine.get()
+        res = rv.fetchone()
+        self.assertIn(ce.encrypt(u'alice'), res[1])
+
+    def test_4_test_tags(self):
+        node = Node(clear_text=True,
+                    **{'username': u"alice", 'password': u"secret",
+                       'url': u"wonderland.com",
+                       'notes': u"a really great place",
+                       'tags': [u'foo', u'bar']})
+        ce = CryptoEngine.get()
+        self.db._get_or_create_tag(node._tags[0])
+        self.assertEqual(1, self.db._get_or_create_tag(node._tags[0]))
+        self.assertEqual(3, self.db._get_or_create_tag(ce.encrypt('baz')))
+
+    def test_5_test_lookup(self):
+        self.db._cur.execute('SELECT * FROM LOOKUP')
+        rows = self.db._cur.fetchall()
+        self.assertEqual(2, len(rows))
+
+    def test_6_listnodes(self):
+        node = Node(clear_text=True,
+                    **{'username': u"hatman", 'password': u"secret",
+                       'url': u"wonderland.com",
+                       'notes': u"a really great place",
+                       'tags': [u'baz', u'bar']})
+        self.db.add_node(node)
+        ids = self.db.listnodes()
+        self.assertEqual(2, len(ids))
+
+    def test_7_listnodes_w_filter(self):
+        ce = CryptoEngine.get()
+        tag = ce.encrypt(u'bar')
+        rv = self.db.listnodes(tag)
+        self.assertEqual(len(rv), 2)
+        tag = ce.encrypt(u'baz')
+        rv = self.db.listnodes(tag)
+        self.assertEqual(len(rv), 1)
+
+    def tearDown(self):
+        self.db.close()
+
+if __name__ == '__main__':
+    try:
+        unittest.main(verbosity=2)
+    except SystemExit:
+        os.remove('test.db')