diff options
Diffstat (limited to 'model/mailstore.py')
-rw-r--r-- | model/mailstore.py | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/model/mailstore.py b/model/mailstore.py new file mode 100644 index 0000000..e65bad5 --- /dev/null +++ b/model/mailstore.py @@ -0,0 +1,236 @@ +# Copyright 2009 Shikhar Bhushan <shikhar@schmizz.net> +# +# This file is part of the Sweetmail activity for Sugar. +# +# Sweetmail 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. +# +# Sweetmail 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 Sweetmail. If not, see <http://www.gnu.org/licenses/>. + +import email +import mailbox +import os +import sqlite3 as sqlite +import threading + +from gettext import gettext as _ + +import tags +from msginfo import MsgInfo + +class MailStore(object): + ''' + Stores messages in Maildir. Tag and header metadata + is stored in a relational database. + Tags are uniquely identified by a 'uid' and messages by a 'key'. + Methods to manipulate tags and messages are provided. + ''' + + def __init__(self, path): + + dir = os.path.join(path, 'mail') + self.maildir = mailbox.Maildir(dir, None) + + self._dbfile = os.path.join(path, 'metadata.db') + self._cons = {} + + con = self.get_connection() + con.executescript(''' + CREATE TABLE IF NOT EXISTS tags ( + type INTEGER NOT NULL, + uid INTEGER PRIMARY KEY NOT NULL, + label TEXT NOT NULL, + icon TEXT NOT NULL, + pos INTEGER NOT NULL, + query TEXT + ); + + CREATE TABLE IF NOT EXISTS associations ( + key TEXT NOT NULL, + uid INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY NOT NULL, + flags INTEGER NOT NULL, + hdr_from TEXT NOT NULL, + hdr_to TEXT NOT NULL, + hdr_subj TEXT NOT NULL, + hdr_date TEXT NOT NULL + ); + ''') + con.commit() + + self._tags = TagStore(self) + + tags.Tag.register(self) + + def __del__(self): + for con in self._cons.values(): + con.close() + + def get_connection(self): + t = threading.currentThread() + try: + return self._cons[t] + except KeyError: + con = sqlite.connect(self._dbfile) + #con.row_factory = sqlite.Row + self._cons[t] = con + return con + + def _extract_headers(self, msg): + 'we don\'t check for rfc822 compliance here; store whatever the message purports' + frm = msg.get('From', 'undefined') + to = msg.get('To', 'undefined') + subj = msg.get('Subject', 'undefined') + date = msg.get('Date', 'undefined') + return (frm, to, subj, date) + + def add_msg(self, msg, flags=0): + 'msg arg should be email.message.Message instance' + key = self.maildir.add(msg) + (frm, to, subj, date) = self._extract_headers(msg) + con = self.get_connection() + con.execute('INSERT INTO metadata VALUES (?,?,?,?,?,?)', + (key, flags, frm, to, subj, date)) + con.commit() + return key + + def del_msg(self, key): + 'delete from maildir, scrub the db' + self.maildir.remove(key) + con = self.get_connection() + con.executescript(''' + DELETE FROM associations WHERE key=:key; + DELETE FROM metadata WHERE key=?''', (key, key)) + con.commit() + + get_tag = lambda self, uid: self._tags[uid] + + get_msg = lambda self, key: self.maildir.get(msg_key) + + def get_msg_info(self, key): + q = 'SELECT key, flags, hdr_from, hdr_to, hdr_subj, hdr_date FROM metadata WHERE key=?' + con = self.get_connection() + metadata = con.execute(q, (key,)).fetchone() + return MsgInfo(self, *metadata) + + def associated_uids(self, key): + con = self.get_connection() + uids = [res[0] for res in con.execute('SELECT uid FROM associations WHERE key=?', (key,))] + return uids + + def associated_keys(self, uid): + return self._tags[uid].associated_keys() + + def flagged_keys(self, flag, tick=True): + where = 'flags&?' if tick else 'NOT flags&?' + con = self.get_connection() + keys = [res[0] for res in con.execute('SELECT key FROM metadata WHERE %s' % where, (flag,))] + return keys + + def associate(self, uid, key): + self._tags[uid].associate(key) + + def dissociate(self, uid, key): + self._tags[uid].dissociate(key) + + def flag(self, key, flag): + con = self.get_connection() + con.execute('UPDATE metadata SET flags=flags|? WHERE key=?', (flag, key)) + con.commit() + + def unflag(self, key, flag): + con = self.get_connection() + con.execute('UPDATE metadata SET flags=flags&(~?) WHERE key=?', (flag, key)) + con.commit() + + tags = property(lambda self: self._tags) + +class TagStore: + + __contains__ = lambda self: self._tags.__contains__() + + __iter__ = lambda self: self._tags.__iter__() + + __len__ = lambda self: self._tags.__len__() + + def __getitem__(self, key): + for tag in self._tags: + if tag.uid==key: + return tag + else: + return None + + def __init__(self, mailstore): + #self._ms = mailstore + self.get_connection = mailstore.get_connection + + con = self.get_connection() + con.executemany('INSERT OR IGNORE INTO tags (type, uid, label, icon, pos) VALUES (?,?,?,?,?)', + tags.get_defaults()) + con.commit() + + self._tags = [] + self._next_uid = 0 + self._cache_tags() + + def _order_by_pos(self): + self._tags.sort(key=lambda t: t.pos) + + def _cache_tags(self, con=None): + con = self.get_connection() + self._tags = [tags.tag_factory(type, uid, label, icon, pos) + for (type, uid, label, icon, pos) in + con.execute('SELECT type, uid, label, icon, pos FROM tags')] + self._order_by_pos() + max_uid = con.execute('SELECT MAX(uid) FROM tags').fetchone()[0] + self._next_uid = max_uid + 1 + + def add_tag(self, type, label, icon='tag-default', pos=None, query=None): + count = len(self._tags) + if pos is None or pos>count: + pos = count + uid = self._next_uid + tag = tags.tag_factory(type, uid, label, icon, pos, query) + tags.Tag.register(ma) + con = self.get_connection() + con.execute('INSERT INTO tags VALUES(?,?,?,?,?,?,?)', + (uid, type, label, icon, pos, query)) + con.commit() + self._tags.append(tag) + self._order_by_pos() + self._next_uid += 1 + + def del_tag(self, uid): + if not isinstance(self[uid], tags.HardCodedTag): + con = self.get_connection() + con.execute('DELETE FROM tags WHERE uid=?', (uid,)) + con.execute('DELETE FROM associations WHERE uid=?', (uid,)) + con.commit() + self._order_by_pos() + + def get_tag(self, **kwd): + attr, val = kwd.popitem() + for tag in self._tags: + if getattr(tag, attr)==val: + return tag + else: + return None + + def swap_pos(self, uid1, uid2): + tag1 = self[uid1] + tag2 = self.get_tag(uid=uid2) + tmp = tag1.pos + tag1.pos = tag2.pos + tag2.pos = tmp + self._order_by_pos()
\ No newline at end of file |