diff --git a/slixfeed/sqlite.py b/slixfeed/sqlite.py index 081aa80..209b99c 100644 --- a/slixfeed/sqlite.py +++ b/slixfeed/sqlite.py @@ -211,6 +211,22 @@ def create_tables(db_file): ); """ ) + feeds_tags_table_sql = ( + """ + CREATE TABLE IF NOT EXISTS feeds_tags ( + id INTEGER NOT NULL, + feed_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id") + ON UPDATE CASCADE + ON DELETE CASCADE, + FOREIGN KEY ("tag_id") REFERENCES "tags" ("id") + ON UPDATE CASCADE + ON DELETE CASCADE, + PRIMARY KEY ("id") + ); + """ + ) # TODO # Consider parameter unique: # entry_id TEXT NOT NULL UNIQUE, @@ -246,6 +262,15 @@ def create_tables(db_file): ); """ ) + tags_table_sql = ( + """ + CREATE TABLE IF NOT EXISTS tags ( + id INTEGER NOT NULL, + tag TEXT NOT NULL UNIQUE, + PRIMARY KEY ("id") + ); + """ + ) cur = conn.cursor() # cur = get_cursor(db_file) cur.execute(archive_table_sql) @@ -254,10 +279,12 @@ def create_tables(db_file): cur.execute(feeds_state_table_sql) cur.execute(feeds_properties_table_sql) cur.execute(feeds_rules_table_sql) + cur.execute(feeds_tags_table_sql) cur.execute(filters_table_sql) # cur.execute(statistics_table_sql) cur.execute(settings_table_sql) cur.execute(status_table_sql) + cur.execute(tags_table_sql) def get_cursor(db_file): @@ -598,7 +625,8 @@ async def remove_feed_by_index(db_file, ix): # cur.execute(sql, par) sql = ( """ - DELETE FROM feeds + DELETE + FROM feeds WHERE id = ? """ ) @@ -606,6 +634,231 @@ async def remove_feed_by_index(db_file, ix): cur.execute(sql, par) +def get_tags_by_feed_id(db_file, feed_id): + """ + Get tags of given feed. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed ID. + + Returns + ------- + result : list + List of tags. + """ + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT tags.tag + FROM tags + INNER JOIN feeds_tags ON tags.id = feeds_tags.tag_id + INNER JOIN feeds ON feeds.id = feeds_tags.feed_id + WHERE feeds.id = ?; + """ + ) + par = (feed_id,) + result = cur.execute(sql, par).fetchall() + return result + + +async def set_feed_id_and_tag_id(db_file, feed_id, tag_id): + """ + Set Feed ID and Tag ID. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed ID + tag_id : str + Tag ID + """ + async with DBLOCK: + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + INSERT + INTO feeds_tags( + feed_id, tag_id) + VALUES( + :feed_id, :tag_id) + """ + ) + par = { + "feed_id": feed_id, + "tag_id": tag_id + } + cur.execute(sql, par) + + +def get_tag_id(db_file, tag): + """ + Get ID of given tag. Check whether tag exist. + + Parameters + ---------- + db_file : str + Path to database file. + tag : str + Tag. + + Returns + ------- + ix : str + Tag ID. + """ + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT id + FROM tags + WHERE tag = ? + """ + ) + par = (tag,) + ix = cur.execute(sql, par).fetchone() + return ix + + +def is_tag_id_associated(db_file, tag_id): + """ + Check whether tag_id is associated with any feed. + + Parameters + ---------- + db_file : str + Path to database file. + tag_id : str + Tag ID. + + Returns + ------- + tag_id : str + Tag ID. + """ + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT tag_id + FROM feeds_tags + WHERE tag_id = :tag_id + """ + ) + par = { + "tag_id": tag_id + } + tag_id = cur.execute(sql, par).fetchone() + return tag_id + + +async def delete_tag_by_index(db_file, ix): + async with DBLOCK: + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + DELETE + FROM tags + WHERE id = :id + """ + ) + par = { + "id": ix + } + cur.execute(sql, par) + + +def is_tag_id_of_feed_id(db_file, tag_id, feed_id): + """ + Check whether given tag is related with given feed. + + Parameters + ---------- + db_file : str + Path to database file. + feed_id : str + Feed ID. + tag_id : str + Tag ID. + + Returns + ------- + tag_id : str + Tag ID. + """ + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + SELECT tag_id + FROM feeds_tags + WHERE tag_id = :tag_id AND feed_id = :feed_id + """ + ) + par = { + "tag_id": tag_id, + "feed_id": feed_id + } + tag_id = cur.execute(sql, par).fetchone() + return tag_id + + +async def delete_feed_id_tag_id(db_file, feed_id, tag_id): + async with DBLOCK: + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + DELETE + FROM feeds_tags + WHERE tag_id = :tag_id AND feed_id = :feed_id + """ + ) + par = { + "tag_id": tag_id, + "feed_id": feed_id + } + cur.execute(sql, par) + + +async def set_new_tag(db_file, tag): + """ + Set new Tag + + Parameters + ---------- + db_file : str + Path to database file. + tag : str + Tag + """ + async with DBLOCK: + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + """ + INSERT + INTO tags( + tag) + VALUES( + :tag) + """ + ) + par = { + "tag": tag + } + cur.execute(sql, par) + + async def get_feed_id_and_name(db_file, url): """ Get Id and Name of feed. @@ -1112,7 +1365,8 @@ async def delete_archived_entry(cur, ix): """ sql = ( """ - DELETE FROM archive + DELETE + FROM archive WHERE id = ? """ ) @@ -1469,7 +1723,8 @@ async def maintain_archive(db_file, limit): if difference > 0: sql = ( """ - DELETE FROM archive + DELETE + FROM archive WHERE id IN ( SELECT id diff --git a/slixfeed/version.py b/slixfeed/version.py index af57d4b..6108b5f 100644 --- a/slixfeed/version.py +++ b/slixfeed/version.py @@ -1,2 +1,2 @@ -__version__ = '0.1.21' -__version_info__ = (0, 1, 21) +__version__ = '0.1.22' +__version_info__ = (0, 1, 22) diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index 4a23f19..c3cb1ae 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -621,14 +621,14 @@ class Slixfeed(slixmpp.ClientXMPP): ftype='text-single', label='Allow list', value=value, - desc=('Keywords to allow (comma-separated keywords).')) + desc='Keywords to allow (comma-separated keywords).') value = sqlite.get_filter_value(db_file, 'deny') if value: value = str(value[0]) form.add_field(var='deny', ftype='text-single', label='Deny list', value=value, - desc=('Keywords to deny (comma-separated keywords).')) + desc='Keywords to deny (comma-separated keywords).') session['allow_complete'] = True session['has_next'] = False session['next'] = self._handle_filters_complete @@ -1366,9 +1366,14 @@ class Slixfeed(slixmpp.ClientXMPP): feed_id = feed_id[0] title = sqlite.get_feed_title(db_file, feed_id) title = title[0] + tags_result = sqlite.get_tags_by_feed_id(db_file, feed_id) + tags_sorted = sorted(x[0] for x in tags_result) + tags = ', '.join(tags_sorted) form['instructions'] = 'Editing subscription #{}'.format(feed_id) else: form['instructions'] = 'Adding subscription' + title = '' + tags = '' form.add_field(ftype='fixed', value='Properties') form.add_field(var='name', @@ -1391,11 +1396,14 @@ class Slixfeed(slixmpp.ClientXMPP): label='ID #', value=feed_id_str) options.addOption(feed_id_str, feed_id_str) - form.add_field(var='tags', + form.add_field(var='tags_new', ftype='text-single', - # ftype='text-multi', label='Tags', - value='') + desc='Comma-separated tags.', + value=tags) + form.add_field(var='tags_old', + ftype='hidden', + value=tags) form.add_field(ftype='fixed', value='Options') options = form.add_field(var='priority', @@ -1443,8 +1451,32 @@ class Slixfeed(slixmpp.ClientXMPP): await sqlite.set_enabled_status(db_file, feed_id, enabled_status) name = values['name'] await sqlite.set_feed_title(db_file, feed_id, name) - values['priority'] - values['tags'] + priority = values['priority'] + tags_new = values['tags_new'] + tags_old = values['tags_old'] + # Add new tags + for tag in tags_new.split(','): + tag = tag.strip() + if not tag: + continue + tag_id = sqlite.get_tag_id(db_file, tag) + if not tag_id: + await sqlite.set_new_tag(db_file, tag) + tag_id = sqlite.get_tag_id(db_file, tag) + tag_id = tag_id[0] + if not sqlite.is_tag_id_of_feed_id(db_file, tag_id, feed_id): + await sqlite.set_feed_id_and_tag_id(db_file, feed_id, tag_id) + # Remove tags that were not submitted + for tag in tags_old[0].split(','): + tag = tag.strip() + if not tag: + continue + if tag not in tags_new: + tag_id = sqlite.get_tag_id(db_file, tag) + tag_id = tag_id[0] + await sqlite.delete_feed_id_tag_id(db_file, feed_id, tag_id) + sqlite.is_tag_id_associated(db_file, tag_id) + await sqlite.delete_tag_by_index(db_file, tag_id) # form = self['xep_0004'].make_form('form', 'Subscription') # form['instructions'] = ('📁️ Subscription #{} has been {}' # .format(feed_id, action)) diff --git a/slixfeed/xmpp/process.py b/slixfeed/xmpp/process.py index a0e6dc0..58bf351 100644 --- a/slixfeed/xmpp/process.py +++ b/slixfeed/xmpp/process.py @@ -39,7 +39,7 @@ from slixfeed.xmpp.muc import XmppGroupchat from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.upload import XmppUpload -from slixfeed.xmpp.utility import get_chat_type +from slixfeed.xmpp.utility import get_chat_type, is_moderator import time @@ -81,9 +81,7 @@ async def message(self, message): if (message['muc']['nick'] == self.alias): return jid_full = str(message['from']) - alias = jid_full[jid_full.index('/')+1:] - role = self.plugin['xep_0045'].get_jid_property(jid, alias, 'role') - if role != 'moderator': + if not is_moderator(self, jid, jid_full): return # NOTE This is an exceptional case in which we treat @@ -132,9 +130,7 @@ async def message(self, message): # return # approved = False jid_full = str(message['from']) - role = self.plugin['xep_0045'].get_jid_property( - jid, jid_full[jid_full.index('/')+1:], 'role') - if role != 'moderator': + if not is_moderator(self, jid, jid_full): return # if role == 'moderator': # approved = True