diff --git a/lib/nntp/tiny/db.py b/lib/nntp/tiny/db.py index 6c7acdd..bd3c837 100644 --- a/lib/nntp/tiny/db.py +++ b/lib/nntp/tiny/db.py @@ -1,28 +1,141 @@ +import enum import sqlite3 -from nntp.tiny.message import Message +class DatabaseOrder(enum.Enum): + DEFAULT = 0 + ASC = 1 + DESC = 2 + +class DatabaseTable(): + pass + +class DatabaseTableCursor(): + __slots__ = 'cr', 'table', + + def __init__(self, table, cr): + self.cr = cr + self.table = table + + def __getattr__(self, name): + return getattr(self.cr, name) + + def __map__(self, row): + obj = self.table() + + for name in self.table.columns: + try: + setattr(obj, name, row[name]) + except IndexError: + setattr(obj, name, None) + + return obj + + def fetchone(self): + return self.__map__(self.cr.fetchone()) + + def fetchall(self): + return map(self.__map__, self.cr.fetchall()) class Database(): __slots__ = 'db', - def __init__(self, path: str): - self.db = sqlite3.connect(path) + def __init__(self, db): + self.db = db - def message_add(self, newsgroup_id: int, message: Message): - sql = """ - insert into - newsgroup_message - (newsgroup_id, message_id, created_on, sender, subject, content) values (?, ?, ?, ?, ?, ?) - """ + def __getattr__(self, name): + return getattr(self.db, name) - self.db.execute(sql, ( - newsgroup_id, - message.id(), - message.date().isoformat(), - message.sender(), - message.subject(), - message.content - )) + @staticmethod + def connect(path): + db = sqlite3.connect(path) + db.row_factory = sqlite3.Row - def commit(self): - self.db.commit() + return Database(db) + + def add(self, obj): + table = type(obj) + sql = f"insert into {table.name} (" + + first = True + + for column in table.columns: + if column == table.key: + continue + + if first: + sql += column + first = False + else: + sql += ', ' + column + + sql += ') values (' + + first = True + + for column in table.columns: + if column == table.key: + continue + + if first: + first = False + else: + sql += ', ' + + sql += '?' + + sql += ')' + + fn = getattr(obj, '__values__') + + if fn is not None: + values = fn() + else: + values = list() + + for column in table.columns: + if column != table.key: + values.append(getattr(obj, column)) + + self.db.execute(sql, values) + + def query(self, table, values=dict(), order_by=list()): + sql = f"select * from {table.name}" + + params = list() + first = True + + for key in values.keys(): + if first: + sql += f" where {table.name}.{key} = ?" + + first = False + else: + sql += f" and {table.name}.{key} = ?" + + params.append(values[key]) + + first = True + + if len(order_by) > 0: + sql += " order by" + + for column, order in order_by: + if first: + first = False + else: + sql += ", " + + if order is None or order is DatabaseOrder.DEFAULT: + sql += f" {column}" + elif order is DatabaseOrder.ASC: + sql += f" {column} asc" + elif order is DatabaseOrder.DESC: + sql += f" {column} desc" + + cr = DatabaseTableCursor(table, self.db.cursor()) + cr.execute(sql, params) + + return cr + + def get(self, table, values=dict()): + return self.query(table, values).fetchone() diff --git a/lib/nntp/tiny/message.py b/lib/nntp/tiny/message.py index 563f11c..401f68c 100644 --- a/lib/nntp/tiny/message.py +++ b/lib/nntp/tiny/message.py @@ -5,6 +5,8 @@ import datetime from dateparser.search import search_dates from email.header import decode_header +from nntp.tiny.db import DatabaseTable + def decode(text: str): decoded = decode_header(text)[0] @@ -21,19 +23,41 @@ class MessageState(enum.Enum): HEADER = 1 BODY = 2 -class Message(): - __slots__ = 'state', 'headers', 'line', 'content', 'body', 'key', +class Message(DatabaseTable): + __slots__ = 'newsgroup_id', 'state', 'headers', 'line', 'content', 'body', '_key', + + name = 'newsgroup_message' + key = 'id' + columns = ( + 'newsgroup_id', + 'message_id', + 'created_on', + 'sender', + 'subject', + 'content' + ) RE_HEADER = re.compile(r'^([A-Za-z0-9\-]+): (.*)$') RE_MESSAGE_ID = re.compile(r'^<([^<>]+)>$') def __init__(self): - self.state = MessageState.EMPTY - self.headers = dict() - self.line = None - self.content = '' - self.body = None - self.key = None + self.newsgroup_id = None + self.state = MessageState.EMPTY + self.headers = dict() + self.line = None + self.content = '' + self.body = None + self._key = None + + def __values__(self): + return ( + self.newsgroup_id, + self.id(), + self.date(), + self.sender(), + self.subject(), + self.content + ) def add(self, line: str): if self.line is not None: @@ -46,14 +70,14 @@ class Message(): if line == '\n' or line == '\r\n': self.state = MessageState.BODY elif line[0] == ' ' or line[0] == '\t': - self.headers[self.key] += ' ' + decode(line.strip()) + self.headers[self._key] += ' ' + decode(line.strip()) else: match = self.RE_HEADER.match(line) if match: - self.key = match[1].lower() + self._key = match[1].lower() - self.headers[self.key] = decode(match[2].rstrip()) + self.headers[self._key] = decode(match[2].rstrip()) elif self.state is MessageState.BODY: if self.body is None: self.body = '' diff --git a/lib/nntp/tiny/newsgroup.py b/lib/nntp/tiny/newsgroup.py new file mode 100644 index 0000000..8a841cf --- /dev/null +++ b/lib/nntp/tiny/newsgroup.py @@ -0,0 +1,6 @@ +from nntp.tiny.db import DatabaseTable + +class Newsgroup(DatabaseTable): + name = 'newsgroup' + key = 'id' + columns = 'id', 'name', 'description',