2011-05-05 15:59:24 +02:00
|
|
|
# Local status cache virtual folder: SQLite backend
|
2017-01-31 01:41:26 +01:00
|
|
|
# Copyright (C) 2009-2017 Stewart Smith and contributors.
|
2011-05-05 15:59:24 +02:00
|
|
|
#
|
|
|
|
# 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 2 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, write to the Free Software
|
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
2016-06-06 15:18:29 +02:00
|
|
|
|
2014-08-02 20:23:31 +02:00
|
|
|
import os
|
2016-06-06 15:18:29 +02:00
|
|
|
import sqlite3 as sqlite
|
2015-01-11 21:44:24 +01:00
|
|
|
from sys import exc_info
|
2011-05-07 11:00:56 +02:00
|
|
|
from threading import Lock
|
2015-01-16 00:00:07 +01:00
|
|
|
from .Base import BaseFolder
|
|
|
|
|
2016-08-08 15:29:18 +02:00
|
|
|
|
2020-08-30 13:35:14 +02:00
|
|
|
class DatabaseFileLock:
|
2016-07-25 14:39:11 +02:00
|
|
|
"""Lock at database file level."""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self._lock = Lock()
|
|
|
|
self._counter = 0
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
self._lock.acquire()
|
|
|
|
|
|
|
|
def __exit__(self, typ, value, tb):
|
|
|
|
self._lock.release()
|
|
|
|
|
|
|
|
def registerNewUser(self):
|
|
|
|
self._counter += 1
|
|
|
|
|
|
|
|
def removeOneUser(self):
|
|
|
|
self._counter -= 1
|
|
|
|
|
|
|
|
def getLock(self):
|
|
|
|
return self._lock
|
|
|
|
|
|
|
|
def shouldClose(self):
|
|
|
|
return self._counter < 1
|
|
|
|
|
2011-08-16 12:16:46 +02:00
|
|
|
|
2013-07-28 13:58:30 +02:00
|
|
|
class LocalStatusSQLiteFolder(BaseFolder):
|
2011-05-07 11:00:56 +02:00
|
|
|
"""LocalStatus backend implemented with an SQLite database
|
|
|
|
|
|
|
|
As python-sqlite currently does not allow to access the same sqlite
|
|
|
|
objects from various threads, we need to open get and close a db
|
|
|
|
connection and cursor for all operations. This is a big disadvantage
|
|
|
|
and we might want to investigate if we cannot hold an object open
|
|
|
|
for a thread somehow."""
|
2015-02-13 17:43:29 +01:00
|
|
|
# Though. According to sqlite docs, you need to commit() before
|
|
|
|
# the connection is closed or your changes will be lost!
|
|
|
|
# get db connection which autocommits
|
|
|
|
# connection = sqlite.connect(self.filename, isolation_level=None)
|
|
|
|
# cursor = connection.cursor()
|
|
|
|
# return connection, cursor
|
2011-05-07 11:00:56 +02:00
|
|
|
|
2015-02-13 17:43:29 +01:00
|
|
|
# Current version of our db format.
|
2012-10-16 20:20:35 +02:00
|
|
|
cur_version = 2
|
2016-07-22 00:08:45 +02:00
|
|
|
# Keep track on how many threads need access to the database.
|
2020-08-29 19:45:15 +02:00
|
|
|
locks = {} # Key: filename, value: DatabaseFileLock instance.
|
2011-05-05 15:59:24 +02:00
|
|
|
|
2011-09-16 10:54:25 +02:00
|
|
|
def __init__(self, name, repository):
|
2020-08-29 19:45:15 +02:00
|
|
|
self.sep = '.' # Needs to be set before super().__init__().
|
2013-07-21 21:00:23 +02:00
|
|
|
super(LocalStatusSQLiteFolder, self).__init__(name, repository)
|
2013-07-28 13:58:30 +02:00
|
|
|
self.root = repository.root
|
|
|
|
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
|
|
|
|
|
2020-08-29 19:45:15 +02:00
|
|
|
self._newfolder = False # Flag if the folder is new.
|
2013-07-28 13:58:30 +02:00
|
|
|
|
2014-08-02 20:23:31 +02:00
|
|
|
dirname = os.path.dirname(self.filename)
|
|
|
|
if not os.path.exists(dirname):
|
|
|
|
os.makedirs(dirname)
|
|
|
|
if not os.path.isdir(dirname):
|
2020-08-29 19:45:15 +02:00
|
|
|
raise UserWarning("SQLite database path '%s' is not a directory." %
|
|
|
|
dirname)
|
2014-08-02 20:23:31 +02:00
|
|
|
|
2016-07-25 14:39:11 +02:00
|
|
|
self.connection = None
|
2016-07-26 02:56:36 +02:00
|
|
|
# The lock serialize the writing/open/close of database accross threads.
|
|
|
|
if self.filename not in LocalStatusSQLiteFolder.locks:
|
|
|
|
LocalStatusSQLiteFolder.locks[self.filename] = DatabaseFileLock()
|
|
|
|
self._databaseFileLock = LocalStatusSQLiteFolder.locks[self.filename]
|
2016-10-25 21:34:38 +02:00
|
|
|
self._in_transactions = 0
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
if not self.dofsync():
|
|
|
|
assert self.connection is not None
|
|
|
|
self._in_transactions += 1
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
if not self.dofsync():
|
|
|
|
assert self._in_transactions > 0
|
|
|
|
self._in_transactions -= 1
|
|
|
|
if self._in_transactions < 1:
|
|
|
|
self.connection.commit()
|
|
|
|
|
2016-05-12 18:27:14 +02:00
|
|
|
def openfiles(self):
|
2016-07-25 14:39:11 +02:00
|
|
|
# Make sure sqlite is in multithreading SERIALIZE mode.
|
|
|
|
assert sqlite.threadsafety == 1, 'Your sqlite is not multithreading safe.'
|
|
|
|
|
2016-07-26 02:56:36 +02:00
|
|
|
with self._databaseFileLock.getLock():
|
2016-07-22 00:08:45 +02:00
|
|
|
# Try to establish connection, no need for threadsafety in __init__.
|
|
|
|
try:
|
|
|
|
self.connection = sqlite.connect(self.filename,
|
|
|
|
check_same_thread=False)
|
2016-07-26 02:56:36 +02:00
|
|
|
self._databaseFileLock.registerNewUser()
|
2016-07-22 00:08:45 +02:00
|
|
|
except sqlite.OperationalError as e:
|
|
|
|
# Operation had failed.
|
2020-09-03 20:30:14 +02:00
|
|
|
raise UserWarning(
|
|
|
|
"cannot open database file '%s': %s.\nYou might"
|
|
|
|
" want to check the rights to that file and if "
|
|
|
|
"it cleanly opens with the 'sqlite<3>' command" %
|
|
|
|
(self.filename, e), exc_info()[2])
|
2016-07-22 00:08:45 +02:00
|
|
|
|
|
|
|
# Test if db version is current enough and if db is readable.
|
|
|
|
try:
|
|
|
|
cursor = self.connection.execute(
|
|
|
|
"SELECT value from metadata WHERE key='db_version'")
|
|
|
|
except sqlite.DatabaseError:
|
|
|
|
# db file missing or corrupt, recreate it.
|
|
|
|
self.__create_db()
|
|
|
|
else:
|
|
|
|
# Fetch db version and upgrade if needed.
|
|
|
|
version = int(cursor.fetchone()[0])
|
|
|
|
if version < LocalStatusSQLiteFolder.cur_version:
|
|
|
|
self.__upgrade_db(version)
|
2011-05-07 11:00:56 +02:00
|
|
|
|
2016-06-17 19:47:37 +02:00
|
|
|
def purge(self):
|
2016-08-08 15:29:18 +02:00
|
|
|
"""Remove any pre-existing database. Do not call in dry-run mode."""
|
2016-06-17 19:47:37 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
os.unlink(self.filename)
|
|
|
|
except OSError as e:
|
2020-08-29 19:45:15 +02:00
|
|
|
self.ui.debug('', "could not remove file %s: %s" %
|
|
|
|
(self.filename, e))
|
2013-07-28 13:58:30 +02:00
|
|
|
|
|
|
|
def storesmessages(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def getfullname(self):
|
|
|
|
return self.filename
|
|
|
|
|
|
|
|
# Interface from LocalStatusFolder
|
|
|
|
def isnewfolder(self):
|
|
|
|
return self._newfolder
|
|
|
|
|
2016-07-25 14:39:11 +02:00
|
|
|
def __sql_write(self, sql, args=None, executemany=False):
|
2012-01-07 22:39:59 +01:00
|
|
|
"""Execute some SQL, retrying if the db was locked.
|
2011-05-07 11:00:56 +02:00
|
|
|
|
2012-01-07 22:39:59 +01:00
|
|
|
:param sql: the SQL string passed to execute()
|
2016-07-25 14:39:11 +02:00
|
|
|
:param args: the variable values to `sql`. E.g. (1,2) or {uid:1,
|
2012-01-07 22:39:59 +01:00
|
|
|
flags:'T'}. See sqlite docs for possibilities.
|
|
|
|
:param executemany: bool indicating whether we want to
|
|
|
|
perform conn.executemany() or conn.execute().
|
2016-07-25 14:39:11 +02:00
|
|
|
:returns: None or raises an Exception."""
|
2015-01-14 22:58:25 +01:00
|
|
|
|
2011-05-07 11:00:56 +02:00
|
|
|
success = False
|
|
|
|
while not success:
|
|
|
|
try:
|
2016-07-26 02:56:36 +02:00
|
|
|
with self._databaseFileLock.getLock():
|
2016-07-25 14:39:11 +02:00
|
|
|
if args is None:
|
|
|
|
if executemany:
|
|
|
|
self.connection.executemany(sql)
|
|
|
|
else:
|
|
|
|
self.connection.execute(sql)
|
2012-01-07 22:39:59 +01:00
|
|
|
else:
|
2016-07-25 14:39:11 +02:00
|
|
|
if executemany:
|
|
|
|
self.connection.executemany(sql, args)
|
|
|
|
else:
|
|
|
|
self.connection.execute(sql, args)
|
|
|
|
success = True
|
2016-10-25 21:34:38 +02:00
|
|
|
if not self._in_transactions:
|
|
|
|
self.connection.commit()
|
2012-01-07 22:39:59 +01:00
|
|
|
except sqlite.OperationalError as e:
|
2011-05-07 11:00:56 +02:00
|
|
|
if e.args[0] == 'cannot commit - no transaction is active':
|
|
|
|
pass
|
|
|
|
elif e.args[0] == 'database is locked':
|
|
|
|
self.ui.debug('', "Locked sqlite database, retrying.")
|
|
|
|
success = False
|
|
|
|
else:
|
|
|
|
raise
|
2011-05-05 15:59:24 +02:00
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
def __upgrade_db(self, from_ver):
|
2011-05-05 15:59:24 +02:00
|
|
|
"""Upgrade the sqlite format from version 'from_ver' to current"""
|
2011-05-07 11:00:56 +02:00
|
|
|
|
2016-07-22 00:08:45 +02:00
|
|
|
if self.connection is not None:
|
2020-08-29 19:45:15 +02:00
|
|
|
self.connection.close() # Close old connections first.
|
2011-08-29 14:26:49 +02:00
|
|
|
self.connection = sqlite.connect(self.filename,
|
2016-07-16 18:00:59 +02:00
|
|
|
check_same_thread=False)
|
2011-05-07 11:00:56 +02:00
|
|
|
|
2012-10-16 20:20:35 +02:00
|
|
|
# Upgrade from database version 1 to version 2
|
|
|
|
# This change adds labels and mtime columns, to be used by Gmail IMAP and Maildir folders.
|
|
|
|
if from_ver <= 1:
|
2020-08-29 19:45:15 +02:00
|
|
|
self.ui._msg('Upgrading LocalStatus cache from version 1 to version 2 for %s:%s' %
|
|
|
|
(self.repository, self))
|
2012-10-16 20:20:35 +02:00
|
|
|
self.connection.executescript("""ALTER TABLE status ADD mtime INTEGER DEFAULT 0;
|
|
|
|
ALTER TABLE status ADD labels VARCHAR(256) DEFAULT '';
|
|
|
|
UPDATE metadata SET value='2' WHERE key='db_version';
|
|
|
|
""")
|
|
|
|
self.connection.commit()
|
|
|
|
|
2011-05-05 15:59:24 +02:00
|
|
|
# Future version upgrades come here...
|
|
|
|
# if from_ver <= 2: ... #upgrade from 2 to 3
|
2012-10-16 20:20:35 +02:00
|
|
|
# if from_ver <= 3: ... #upgrade from 3 to 4
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
def __create_db(self):
|
2015-01-14 22:58:25 +01:00
|
|
|
"""Create a new db file.
|
2012-10-16 20:20:35 +02:00
|
|
|
|
|
|
|
self.connection must point to the opened and valid SQlite
|
2015-01-14 22:58:25 +01:00
|
|
|
database connection."""
|
2020-08-29 19:45:15 +02:00
|
|
|
self.ui._msg('Creating new Local Status db for %s:%s' %
|
2016-07-22 00:08:45 +02:00
|
|
|
(self.repository, self))
|
2011-05-07 11:00:56 +02:00
|
|
|
self.connection.executescript("""
|
|
|
|
CREATE TABLE metadata (key VARCHAR(50) PRIMARY KEY, value VARCHAR(128));
|
2013-07-28 13:58:30 +02:00
|
|
|
INSERT INTO metadata VALUES('db_version', '2');
|
2012-10-16 20:20:35 +02:00
|
|
|
CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50), mtime INTEGER, labels VARCHAR(256));
|
2011-05-07 11:00:56 +02:00
|
|
|
""")
|
|
|
|
self.connection.commit()
|
2013-07-28 13:58:30 +02:00
|
|
|
self._newfolder = True
|
2011-05-05 15:59:24 +02:00
|
|
|
|
2014-08-03 14:47:26 +02:00
|
|
|
# Interface from BaseFolder
|
|
|
|
def msglist_item_initializer(self, uid):
|
|
|
|
return {'uid': uid, 'flags': set(), 'labels': set(), 'time': 0, 'mtime': 0}
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2011-05-05 15:59:24 +02:00
|
|
|
def cachemessagelist(self):
|
2016-04-09 18:12:18 +02:00
|
|
|
self.dropmessagelistcache()
|
2012-10-16 20:20:35 +02:00
|
|
|
cursor = self.connection.execute('SELECT id,flags,mtime,labels from status')
|
2011-05-07 11:00:56 +02:00
|
|
|
for row in cursor:
|
2014-08-03 14:47:26 +02:00
|
|
|
uid = row[0]
|
|
|
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
2012-10-16 20:20:35 +02:00
|
|
|
flags = set(row[1])
|
2015-02-22 14:08:39 +01:00
|
|
|
try:
|
|
|
|
labels = set([lb.strip() for lb in
|
2020-08-29 19:45:15 +02:00
|
|
|
row[3].split(',') if len(lb.strip()) > 0])
|
2015-02-22 14:08:39 +01:00
|
|
|
except AttributeError:
|
|
|
|
# FIXME: This except clause was introduced because row[3] from
|
|
|
|
# database can be found of unexpected type NoneType. See
|
|
|
|
# https://github.com/OfflineIMAP/offlineimap/issues/103
|
|
|
|
#
|
|
|
|
# We are fixing the type here but this would require more
|
|
|
|
# researches to find the true root cause. row[3] is expected to
|
|
|
|
# be a (empty) string, not None.
|
|
|
|
#
|
|
|
|
# Also, since database might return None, we have to fix the
|
|
|
|
# database, too.
|
|
|
|
labels = set()
|
2014-08-03 14:47:26 +02:00
|
|
|
self.messagelist[uid]['flags'] = flags
|
|
|
|
self.messagelist[uid]['labels'] = labels
|
|
|
|
self.messagelist[uid]['mtime'] = row[2]
|
2011-05-05 15:59:24 +02:00
|
|
|
|
2016-04-09 19:52:33 +02:00
|
|
|
def closefiles(self):
|
2016-07-26 02:56:36 +02:00
|
|
|
with self._databaseFileLock.getLock():
|
|
|
|
self._databaseFileLock.removeOneUser()
|
|
|
|
if self._databaseFileLock.shouldClose():
|
2016-07-22 00:08:45 +02:00
|
|
|
try:
|
|
|
|
self.connection.close()
|
|
|
|
except:
|
|
|
|
pass
|
2016-04-09 19:52:33 +02:00
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from LocalStatusFolder
|
2011-05-05 15:59:24 +02:00
|
|
|
def save(self):
|
|
|
|
pass
|
2013-07-28 13:58:30 +02:00
|
|
|
# Noop. every transaction commits to database!
|
|
|
|
|
|
|
|
def saveall(self):
|
|
|
|
"""Saves the entire messagelist to the database."""
|
2015-01-14 22:58:25 +01:00
|
|
|
|
2017-01-31 01:41:26 +01:00
|
|
|
with self._databaseFileLock.getLock():
|
|
|
|
data = []
|
2020-08-28 03:32:43 +02:00
|
|
|
for uid, msg in list(self.messagelist.items()):
|
2017-01-31 01:41:26 +01:00
|
|
|
mtime = msg['mtime']
|
|
|
|
flags = ''.join(sorted(msg['flags']))
|
|
|
|
labels = ', '.join(sorted(msg['labels']))
|
|
|
|
data.append((uid, flags, mtime, labels))
|
|
|
|
|
|
|
|
self.__sql_write('INSERT OR REPLACE INTO status '
|
2020-08-29 19:45:15 +02:00
|
|
|
'(id,flags,mtime,labels) VALUES (?,?,?,?)',
|
|
|
|
data, executemany=True)
|
2011-05-05 15:59:24 +02:00
|
|
|
|
2011-05-07 11:00:56 +02:00
|
|
|
# Following some pure SQLite functions, where we chose to use
|
|
|
|
# BaseFolder() methods instead. Doing those on the in-memory list is
|
|
|
|
# quicker anyway. If our db becomes so big that we don't want to
|
|
|
|
# maintain the in-memory list anymore, these might come in handy
|
|
|
|
# in the future though.
|
|
|
|
#
|
2020-08-29 19:45:15 +02:00
|
|
|
# def uidexists(self,uid):
|
2011-05-07 11:00:56 +02:00
|
|
|
# conn, cursor = self.get_cursor()
|
|
|
|
# with conn:
|
|
|
|
# cursor.execute('SELECT id FROM status WHERE id=:id',{'id': uid})
|
|
|
|
# return cursor.fetchone()
|
|
|
|
# This would be the pure SQLite solution, use BaseFolder() method,
|
|
|
|
# to avoid threading with sqlite...
|
2020-08-29 19:45:15 +02:00
|
|
|
# def getmessageuidlist(self):
|
2011-05-07 11:00:56 +02:00
|
|
|
# conn, cursor = self.get_cursor()
|
|
|
|
# with conn:
|
|
|
|
# cursor.execute('SELECT id from status')
|
|
|
|
# r = []
|
|
|
|
# for row in cursor:
|
|
|
|
# r.append(row[0])
|
|
|
|
# return r
|
2020-08-29 19:45:15 +02:00
|
|
|
# def getmessagecount(self):
|
2011-05-07 11:00:56 +02:00
|
|
|
# conn, cursor = self.get_cursor()
|
|
|
|
# with conn:
|
|
|
|
# cursor.execute('SELECT count(id) from status');
|
|
|
|
# return cursor.fetchone()[0]
|
2020-08-29 19:45:15 +02:00
|
|
|
# def getmessageflags(self, uid):
|
2011-05-07 11:00:56 +02:00
|
|
|
# conn, cursor = self.get_cursor()
|
|
|
|
# with conn:
|
|
|
|
# cursor.execute('SELECT flags FROM status WHERE id=:id',
|
|
|
|
# {'id': uid})
|
|
|
|
# for row in cursor:
|
|
|
|
# flags = [x for x in row[0]]
|
|
|
|
# return flags
|
|
|
|
# assert False,"getmessageflags() called on non-existing message"
|
2011-05-05 15:59:24 +02:00
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2021-02-19 23:00:15 +01:00
|
|
|
def savemessage(self, uid, msg, flags, rtime, mtime=0, labels=None):
|
2015-01-14 22:58:25 +01:00
|
|
|
"""Writes a new message, with the specified uid.
|
2011-09-16 11:44:50 +02:00
|
|
|
|
|
|
|
See folder/Base for detail. Note that savemessage() does not
|
|
|
|
check against dryrun settings, so you need to ensure that
|
2015-01-14 22:58:25 +01:00
|
|
|
savemessage is never called in a dryrun mode."""
|
|
|
|
|
2020-10-10 15:00:34 +02:00
|
|
|
if labels is None:
|
|
|
|
labels = set()
|
|
|
|
|
2011-05-05 15:59:24 +02:00
|
|
|
if uid < 0:
|
|
|
|
# We cannot assign a uid.
|
|
|
|
return uid
|
|
|
|
|
2020-08-29 19:45:15 +02:00
|
|
|
if self.uidexists(uid): # Already have it.
|
2011-05-05 15:59:24 +02:00
|
|
|
self.savemessageflags(uid, flags)
|
|
|
|
return uid
|
|
|
|
|
2014-08-03 14:47:26 +02:00
|
|
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
2012-10-16 20:20:35 +02:00
|
|
|
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime, 'mtime': mtime, 'labels': labels}
|
2011-05-07 11:00:56 +02:00
|
|
|
flags = ''.join(sorted(flags))
|
2012-10-16 20:20:35 +02:00
|
|
|
labels = ', '.join(sorted(labels))
|
2017-07-25 11:27:19 +02:00
|
|
|
try:
|
|
|
|
self.__sql_write('INSERT INTO status (id,flags,mtime,labels) VALUES (?,?,?,?)',
|
2020-08-29 19:45:15 +02:00
|
|
|
(uid, flags, mtime, labels))
|
2017-07-25 11:27:19 +02:00
|
|
|
except Exception as e:
|
2020-09-03 20:30:14 +02:00
|
|
|
raise UserWarning("%s while inserting UID %s" %
|
|
|
|
(str(e), str(uid)),
|
|
|
|
exc_info()[2])
|
2011-05-05 15:59:24 +02:00
|
|
|
return uid
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2011-05-05 15:59:24 +02:00
|
|
|
def savemessageflags(self, uid, flags):
|
2014-08-03 14:47:26 +02:00
|
|
|
assert self.uidexists(uid)
|
|
|
|
self.messagelist[uid]['flags'] = flags
|
2011-08-22 14:58:42 +02:00
|
|
|
flags = ''.join(sorted(flags))
|
2020-08-29 19:45:15 +02:00
|
|
|
self.__sql_write('UPDATE status SET flags=? WHERE id=?', (flags, uid))
|
2012-10-16 20:20:35 +02:00
|
|
|
|
|
|
|
def getmessageflags(self, uid):
|
|
|
|
return self.messagelist[uid]['flags']
|
|
|
|
|
|
|
|
def savemessagelabels(self, uid, labels, mtime=None):
|
|
|
|
self.messagelist[uid]['labels'] = labels
|
2020-08-30 13:37:28 +02:00
|
|
|
if mtime:
|
|
|
|
self.messagelist[uid]['mtime'] = mtime
|
2012-10-16 20:20:35 +02:00
|
|
|
|
|
|
|
labels = ', '.join(sorted(labels))
|
|
|
|
if mtime:
|
2020-08-29 19:45:15 +02:00
|
|
|
self.__sql_write('UPDATE status SET labels=?, mtime=? WHERE id=?', (labels, mtime, uid))
|
2012-10-16 20:20:35 +02:00
|
|
|
else:
|
2020-08-29 19:45:15 +02:00
|
|
|
self.__sql_write('UPDATE status SET labels=? WHERE id=?', (labels, uid))
|
2012-10-16 20:20:35 +02:00
|
|
|
|
|
|
|
def savemessageslabelsbulk(self, labels):
|
|
|
|
"""
|
|
|
|
Saves labels from a dictionary in a single database operation.
|
2016-04-09 19:52:33 +02:00
|
|
|
|
2012-10-16 20:20:35 +02:00
|
|
|
"""
|
2020-08-28 03:32:43 +02:00
|
|
|
data = [(', '.join(sorted(l)), uid) for uid, l in list(labels.items())]
|
2012-10-16 20:20:35 +02:00
|
|
|
self.__sql_write('UPDATE status SET labels=? WHERE id=?', data, executemany=True)
|
2020-08-28 03:32:43 +02:00
|
|
|
for uid, l in list(labels.items()):
|
2012-10-16 20:20:35 +02:00
|
|
|
self.messagelist[uid]['labels'] = l
|
|
|
|
|
|
|
|
def addmessageslabels(self, uids, labels):
|
|
|
|
data = []
|
|
|
|
for uid in uids:
|
|
|
|
newlabels = self.messagelist[uid]['labels'] | labels
|
|
|
|
data.append((', '.join(sorted(newlabels)), uid))
|
|
|
|
self.__sql_write('UPDATE status SET labels=? WHERE id=?', data, executemany=True)
|
|
|
|
for uid in uids:
|
|
|
|
self.messagelist[uid]['labels'] = self.messagelist[uid]['labels'] | labels
|
|
|
|
|
|
|
|
def deletemessageslabels(self, uids, labels):
|
|
|
|
data = []
|
|
|
|
for uid in uids:
|
|
|
|
newlabels = self.messagelist[uid]['labels'] - labels
|
|
|
|
data.append((', '.join(sorted(newlabels)), uid))
|
|
|
|
self.__sql_write('UPDATE status SET labels=? WHERE id=?', data, executemany=True)
|
|
|
|
for uid in uids:
|
|
|
|
self.messagelist[uid]['labels'] = self.messagelist[uid]['labels'] - labels
|
|
|
|
|
|
|
|
def getmessagelabels(self, uid):
|
|
|
|
return self.messagelist[uid]['labels']
|
|
|
|
|
|
|
|
def savemessagesmtimebulk(self, mtimes):
|
|
|
|
"""Saves mtimes from the mtimes dictionary in a single database operation."""
|
2015-01-14 22:58:25 +01:00
|
|
|
|
2020-08-28 03:32:43 +02:00
|
|
|
data = [(mt, uid) for uid, mt in list(mtimes.items())]
|
2012-10-16 20:20:35 +02:00
|
|
|
self.__sql_write('UPDATE status SET mtime=? WHERE id=?', data, executemany=True)
|
2020-08-28 03:32:43 +02:00
|
|
|
for uid, mt in list(mtimes.items()):
|
2012-10-16 20:20:35 +02:00
|
|
|
self.messagelist[uid]['mtime'] = mt
|
|
|
|
|
|
|
|
def getmessagemtime(self, uid):
|
|
|
|
return self.messagelist[uid]['mtime']
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2012-01-07 22:39:59 +01:00
|
|
|
def deletemessage(self, uid):
|
2020-08-30 13:36:40 +02:00
|
|
|
if uid not in self.messagelist:
|
2012-01-07 22:39:59 +01:00
|
|
|
return
|
2020-08-29 19:45:15 +02:00
|
|
|
self.__sql_write('DELETE FROM status WHERE id=?', (uid,))
|
|
|
|
del (self.messagelist[uid])
|
2012-01-07 22:39:59 +01:00
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2011-05-05 15:59:24 +02:00
|
|
|
def deletemessages(self, uidlist):
|
2012-01-07 22:39:59 +01:00
|
|
|
"""Delete list of UIDs from status cache
|
|
|
|
|
|
|
|
This function uses sqlites executemany() function which is
|
|
|
|
much faster than iterating through deletemessage() when we have
|
|
|
|
many messages to delete."""
|
2015-01-14 22:58:25 +01:00
|
|
|
|
2011-05-05 15:59:24 +02:00
|
|
|
# Weed out ones not in self.messagelist
|
|
|
|
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
|
|
|
if not len(uidlist):
|
|
|
|
return
|
2012-01-07 22:39:59 +01:00
|
|
|
# arg2 needs to be an iterable of 1-tuples [(1,),(2,),...]
|
2016-05-08 17:35:38 +02:00
|
|
|
self.__sql_write('DELETE FROM status WHERE id=?', list(zip(uidlist, )), True)
|
2011-05-05 15:59:24 +02:00
|
|
|
for uid in uidlist:
|
2020-08-29 19:45:15 +02:00
|
|
|
del (self.messagelist[uid])
|