diff sat_pubsub/pgsql_storage.py @ 422:c21f31355ab9

configuration: "max_items" option: "max_items" is implemented using a text-single field, as it is done in the XEP-0060 example (there is no real formal description). When changing the node configuration, the max_items can't be set to a number lower than the total number of items in the node (the configuration will then be rejected), this is to avoid accidental deletion of items.
author Goffi <goffi@goffi.org>
date Tue, 10 Mar 2020 11:11:38 +0100
parents ccb2a22ea0fc
children 3fce48c0a44d
line wrap: on
line diff
--- a/sat_pubsub/pgsql_storage.py	Sat Feb 22 13:06:51 2020 +0100
+++ b/sat_pubsub/pgsql_storage.py	Tue Mar 10 11:11:38 2020 +0100
@@ -109,6 +109,7 @@
     defaultConfig = {
             'leaf': {
                 const.OPT_PERSIST_ITEMS: True,
+                const.OPT_MAX_ITEMS: 'max',
                 const.OPT_DELIVER_PAYLOADS: True,
                 const.OPT_SEND_LAST_PUBLISHED_ITEM: 'on_sub',
                 const.OPT_ACCESS_MODEL: const.VAL_AMODEL_DEFAULT,
@@ -142,22 +143,21 @@
 
     def _buildNode(self, row):
         """Build a note class from database result row"""
-        configuration = {}
-
         if not row:
             raise error.NodeNotFound()
 
         if row[2] == 'leaf':
             configuration = {
-                    'pubsub#persist_items': row[3],
-                    'pubsub#deliver_payloads': row[4],
-                    'pubsub#send_last_published_item': row[5],
-                    const.OPT_ACCESS_MODEL:row[6],
-                    const.OPT_PUBLISH_MODEL:row[7],
-                    const.OPT_SERIAL_IDS:row[8],
-                    const.OPT_CONSISTENT_PUBLISHER:row[9],
+                    const.OPT_PERSIST_ITEMS: row[3],
+                    const.OPT_MAX_ITEMS: 'max' if row[4] == 0 else str(row[4]),
+                    const.OPT_DELIVER_PAYLOADS: row[5],
+                    const.OPT_SEND_LAST_PUBLISHED_ITEM: row[6],
+                    const.OPT_ACCESS_MODEL:row[7],
+                    const.OPT_PUBLISH_MODEL:row[8],
+                    const.OPT_SERIAL_IDS:row[9],
+                    const.OPT_CONSISTENT_PUBLISHER:row[10],
                     }
-            schema = row[10]
+            schema = row[11]
             if schema is not None:
                 schema = parseXml(schema)
             node = LeafNode(row[0], row[1], configuration, schema)
@@ -165,10 +165,10 @@
             return node
         elif row[2] == 'collection':
             configuration = {
-                    'pubsub#deliver_payloads': row[4],
-                    'pubsub#send_last_published_item': row[5],
-                    const.OPT_ACCESS_MODEL: row[6],
-                    const.OPT_PUBLISH_MODEL:row[7],
+                    const.OPT_DELIVER_PAYLOADS: row[5],
+                    const.OPT_SEND_LAST_PUBLISHED_ITEM: row[6],
+                    const.OPT_ACCESS_MODEL: row[7],
+                    const.OPT_PUBLISH_MODEL:row[8],
                     }
             node = CollectionNode(row[0], row[1], configuration, None)
             node.dbpool = self.dbpool
@@ -188,6 +188,7 @@
                                  node,
                                  node_type,
                                  persist_items,
+                                 max_items,
                                  deliver_payloads,
                                  send_last_published_item,
                                  access_model,
@@ -210,6 +211,7 @@
                                           node,
                                           node_type,
                                           persist_items,
+                                          max_items,
                                           deliver_payloads,
                                           send_last_published_item,
                                           access_model,
@@ -537,6 +539,7 @@
         self._checkNodeExists(cursor)
         self._configurationTriggers(cursor, self.nodeDbId, self._config, config)
         cursor.execute("""UPDATE nodes SET persist_items=%s,
+                                           max_items=%s,
                                            deliver_payloads=%s,
                                            send_last_published_item=%s,
                                            access_model=%s,
@@ -545,6 +548,7 @@
                                            consistent_publisher=%s
                           WHERE node_id=%s""",
                        (config[const.OPT_PERSIST_ITEMS],
+                        config[const.OPT_MAX_ITEMS],
                         config[const.OPT_DELIVER_PAYLOADS],
                         config[const.OPT_SEND_LAST_PUBLISHED_ITEM],
                         config[const.OPT_ACCESS_MODEL],
@@ -951,7 +955,8 @@
 
         if access_model == const.VAL_AMODEL_PUBLISHER_ROSTER:
             if const.OPT_ROSTER_GROUPS_ALLOWED in item_config:
-                item_config.fields[const.OPT_ROSTER_GROUPS_ALLOWED].fieldType='list-multi' #XXX: needed to force list if there is only one value
+                # XXX: needed to force list if there is only one value
+                item_config.fields[const.OPT_ROSTER_GROUPS_ALLOWED].fieldType='list-multi'
                 allowed_groups = item_config[const.OPT_ROSTER_GROUPS_ALLOWED]
             else:
                 allowed_groups = []
@@ -961,6 +966,24 @@
                                   VALUES (%s,%s)""" , (item_id, group))
         # TODO: whitelist access model
 
+        max_items = self._config.get(const.OPT_MAX_ITEMS, 'max')
+
+        if max_items != 'max':
+            try:
+                max_items = int(self._config[const.OPT_MAX_ITEMS])
+            except ValueError:
+                log.err(f"Invalid max_items value: {max_items!r}")
+            else:
+                if max_items > 0:
+                    # we delete all items above the requested max
+                    cursor.execute(
+                        "DELETE FROM items WHERE node_id=%s and item_id in (SELECT "
+                        "item_id FROM items WHERE node_id=%s ORDER BY items.updated "
+                        "DESC, items.item_id DESC OFFSET %s)" ,
+                        (self.nodeDbId, self.nodeDbId, max_items)
+                    )
+
+
     def _storeCategories(self, cursor, item_id, categories, update=False):
         # TODO: handle canonical form
         if update: