From 6eae782f13953dd0ba2bbe1b582cf33fd4d7d90a Mon Sep 17 00:00:00 2001 From: RaNaN Date: Mon, 19 Dec 2011 23:10:49 +0100 Subject: configparser v2, warning CONFIG will be DELETED. --- module/database/DatabaseBackend.py | 40 ++++---------------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 9530390c3..db8b1aa3c 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -138,7 +138,6 @@ class DatabaseBackend(Thread): self._convertDB(convert) self._createTables() - self._migrateUser() self.conn.commit() @@ -191,22 +190,10 @@ class DatabaseBackend(Thread): print "Filedatabase could NOT be converted." #--convert scripts start - - def _convertV2(self): - self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') - try: - self.manager.core.log.info(_("Database was converted from v2 to v3.")) - except: - print "Database was converted from v2 to v3." - self._convertV3() - - def _convertV3(self): - self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') - try: - self.manager.core.log.info(_("Database was converted from v3 to v4.")) - except: - print "Database was converted from v3 to v4." - + + def _convertV4(self): + pass + #--convert scripts end def _createTables(self): @@ -246,25 +233,6 @@ class DatabaseBackend(Thread): self.c.execute('VACUUM') - def _migrateUser(self): - if exists("pyload.db"): - try: - self.core.log.info(_("Converting old Django DB")) - except: - print "Converting old Django DB" - conn = sqlite3.connect('pyload.db') - c = conn.cursor() - c.execute("SELECT username, password, email from auth_user WHERE is_superuser") - users = [] - for r in c: - pw = r[1].split("$") - users.append((r[0], pw[1] + pw[2], r[2])) - c.close() - conn.close() - - self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users) - move("pyload.db", "pyload.old.db") - def createCursor(self): return self.conn.cursor() -- cgit v1.2.3 From 958bf611f5d9d117f19f824990ec6fd6b537e967 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Thu, 22 Dec 2011 23:45:38 +0100 Subject: accountmanager v2, delete your accounts.conf and re-enter them in pyload, new nice debug functions, try core.shell() and core.breakpoint() --- module/database/DatabaseBackend.py | 190 ++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 86 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index db8b1aa3c..e10bcbbaf 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -32,55 +32,58 @@ try: except: import sqlite3 +DB = None DB_VERSION = 4 -class style(): - db = None - - @classmethod - def setDB(cls, db): - cls.db = db - - @classmethod - def inner(cls, f): - @staticmethod - def x(*args, **kwargs): - if cls.db: - return f(cls.db, *args, **kwargs) - return x - - @classmethod - def queue(cls, f): - @staticmethod - def x(*args, **kwargs): - if cls.db: - return cls.db.queue(f, *args, **kwargs) - return x - - @classmethod - def async(cls, f): - @staticmethod - def x(*args, **kwargs): - if cls.db: - return cls.db.async(f, *args, **kwargs) - return x +def set_DB(db): + global DB + DB = db + + +def queue(f): + @staticmethod + def x(*args, **kwargs): + if DB: + return DB.queue(f, *args, **kwargs) + + return x + + +def async(f): + @staticmethod + def x(*args, **kwargs): + if DB: + return DB.async(f, *args, **kwargs) + + return x + + +def inner(f): + @staticmethod + def x(*args, **kwargs): + if DB: + return f(DB, *args, **kwargs) + + return x + class DatabaseJob(): def __init__(self, f, *args, **kwargs): self.done = Event() - + self.f = f self.args = args self.kwargs = kwargs - + self.result = None self.exception = False -# import inspect -# self.frame = inspect.currentframe() + # import inspect + # self.frame = inspect.currentframe() def __repr__(self): from os.path import basename + frame = self.frame.f_back output = "" for i in range(5): @@ -104,45 +107,50 @@ class DatabaseJob(): self.exception = e finally: self.done.set() - + def wait(self): self.done.wait() + class DatabaseBackend(Thread): subs = [] + + DB_FILE = "pyload.db" + VERSION_FILE = "db.version" + def __init__(self, core): Thread.__init__(self) self.setDaemon(True) self.core = core self.jobs = Queue() - + self.setuplock = Event() - - style.setDB(self) - + + set_DB(self) + def setup(self): self.start() self.setuplock.wait() - + def run(self): """main loop, which executes commands""" convert = self._checkVersion() #returns None or current version - - self.conn = sqlite3.connect("files.db") - chmod("files.db", 0600) + + self.conn = sqlite3.connect(self.DB_FILE) + chmod(self.DB_FILE, 0600) self.c = self.conn.cursor() #compatibility - + if convert is not None: self._convertDB(convert) - + self._createTables() self.conn.commit() - + self.setuplock.set() - + while True: j = self.jobs.get() if j == "quit": @@ -151,20 +159,23 @@ class DatabaseBackend(Thread): break j.processJob() - @style.queue + @queue def shutdown(self): self.conn.commit() self.jobs.put("quit") def _checkVersion(self): """ check db version and delete it if needed""" - if not exists("files.version"): - f = open("files.version", "wb") + if not exists(self.VERSION_FILE): + f = open(self.VERSION_FILE, "wb") f.write(str(DB_VERSION)) f.close() return - - f = open("files.version", "rb") + + if exists("files.db") and not exists(self.DB_FILE): + move("files.db", self.DB_FILE) + + f = open(self.VERSION_FILE, "rb") v = int(f.read().strip()) f.close() if v < DB_VERSION: @@ -173,13 +184,13 @@ class DatabaseBackend(Thread): self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version.")) except: print "Filedatabase was deleted due to incompatible version." - remove("files.version") - move("files.db", "files.backup.db") - f = open("files.version", "wb") + remove(self.VERSION_FILE) + move(self.DB_FILE, self.DB_FILE + ".backup") + f = open(self.VERSION_FILE, "wb") f.write(str(DB_VERSION)) f.close() return v - + def _convertDB(self, v): try: getattr(self, "_convertV%i" % v)() @@ -188,22 +199,28 @@ class DatabaseBackend(Thread): self.core.log.error(_("Filedatabase could NOT be converted.")) except: print "Filedatabase could NOT be converted." - + #--convert scripts start def _convertV4(self): pass #--convert scripts end - + def _createTables(self): """create tables for database""" - self.c.execute('CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)') - self.c.execute('CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))') self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)') - self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') - self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "users" ("name" TEXT PRIMARY KEY NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "accounts" ("plugin" TEXT NOT NULL, "loginname" TEXT NOT NULL, "activated" INTEGER DEFAULT 1, "password" TEXT DEFAULT "", "options" TEXT DEFAULT "", PRIMARY KEY (plugin, loginname) ON CONFLICT REPLACE)') self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \ SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\ @@ -221,7 +238,6 @@ class DatabaseBackend(Thread): fid = 0 self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links")) - self.c.execute('SELECT max(id) FROM packages') pid = self.c.fetchone()[0] if pid: @@ -235,39 +251,39 @@ class DatabaseBackend(Thread): def createCursor(self): return self.conn.cursor() - - @style.async + + @async def commit(self): self.conn.commit() - @style.queue + @queue def syncSave(self): self.conn.commit() - - @style.async + + @async def rollback(self): self.conn.rollback() - + def async(self, f, *args, **kwargs): args = (self, ) + args job = DatabaseJob(f, *args, **kwargs) self.jobs.put(job) - + def queue(self, f, *args, **kwargs): args = (self, ) + args job = DatabaseJob(f, *args, **kwargs) self.jobs.put(job) job.wait() return job.result - + @classmethod def registerSub(cls, klass): cls.subs.append(klass) - + @classmethod def unregisterSub(cls, klass): cls.subs.remove(klass) - + def __getattr__(self, attr): for sub in DatabaseBackend.subs: if hasattr(sub, attr): @@ -276,45 +292,47 @@ class DatabaseBackend(Thread): if __name__ == "__main__": db = DatabaseBackend() db.setup() - + class Test(): - @style.queue + @queue def insert(db): c = db.createCursor() for i in range(1000): c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar")) - @style.async + + @async def insert2(db): c = db.createCursor() - for i in range(1000*1000): + for i in range(1000 * 1000): c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar")) - - @style.queue + + @queue def select(db): c = db.createCursor() for i in range(10): res = c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", ("foo", i)) print res.fetchone() - - @style.queue + + @queue def error(db): c = db.createCursor() print "a" c.execute("SELECT myerror FROM storage WHERE identifier=? AND key=?", ("foo", i)) print "e" - + db.registerSub(Test) from time import time + start = time() for i in range(100): db.insert() end = time() - print end-start - + print end - start + start = time() db.insert2() end = time() - print end-start - + print end - start + db.error() -- cgit v1.2.3 From 1ecdd9f6b53fec45e1d48592e3ff56aa7a576bec Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 8 Jan 2012 16:47:52 +0100 Subject: some cleanups, closed #490 --- module/database/DatabaseBackend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index e10bcbbaf..32f75328c 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -25,7 +25,7 @@ from shutil import move from Queue import Queue from traceback import print_exc -from module.utils import chmod +from module.utils.fs import chmod try: from pysqlite2 import dbapi2 as sqlite3 -- cgit v1.2.3 From 4df2b77fdf42046fe19bd371be7c7255986b5980 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 6 Mar 2012 13:36:39 +0100 Subject: renamed hooks to addons, new filemanager and database, many new api methods you will loose ALL your LINKS, webinterface will NOT work --- module/database/DatabaseBackend.py | 237 +++++++++++++++++++++++++++---------- 1 file changed, 173 insertions(+), 64 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 32f75328c..8159446bd 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -16,16 +16,13 @@ @author: RaNaN @author: mkaay """ -from threading import Thread -from threading import Event -from os import remove -from os.path import exists +from threading import Thread, Event from shutil import move from Queue import Queue from traceback import print_exc -from module.utils.fs import chmod +from module.utils.fs import chmod, exists, remove try: from pysqlite2 import dbapi2 as sqlite3 @@ -33,7 +30,7 @@ except: import sqlite3 DB = None -DB_VERSION = 4 +DB_VERSION = 5 def set_DB(db): global DB @@ -67,6 +64,18 @@ def inner(f): return x +class DatabaseMethods: + # stubs for autocompletion + core = None + manager = None + conn = None + c = None + + @classmethod + def register(cls): + DatabaseBackend.registerSub(cls) + + class DatabaseJob(): def __init__(self, f, *args, **kwargs): self.done = Event() @@ -122,34 +131,60 @@ class DatabaseBackend(Thread): Thread.__init__(self) self.setDaemon(True) self.core = core + self.manager = None # setted later + self.running = Event() self.jobs = Queue() - self.setuplock = Event() - set_DB(self) def setup(self): + self.start() - self.setuplock.wait() + self.running.wait() - def run(self): + def init(self): """main loop, which executes commands""" - convert = self._checkVersion() #returns None or current version + + version = self._checkVersion() self.conn = sqlite3.connect(self.DB_FILE) chmod(self.DB_FILE, 0600) - self.c = self.conn.cursor() #compatibility + self.c = self.conn.cursor() - if convert is not None: - self._convertDB(convert) + if version is not None and version < DB_VERSION: + success = self._convertDB(version) - self._createTables() + # delete database + if not success: + self.c.close() + self.conn.close() + try: + self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version.")) + except: + print "Filedatabase was deleted due to incompatible version." + + remove(self.VERSION_FILE) + move(self.DB_FILE, self.DB_FILE + ".backup") + f = open(self.VERSION_FILE, "wb") + f.write(str(DB_VERSION)) + f.close() + + self.conn = sqlite3.connect(self.DB_FILE) + chmod(self.DB_FILE, 0600) + self.c = self.conn.cursor() + + self._createTables() self.conn.commit() - self.setuplock.set() + + def run(self): + try: + self.init() + finally: + self.running.set() while True: j = self.jobs.get() @@ -159,51 +194,40 @@ class DatabaseBackend(Thread): break j.processJob() - @queue + def shutdown(self): + self.running.clear() + self._shutdown() + + @queue + def _shutdown(self): self.conn.commit() self.jobs.put("quit") def _checkVersion(self): - """ check db version and delete it if needed""" + """ get db version""" if not exists(self.VERSION_FILE): f = open(self.VERSION_FILE, "wb") f.write(str(DB_VERSION)) f.close() return - if exists("files.db") and not exists(self.DB_FILE): - move("files.db", self.DB_FILE) - f = open(self.VERSION_FILE, "rb") v = int(f.read().strip()) f.close() - if v < DB_VERSION: - if v < 2: - try: - self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version.")) - except: - print "Filedatabase was deleted due to incompatible version." - remove(self.VERSION_FILE) - move(self.DB_FILE, self.DB_FILE + ".backup") - f = open(self.VERSION_FILE, "wb") - f.write(str(DB_VERSION)) - f.close() - return v + + return v def _convertDB(self, v): try: - getattr(self, "_convertV%i" % v)() + return getattr(self, "_convertV%i" % v)() except: - try: - self.core.log.error(_("Filedatabase could NOT be converted.")) - except: - print "Filedatabase could NOT be converted." + return False #--convert scripts start - def _convertV4(self): - pass + def _convertV5(self): + return False #--convert scripts end @@ -211,39 +235,122 @@ class DatabaseBackend(Thread): """create tables for database""" self.c.execute( - 'CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)') + 'CREATE TABLE IF NOT EXISTS "packages" (' + '"pid" INTEGER PRIMARY KEY AUTOINCREMENT, ' + '"name" TEXT NOT NULL, ' + '"folder" TEXT DEFAULT "" NOT NULL, ' + '"site" TEXT DEFAULT "" NOT NULL, ' + '"comment" TEXT DEFAULT "" NOT NULL, ' + '"password" TEXT DEFAULT "" NOT NULL, ' + '"added" INTEGER DEFAULT 0 NOT NULL,' # set by trigger + '"status" INTEGER DEFAULT 0 NOT NULL,' + '"packageorder" INTEGER DEFAULT -1 NOT NULL,' #incremented by trigger + '"root" INTEGER DEFAULT -1 NOT NULL,' + 'CHECK (root != pid) ' + ')' + ) + self.c.execute( - 'CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))') - self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)') + 'CREATE TRIGGER IF NOT EXISTS "insert_package" AFTER INSERT ON "packages"' + 'BEGIN ' + 'UPDATE packages SET added = strftime("%s", "now"), ' + 'packageorder = (SELECT max(p.packageorder) + 1 FROM packages p WHERE p.root=new.root) ' + 'WHERE rowid = new.rowid;' + 'END' + ) + self.c.execute( - 'CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') + 'CREATE TRIGGER IF NOT EXISTS "delete_package" AFTER DELETE ON "packages"' + 'BEGIN ' + 'DELETE FROM files WHERE package = old.pid;' + 'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > old.packageorder AND root=old.pid;' + 'END' + ) + + self.c.execute('CREATE INDEX IF NOT EXISTS "root_index" ON packages(root)') + self.c.execute( - 'CREATE TABLE IF NOT EXISTS "users" ("name" TEXT PRIMARY KEY NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') + 'CREATE TABLE IF NOT EXISTS "files" (' + '"fid" INTEGER PRIMARY KEY AUTOINCREMENT, ' + '"name" TEXT NOT NULL, ' + '"size" INTEGER DEFAULT 0 NOT NULL, ' + '"status" INTEGER DEFAULT 0 NOT NULL, ' + '"media" INTEGER DEFAULT 1 NOT NULL,' + '"added" INTEGER DEFAULT 0 NOT NULL,' + '"fileorder" INTEGER DEFAULT -1 NOT NULL, ' + '"url" TEXT DEFAULT "" NOT NULL, ' + '"plugin" TEXT DEFAULT "" NOT NULL, ' + '"hash" TEXT DEFAULT "" NOT NULL, ' + '"dlstatus" INTEGER DEFAULT 0 NOT NULL, ' + '"error" TEXT DEFAULT "" NOT NULL, ' + '"package" INTEGER NOT NULL, ' + 'FOREIGN KEY(package) REFERENCES packages(id)' + ')' + ) + + self.c.execute('CREATE INDEX IF NOT EXISTS "package_index" ON files(package)') + self.c.execute( - 'CREATE TABLE IF NOT EXISTS "accounts" ("plugin" TEXT NOT NULL, "loginname" TEXT NOT NULL, "activated" INTEGER DEFAULT 1, "password" TEXT DEFAULT "", "options" TEXT DEFAULT "", PRIMARY KEY (plugin, loginname) ON CONFLICT REPLACE)') + 'CREATE TRIGGER IF NOT EXISTS "insert_file" AFTER INSERT ON "files"' + 'BEGIN ' + 'UPDATE files SET added = strftime("%s", "now"), ' + 'fileorder = (SELECT max(f.fileorder) + 1 FROM files f WHERE f.package=new.package) ' + 'WHERE rowid = new.rowid;' + 'END' + ) - self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \ - SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\ - FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\ - (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \ - FROM packages p JOIN links l ON p.id = l.package AND l.status in (0,4,13) GROUP BY p.id) s ON s.id = p.id \ - GROUP BY p.id') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "collector" (' + '"url" TEXT NOT NULL, ' + '"name" TEXT NOT NULL, ' + '"plugin" TEXT DEFAULT "BasePlugin" NOT NULL, ' + '"size" INTEGER DEFAULT 0 NOT NULL, ' + '"status" INTEGER DEFAULT 3 NOT NULL, ' + '"packagename" TEXT DEFAULT "" NOT NULL, ' + 'PRIMARY KEY (url, packagename) ON CONFLICT REPLACE' + ') ' + ) + + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "storage" (' + '"identifier" TEXT NOT NULL, ' + '"key" TEXT NOT NULL, ' + '"value" TEXT DEFAULT "", ' + 'PRIMARY KEY (identifier, key) ON CONFLICT REPLACE' + ')' + ) + + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "users" (' + '"name" TEXT PRIMARY KEY NOT NULL, ' + '"email" TEXT DEFAULT "" NOT NULL, ' + '"password" TEXT NOT NULL, ' + '"role" INTEGER DEFAULT 0 NOT NULL, ' + '"permission" INTEGER DEFAULT 0 NOT NULL, ' + '"template" TEXT DEFAULT "default" NOT NULL' + ')' + ) + + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "accounts" (' + '"plugin" TEXT NOT NULL, ' + '"loginname" TEXT NOT NULL, ' + '"activated" INTEGER DEFAULT 1, ' + '"password" TEXT DEFAULT "", ' + '"options" TEXT DEFAULT "", ' + 'PRIMARY KEY (plugin, loginname) ON CONFLICT REPLACE' + ')' + ) #try to lower ids - self.c.execute('SELECT max(id) FROM LINKS') + self.c.execute('SELECT max(fid) FROM files') fid = self.c.fetchone()[0] - if fid: - fid = int(fid) - else: - fid = 0 - self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links")) + fid = int(fid) if fid else 0 + self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "files")) - self.c.execute('SELECT max(id) FROM packages') + self.c.execute('SELECT max(pid) FROM packages') pid = self.c.fetchone()[0] - if pid: - pid = int(pid) - else: - pid = 0 + pid = int(pid) if pid else 0 self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages")) self.c.execute('VACUUM') @@ -273,7 +380,8 @@ class DatabaseBackend(Thread): args = (self, ) + args job = DatabaseJob(f, *args, **kwargs) self.jobs.put(job) - job.wait() + # only wait when db is running + if self.running.isSet(): job.wait() return job.result @classmethod @@ -288,6 +396,7 @@ class DatabaseBackend(Thread): for sub in DatabaseBackend.subs: if hasattr(sub, attr): return getattr(sub, attr) + raise AttributeError(attr) if __name__ == "__main__": db = DatabaseBackend() -- cgit v1.2.3 From b40b32ee05f611323a7827fad2a25fa0a28dcb24 Mon Sep 17 00:00:00 2001 From: X3n0m0rph59 Date: Sun, 22 Apr 2012 19:56:17 +0200 Subject: a huge pile of spelling fixes --- module/database/DatabaseBackend.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 8159446bd..97ecec3ab 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -131,7 +131,7 @@ class DatabaseBackend(Thread): Thread.__init__(self) self.setDaemon(True) self.core = core - self.manager = None # setted later + self.manager = None # set later self.running = Event() self.jobs = Queue() @@ -162,9 +162,9 @@ class DatabaseBackend(Thread): self.conn.close() try: - self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version.")) + self.manager.core.log.warning(_("File database was deleted due to incompatible version.")) except: - print "Filedatabase was deleted due to incompatible version." + print "File database was deleted due to incompatible version." remove(self.VERSION_FILE) move(self.DB_FILE, self.DB_FILE + ".backup") -- cgit v1.2.3 From f36bb35cf296c74ff5a3676c038e2ef2a8be9068 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 6 May 2012 17:22:13 +0200 Subject: concept for multiuser api --- module/database/DatabaseBackend.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 97ecec3ab..18898a93c 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -327,6 +327,7 @@ class DatabaseBackend(Thread): '"password" TEXT NOT NULL, ' '"role" INTEGER DEFAULT 0 NOT NULL, ' '"permission" INTEGER DEFAULT 0 NOT NULL, ' + '"folder" TEXT DEFAULT "" NOT NULL, ' '"template" TEXT DEFAULT "default" NOT NULL' ')' ) @@ -338,6 +339,8 @@ class DatabaseBackend(Thread): '"activated" INTEGER DEFAULT 1, ' '"password" TEXT DEFAULT "", ' '"options" TEXT DEFAULT "", ' +# '"user" TEXT NOT NULL, ' +# 'FOREIGN KEY(user) REFERENCES users(name)' 'PRIMARY KEY (plugin, loginname) ON CONFLICT REPLACE' ')' ) -- cgit v1.2.3 From 3d7ed0c367cf521b61ec1f78784dec73a3b7c32a Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 6 May 2012 20:40:39 +0200 Subject: some classes for multi user mode --- module/database/DatabaseBackend.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 18898a93c..6373120ff 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -322,13 +322,36 @@ class DatabaseBackend(Thread): self.c.execute( 'CREATE TABLE IF NOT EXISTS "users" (' - '"name" TEXT PRIMARY KEY NOT NULL, ' + '"id" INTEGER PRIMARY KEY AUTOINCREMENT, ' + '"name" TEXT NOT NULL, ' '"email" TEXT DEFAULT "" NOT NULL, ' '"password" TEXT NOT NULL, ' '"role" INTEGER DEFAULT 0 NOT NULL, ' '"permission" INTEGER DEFAULT 0 NOT NULL, ' '"folder" TEXT DEFAULT "" NOT NULL, ' - '"template" TEXT DEFAULT "default" NOT NULL' + '"ratio" INTEGER DEFAULT -1 NOT NULL, ' + '"limit" INTEGER DEFAULT -1 NOT NULL, ' + '"template" TEXT DEFAULT "default" NOT NULL, ' + '"user" INTEGER DEFAULT -1 NOT NULL, ' + 'FOREIGN KEY(user) REFERENCES users(id)' + ')' + ) + + self.c.execute( + 'CREATE TRIGGER IF NOT EXISTS "insert_user" AFTER INSERT ON "users"' + 'BEGIN ' + 'UPDATE users SET user = new.id ' + 'WHERE rowid = new.rowid;' + 'END' + ) + + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "settings" (' + '"plugin" TEXT NOT NULL, ' + '"owner" INTEGER NOT NULL, ' + '"configuration" TEXT NOT NULL, ' + 'FOREIGN KEY(owner) REFERENCES users(id), ' + 'PRIMARY KEY (plugin, owner) ON CONFLICT REPLACE' ')' ) @@ -339,8 +362,8 @@ class DatabaseBackend(Thread): '"activated" INTEGER DEFAULT 1, ' '"password" TEXT DEFAULT "", ' '"options" TEXT DEFAULT "", ' -# '"user" TEXT NOT NULL, ' -# 'FOREIGN KEY(user) REFERENCES users(name)' +# '"owner" INTEGER NOT NULL, ' TODO: shared, owner attribute +# 'FOREIGN KEY(owner) REFERENCES users(id)' 'PRIMARY KEY (plugin, loginname) ON CONFLICT REPLACE' ')' ) -- cgit v1.2.3 From 84efa9d5ccd46a0adddc256a5eba4f8e33c76afd Mon Sep 17 00:00:00 2001 From: RaNaN Date: Mon, 7 May 2012 18:42:29 +0200 Subject: new multiuser api methods --- module/database/DatabaseBackend.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 6373120ff..516aa981f 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -301,13 +301,10 @@ class DatabaseBackend(Thread): self.c.execute( 'CREATE TABLE IF NOT EXISTS "collector" (' - '"url" TEXT NOT NULL, ' - '"name" TEXT NOT NULL, ' - '"plugin" TEXT DEFAULT "BasePlugin" NOT NULL, ' - '"size" INTEGER DEFAULT 0 NOT NULL, ' - '"status" INTEGER DEFAULT 3 NOT NULL, ' - '"packagename" TEXT DEFAULT "" NOT NULL, ' - 'PRIMARY KEY (url, packagename) ON CONFLICT REPLACE' + '"owner" INTEGER NOT NULL, ' + '"data" TEXT NOT NULL, ' + 'FOREIGN KEY(owner) REFERENCES users(uid)' + 'PRIMARY KEY(owner) ON CONFLICT REPLACE' ') ' ) @@ -322,25 +319,27 @@ class DatabaseBackend(Thread): self.c.execute( 'CREATE TABLE IF NOT EXISTS "users" (' - '"id" INTEGER PRIMARY KEY AUTOINCREMENT, ' + '"uid" INTEGER PRIMARY KEY AUTOINCREMENT, ' '"name" TEXT NOT NULL, ' '"email" TEXT DEFAULT "" NOT NULL, ' '"password" TEXT NOT NULL, ' '"role" INTEGER DEFAULT 0 NOT NULL, ' '"permission" INTEGER DEFAULT 0 NOT NULL, ' '"folder" TEXT DEFAULT "" NOT NULL, ' - '"ratio" INTEGER DEFAULT -1 NOT NULL, ' - '"limit" INTEGER DEFAULT -1 NOT NULL, ' + '"traffic" INTEGER DEFAULT -1 NOT NULL, ' + '"dllimit" INTEGER DEFAULT -1 NOT NULL, ' '"template" TEXT DEFAULT "default" NOT NULL, ' - '"user" INTEGER DEFAULT -1 NOT NULL, ' - 'FOREIGN KEY(user) REFERENCES users(id)' + '"user" INTEGER DEFAULT -1 NOT NULL, ' # set by trigger to self + 'FOREIGN KEY(user) REFERENCES users(uid)' ')' ) + self.c.execute('CREATE INDEX IF NOT EXISTS "username_index" ON users(name)') + self.c.execute( 'CREATE TRIGGER IF NOT EXISTS "insert_user" AFTER INSERT ON "users"' 'BEGIN ' - 'UPDATE users SET user = new.id ' + 'UPDATE users SET user = new.uid, folder=new.name ' 'WHERE rowid = new.rowid;' 'END' ) @@ -350,7 +349,7 @@ class DatabaseBackend(Thread): '"plugin" TEXT NOT NULL, ' '"owner" INTEGER NOT NULL, ' '"configuration" TEXT NOT NULL, ' - 'FOREIGN KEY(owner) REFERENCES users(id), ' + 'FOREIGN KEY(owner) REFERENCES users(uid), ' 'PRIMARY KEY (plugin, owner) ON CONFLICT REPLACE' ')' ) @@ -359,12 +358,13 @@ class DatabaseBackend(Thread): 'CREATE TABLE IF NOT EXISTS "accounts" (' '"plugin" TEXT NOT NULL, ' '"loginname" TEXT NOT NULL, ' + '"owner", INTEGER NOT NULL, ' '"activated" INTEGER DEFAULT 1, ' '"password" TEXT DEFAULT "", ' + '"shared" INTEGER DEFAULT 0, ' '"options" TEXT DEFAULT "", ' -# '"owner" INTEGER NOT NULL, ' TODO: shared, owner attribute -# 'FOREIGN KEY(owner) REFERENCES users(id)' - 'PRIMARY KEY (plugin, loginname) ON CONFLICT REPLACE' + 'FOREIGN KEY(owner) REFERENCES users(uid)' + 'PRIMARY KEY (plugin, loginname, owner) ON CONFLICT REPLACE' ')' ) -- cgit v1.2.3 From 829244a6140763712d50ed046c33f415f2b04301 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Tue, 15 May 2012 19:22:34 +0200 Subject: some multiuser db changes --- module/database/DatabaseBackend.py | 48 +++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 516aa981f..ec39e3fd9 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -1,21 +1,21 @@ #!/usr/bin/env python -""" - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, - or (at your option) any later version. - - This program 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 this program; if not, see . - - @author: RaNaN - @author: mkaay -""" +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2012 pyLoad Team +# http://www.pyload.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Subjected to the terms and conditions in LICENSE +# +# @author: RaNaN +# @author: mkaay +############################################################################### + from threading import Thread, Event from shutil import move @@ -245,8 +245,10 @@ class DatabaseBackend(Thread): '"added" INTEGER DEFAULT 0 NOT NULL,' # set by trigger '"status" INTEGER DEFAULT 0 NOT NULL,' '"packageorder" INTEGER DEFAULT -1 NOT NULL,' #incremented by trigger - '"root" INTEGER DEFAULT -1 NOT NULL,' - 'CHECK (root != pid) ' + '"root" INTEGER DEFAULT -1 NOT NULL, ' + '"owner" INTEGER NOT NULL, ' + 'FOREIGN KEY(owner) REFERENCES users(uid), ' + 'CHECK (root != pid)' ')' ) @@ -267,7 +269,8 @@ class DatabaseBackend(Thread): 'END' ) - self.c.execute('CREATE INDEX IF NOT EXISTS "root_index" ON packages(root)') + self.c.execute('CREATE INDEX IF NOT EXISTS "package_index" ON packages(root, owner)') + self.c.execute('CREATE INDEX IF NOT EXISTS "package_owner" ON packages(owner)') self.c.execute( 'CREATE TABLE IF NOT EXISTS "files" (' @@ -284,11 +287,14 @@ class DatabaseBackend(Thread): '"dlstatus" INTEGER DEFAULT 0 NOT NULL, ' '"error" TEXT DEFAULT "" NOT NULL, ' '"package" INTEGER NOT NULL, ' + '"owner" INTEGER NOT NULL, ' + 'FOREIGN KEY(owner) REFERENCES users(uid), ' 'FOREIGN KEY(package) REFERENCES packages(id)' ')' ) - self.c.execute('CREATE INDEX IF NOT EXISTS "package_index" ON files(package)') + self.c.execute('CREATE INDEX IF NOT EXISTS "file_index" ON files(package, owner)') + self.c.execute('CREATE INDEX IF NOT EXISTS "file_owner" ON files(owner)') self.c.execute( 'CREATE TRIGGER IF NOT EXISTS "insert_file" AFTER INSERT ON "files"' -- cgit v1.2.3 From 0d2d6daef850ac6bcc7fafccd230e52d2a862c2c Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 3 Jun 2012 17:45:10 +0200 Subject: updates for database + api --- module/database/DatabaseBackend.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index ec39e3fd9..2c494e520 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -5,15 +5,15 @@ # Copyright(c) 2008-2012 pyLoad Team # http://www.pyload.org # -# This program is free software: you can redistribute it and/or modify +# This file is part of pyLoad. +# pyLoad is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # Subjected to the terms and conditions in LICENSE # -# @author: RaNaN -# @author: mkaay +# @author: RaNaN, mkaay ############################################################################### from threading import Thread, Event @@ -268,7 +268,6 @@ class DatabaseBackend(Thread): 'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > old.packageorder AND root=old.pid;' 'END' ) - self.c.execute('CREATE INDEX IF NOT EXISTS "package_index" ON packages(root, owner)') self.c.execute('CREATE INDEX IF NOT EXISTS "package_owner" ON packages(owner)') @@ -292,7 +291,6 @@ class DatabaseBackend(Thread): 'FOREIGN KEY(package) REFERENCES packages(id)' ')' ) - self.c.execute('CREATE INDEX IF NOT EXISTS "file_index" ON files(package, owner)') self.c.execute('CREATE INDEX IF NOT EXISTS "file_owner" ON files(owner)') @@ -309,7 +307,7 @@ class DatabaseBackend(Thread): 'CREATE TABLE IF NOT EXISTS "collector" (' '"owner" INTEGER NOT NULL, ' '"data" TEXT NOT NULL, ' - 'FOREIGN KEY(owner) REFERENCES users(uid)' + 'FOREIGN KEY(owner) REFERENCES users(uid), ' 'PRIMARY KEY(owner) ON CONFLICT REPLACE' ') ' ) @@ -326,7 +324,7 @@ class DatabaseBackend(Thread): self.c.execute( 'CREATE TABLE IF NOT EXISTS "users" (' '"uid" INTEGER PRIMARY KEY AUTOINCREMENT, ' - '"name" TEXT NOT NULL, ' + '"name" TEXT NOT NULL UNIQUE, ' '"email" TEXT DEFAULT "" NOT NULL, ' '"password" TEXT NOT NULL, ' '"role" INTEGER DEFAULT 0 NOT NULL, ' @@ -334,12 +332,13 @@ class DatabaseBackend(Thread): '"folder" TEXT DEFAULT "" NOT NULL, ' '"traffic" INTEGER DEFAULT -1 NOT NULL, ' '"dllimit" INTEGER DEFAULT -1 NOT NULL, ' + '"dlquota" TEXT DEFAULT "" NOT NULL, ' + '"hddquota" INTEGER DEFAULT -1 NOT NULL, ' '"template" TEXT DEFAULT "default" NOT NULL, ' '"user" INTEGER DEFAULT -1 NOT NULL, ' # set by trigger to self 'FOREIGN KEY(user) REFERENCES users(uid)' ')' ) - self.c.execute('CREATE INDEX IF NOT EXISTS "username_index" ON users(name)') self.c.execute( @@ -369,11 +368,24 @@ class DatabaseBackend(Thread): '"password" TEXT DEFAULT "", ' '"shared" INTEGER DEFAULT 0, ' '"options" TEXT DEFAULT "", ' - 'FOREIGN KEY(owner) REFERENCES users(uid)' + 'FOREIGN KEY(owner) REFERENCES users(uid), ' 'PRIMARY KEY (plugin, loginname, owner) ON CONFLICT REPLACE' ')' ) + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "stats" (' + '"user" INTEGER NOT NULL, ' + '"plugin" TEXT NOT NULL, ' + '"time" INTEGER NOT NULL, ' + '"premium" INTEGER DEFAULT 0 NOT NULL, ' + '"amount" INTEGER DEFAULT 0 NOT NULL, ' + 'FOREIGN KEY(user) REFERENCES users(uid), ' + 'PRIMARY KEY(user, plugin, time)' + ')' + ) + self.c.execute('CREATE INDEX IF NOT EXISTS "stats_time" ON stats(time)') + #try to lower ids self.c.execute('SELECT max(fid) FROM files') fid = self.c.fetchone()[0] -- cgit v1.2.3 From 560958b70043ea5b7e0e32d41cb51bd44696d775 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Sun, 9 Sep 2012 15:39:50 +0200 Subject: new config api --- module/database/DatabaseBackend.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 2c494e520..3e6b059c0 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -352,10 +352,10 @@ class DatabaseBackend(Thread): self.c.execute( 'CREATE TABLE IF NOT EXISTS "settings" (' '"plugin" TEXT NOT NULL, ' - '"owner" INTEGER NOT NULL, ' - '"configuration" TEXT NOT NULL, ' + '"user" INTEGER NOT NULL, ' + '"config" TEXT NOT NULL, ' 'FOREIGN KEY(owner) REFERENCES users(uid), ' - 'PRIMARY KEY (plugin, owner) ON CONFLICT REPLACE' + 'PRIMARY KEY (plugin, user) ON CONFLICT REPLACE' ')' ) -- cgit v1.2.3 From 54bc92b4c5e0b3543a313f497cbc2276403c5980 Mon Sep 17 00:00:00 2001 From: RaNaN Date: Mon, 10 Sep 2012 11:49:35 +0200 Subject: changed config + progress api --- module/database/DatabaseBackend.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'module/database/DatabaseBackend.py') diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py index 3e6b059c0..58e1e74d8 100644 --- a/module/database/DatabaseBackend.py +++ b/module/database/DatabaseBackend.py @@ -139,7 +139,7 @@ class DatabaseBackend(Thread): set_DB(self) def setup(self): - + """ *MUST* be called before db can be used !""" self.start() self.running.wait() @@ -352,9 +352,9 @@ class DatabaseBackend(Thread): self.c.execute( 'CREATE TABLE IF NOT EXISTS "settings" (' '"plugin" TEXT NOT NULL, ' - '"user" INTEGER NOT NULL, ' + '"user" INTEGER DEFAULT -1 NOT NULL, ' '"config" TEXT NOT NULL, ' - 'FOREIGN KEY(owner) REFERENCES users(uid), ' + 'FOREIGN KEY(user) REFERENCES users(uid), ' 'PRIMARY KEY (plugin, user) ON CONFLICT REPLACE' ')' ) -- cgit v1.2.3