mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-04-25 17:32:09 +00:00
feat(database): update trim_announces_for_aspect to protect favourited destinations and saved contacts from deletion
This commit is contained in:
@@ -50,7 +50,15 @@ class AnnounceDAO:
|
||||
self.provider.execute(query, params)
|
||||
|
||||
def trim_announces_for_aspect(self, aspect, max_rows):
|
||||
"""Delete oldest rows for this aspect until at most max_rows remain."""
|
||||
"""Delete oldest rows for this aspect until at most max_rows remain.
|
||||
|
||||
Announces that correspond to a favourited destination or to a saved
|
||||
contact are considered protected and are never deleted by this trim,
|
||||
even if the total count exceeds ``max_rows``. This prevents purging
|
||||
of announces (and the path/identity context they provide) for
|
||||
favourited NomadNet nodes and for messaging contacts when storage
|
||||
limits are enforced.
|
||||
"""
|
||||
if max_rows < 1 or not aspect:
|
||||
return
|
||||
row = self.provider.fetchone(
|
||||
@@ -64,8 +72,19 @@ class AnnounceDAO:
|
||||
self.provider.execute(
|
||||
"""
|
||||
DELETE FROM announces WHERE id IN (
|
||||
SELECT id FROM announces WHERE aspect = ?
|
||||
ORDER BY updated_at ASC, id ASC
|
||||
SELECT a.id FROM announces a
|
||||
WHERE a.aspect = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM favourite_destinations f
|
||||
WHERE f.destination_hash = a.destination_hash
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM contacts c
|
||||
WHERE c.remote_identity_hash = a.identity_hash
|
||||
OR c.lxmf_address = a.destination_hash
|
||||
OR c.lxst_address = a.destination_hash
|
||||
)
|
||||
ORDER BY a.updated_at ASC, a.id ASC
|
||||
LIMIT ?
|
||||
)
|
||||
""",
|
||||
|
||||
@@ -7,12 +7,12 @@ from meshchatx.src.backend.database import Database
|
||||
from meshchatx.src.backend.database.provider import DatabaseProvider
|
||||
|
||||
|
||||
def _insert(db, dest_hex, aspect, updated_order):
|
||||
def _insert(db, dest_hex, aspect, updated_order, identity_hex=None):
|
||||
db.announces.upsert_announce(
|
||||
{
|
||||
"destination_hash": dest_hex,
|
||||
"aspect": aspect,
|
||||
"identity_hash": "a" * 32,
|
||||
"identity_hash": identity_hex or ("a" * 32),
|
||||
"identity_public_key": "cHVibmtleQ==",
|
||||
"app_data": None,
|
||||
"rssi": None,
|
||||
@@ -26,14 +26,37 @@ def _insert(db, dest_hex, aspect, updated_order):
|
||||
)
|
||||
|
||||
|
||||
def _new_db():
|
||||
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
|
||||
path = f.name
|
||||
db = Database(path)
|
||||
db.initialize()
|
||||
return db, path
|
||||
|
||||
|
||||
def _cleanup(db, path):
|
||||
if db is not None:
|
||||
try:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
DatabaseProvider._instance = None
|
||||
if path:
|
||||
try:
|
||||
os.unlink(path)
|
||||
except OSError:
|
||||
pass
|
||||
for suffix in ("-wal", "-shm"):
|
||||
try:
|
||||
os.unlink(path + suffix)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_trim_announces_for_aspect_drops_oldest():
|
||||
path = None
|
||||
db = None
|
||||
db = path = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
|
||||
path = f.name
|
||||
db = Database(path)
|
||||
db.initialize()
|
||||
db, path = _new_db()
|
||||
aspect = "lxmf.delivery"
|
||||
_insert(db, "01" * 16, aspect, "2000-01-01T00:00:00Z")
|
||||
_insert(db, "02" * 16, aspect, "2000-01-02T00:00:00Z")
|
||||
@@ -43,19 +66,92 @@ def test_trim_announces_for_aspect_drops_oldest():
|
||||
hashes = {r["destination_hash"] for r in rows}
|
||||
assert hashes == {"03" * 16, "02" * 16}
|
||||
finally:
|
||||
if db is not None:
|
||||
try:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
DatabaseProvider._instance = None
|
||||
if path:
|
||||
try:
|
||||
os.unlink(path)
|
||||
except OSError:
|
||||
pass
|
||||
for suffix in ("-wal", "-shm"):
|
||||
try:
|
||||
os.unlink(path + suffix)
|
||||
except OSError:
|
||||
pass
|
||||
_cleanup(db, path)
|
||||
|
||||
|
||||
def test_trim_preserves_favourited_destination():
|
||||
"""Favourited NomadNet/destination announces must survive aspect trim."""
|
||||
db = path = None
|
||||
try:
|
||||
db, path = _new_db()
|
||||
aspect = "nomadnetwork.node"
|
||||
|
||||
_insert(db, "01" * 16, aspect, "2000-01-01T00:00:00Z")
|
||||
_insert(db, "02" * 16, aspect, "2000-01-02T00:00:00Z")
|
||||
_insert(db, "03" * 16, aspect, "2000-01-03T00:00:00Z")
|
||||
_insert(db, "04" * 16, aspect, "2000-01-04T00:00:00Z")
|
||||
|
||||
db.announces.upsert_favourite("01" * 16, "Favourite Node", aspect)
|
||||
|
||||
db.announces.trim_announces_for_aspect(aspect, 2)
|
||||
|
||||
rows = db.announces.get_announces(aspect=aspect)
|
||||
hashes = {r["destination_hash"] for r in rows}
|
||||
|
||||
assert "01" * 16 in hashes, "favourited announce was wrongly trimmed"
|
||||
assert "04" * 16 in hashes, "newest announce should be retained"
|
||||
assert "02" * 16 not in hashes
|
||||
assert "03" * 16 not in hashes
|
||||
finally:
|
||||
_cleanup(db, path)
|
||||
|
||||
|
||||
def test_trim_preserves_contact_announce_by_identity_hash():
|
||||
"""Announces tied to a saved contact via identity hash must not be dropped."""
|
||||
db = path = None
|
||||
try:
|
||||
db, path = _new_db()
|
||||
aspect = "lxmf.delivery"
|
||||
|
||||
contact_identity = "b" * 32
|
||||
|
||||
_insert(db, "01" * 16, aspect, "2000-01-01T00:00:00Z", contact_identity)
|
||||
_insert(db, "02" * 16, aspect, "2000-01-02T00:00:00Z")
|
||||
_insert(db, "03" * 16, aspect, "2000-01-03T00:00:00Z")
|
||||
_insert(db, "04" * 16, aspect, "2000-01-04T00:00:00Z")
|
||||
|
||||
db.contacts.add_contact(
|
||||
name="Friend",
|
||||
remote_identity_hash=contact_identity,
|
||||
)
|
||||
|
||||
db.announces.trim_announces_for_aspect(aspect, 2)
|
||||
|
||||
rows = db.announces.get_announces(aspect=aspect)
|
||||
hashes = {r["destination_hash"] for r in rows}
|
||||
|
||||
assert "01" * 16 in hashes, "contact-linked announce was wrongly trimmed"
|
||||
assert "04" * 16 in hashes
|
||||
finally:
|
||||
_cleanup(db, path)
|
||||
|
||||
|
||||
def test_trim_preserves_contact_announce_by_lxmf_address():
|
||||
"""Announces matching contacts.lxmf_address must be retained."""
|
||||
db = path = None
|
||||
try:
|
||||
db, path = _new_db()
|
||||
aspect = "lxmf.delivery"
|
||||
|
||||
protected_dest = "01" * 16
|
||||
|
||||
_insert(db, protected_dest, aspect, "2000-01-01T00:00:00Z")
|
||||
_insert(db, "02" * 16, aspect, "2000-01-02T00:00:00Z")
|
||||
_insert(db, "03" * 16, aspect, "2000-01-03T00:00:00Z")
|
||||
_insert(db, "04" * 16, aspect, "2000-01-04T00:00:00Z")
|
||||
|
||||
db.contacts.add_contact(
|
||||
name="Friend",
|
||||
remote_identity_hash="c" * 32,
|
||||
lxmf_address=protected_dest,
|
||||
)
|
||||
|
||||
db.announces.trim_announces_for_aspect(aspect, 2)
|
||||
|
||||
rows = db.announces.get_announces(aspect=aspect)
|
||||
hashes = {r["destination_hash"] for r in rows}
|
||||
|
||||
assert protected_dest in hashes
|
||||
assert "04" * 16 in hashes
|
||||
finally:
|
||||
_cleanup(db, path)
|
||||
|
||||
Reference in New Issue
Block a user