Skip to content

Commit

Permalink
Merge pull request #896 from DJ2LS/ls-api-adjustment
Browse files Browse the repository at this point in the history
API and database adjustments
  • Loading branch information
DJ2LS authored Mar 1, 2025
2 parents 3168960 + b59ef36 commit 4bed7da
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 46 deletions.
33 changes: 0 additions & 33 deletions freedata_server/api/freedata.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,6 @@ async def get_freedata_message(message_id: str, request: Request):
return api_response(message)


@router.post("/messages", summary="Transmit Message", tags=["FreeDATA"], responses={
200: {
"description": "Message transmitted successfully.",
"content": {
"application/json": {
"example": {
"destination": "XX1XXX-6",
"body": "Hello FreeDATA"
}
}
}
},
404: {
"description": "The requested resource was not found.",
"content": {
"application/json": {
"example": {
"error": "Resource not found."
}
}
}
},
503: {
"description": "Modem not running or busy.",
"content": {
"application/json": {
"example": {
"error": "Modem not running."
}
}
}
}
})
async def post_freedata_message(request: Request):
"""
Transmit a FreeDATA message.
Expand Down
23 changes: 13 additions & 10 deletions freedata_server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,17 @@ def read(self):
# self.log.info("[CFG] reading...")
if not self.config_exists():
return False

# at first just copy the config as read from file
result = {s: dict(self.parser.items(s)) for s in self.parser.sections()}

# handle the special settings
for section in result:
for setting in result[section]:
result[section][setting] = self.handle_setting(
section, setting, result[section][setting], False)
try:
# at first just copy the config as read from file
result = {s: dict(self.parser.items(s)) for s in self.parser.sections()}

# handle the special settings
for section in result:
for setting in result[section]:
result[section][setting] = self.handle_setting(
section, setting, result[section][setting], False)
return result
except Exception as conferror:
self.log.error("[CFG] reading logfile", e=conferror)
return False

return result
86 changes: 85 additions & 1 deletion freedata_server/message_system_db_attachments.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,88 @@ def get_attachment_by_sha512(self, hash_sha512):
self.log(f"Error fetching attachment with SHA-512 hash {hash_sha512}: {e}", isWarning=True)
return None
finally:
session.remove()
session.remove()

def delete_attachments_by_message_id(self, message_id):
"""
Deletes attachment associations for a given message ID.
For each attachment linked to the message:
- If the attachment is linked to more than one message, only the association for this message is deleted.
- If the attachment is linked only to this message, both the association and the attachment record are deleted.
Parameters:
message_id (str): The ID of the message whose attachment associations should be deleted.
Returns:
bool: True if the deletion was successful, False otherwise.
"""
session = self.get_thread_scoped_session()
try:
# Find all attachment associations for the given message ID.
links = session.query(MessageAttachment).filter_by(message_id=message_id).all()
if not links:
self.log(f"No attachments linked with message ID {message_id} found.")
return True

for link in links:
# Count how many associations exist for this attachment.
link_count = session.query(MessageAttachment).filter_by(attachment_id=link.attachment_id).count()
if link_count > 1:
# More than one link exists, so only remove the association.
session.delete(link)
self.log(
f"Deleted link for attachment '{link.attachment.name}' from message {message_id} (other links exist).")
else:
# Only one link exists, so delete both the association and the attachment.
session.delete(link)
session.delete(link.attachment)
self.log(f"Deleted attachment '{link.attachment.name}' from message {message_id} (only link).")

session.commit()
return True
except Exception as e:
session.rollback()
self.log(f"Error deleting attachments for message ID {message_id}: {e}", isWarning=True)
return False
finally:
session.remove()


def clean_orphaned_attachments(self):
"""
Checks for orphaned attachments in the database, i.e. attachments that have no
MessageAttachment links to any messages. Optionally, deletes these orphaned attachments.
Parameters:
cleanup (bool): If True, deletes the orphaned attachments; if False, only returns them.
Returns:
If cleanup is False:
list: A list of dictionaries representing the orphaned attachments.
If cleanup is True:
dict: A summary dictionary with the count of deleted attachments.
"""
session = self.get_thread_scoped_session()
try:
orphaned = []
# Get all attachments in the database.
attachments = session.query(Attachment).all()
for attachment in attachments:
# Count the number of MessageAttachment links for this attachment.
link_count = session.query(MessageAttachment).filter_by(attachment_id=attachment.id).count()
if link_count == 0:
orphaned.append(attachment)

for attachment in orphaned:
self.log(f"Deleting orphaned attachment: {attachment.name}")
session.delete(attachment)
self.log(f"Checked for orphaned attachments")
session.commit()
return {'status': 'success', 'deleted_count': len(orphaned)}
except Exception as e:
session.rollback()
self.log(f"Error checking orphaned attachments: {e}", isWarning=True)
return None
finally:
session.remove()
11 changes: 9 additions & 2 deletions freedata_server/message_system_db_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,23 +222,30 @@ def get_message_by_id_adif(self, message_id):
return None

def delete_message(self, message_id):

# Delete attachment links associated with this message.
# This call will check each attachment link:
# - If the attachment is used by other messages, only the link is removed.
# - If the attachment is solely linked to this message, the attachment record is deleted.
self.attachments_manager.delete_attachments_by_message_id(message_id)


session = self.get_thread_scoped_session()
try:
message = session.query(P2PMessage).filter_by(id=message_id).first()
if message:
session.delete(message)
session.commit()

self.log(f"Deleted: {message_id}")
self.event_manager.freedata_message_db_change(message_id=message_id)
return {'status': 'success', 'message': f'Message {message_id} deleted'}
else:
return {'status': 'failure', 'message': 'Message not found'}

except Exception as e:
session.rollback()
self.log(f"Error deleting message with ID {message_id}: {e}", isWarning=True)
return {'status': 'failure', 'message': 'error deleting message'}

finally:
session.remove()

Expand Down
42 changes: 42 additions & 0 deletions freedata_server/schedule_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,48 @@ def delete_beacons(self):
print(e)

def push_to_explorer(self):
"""
Exception in thread Thread-5 (run):
Traceback (most recent call last):
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py", line 1045, in _bootstrap_inner
self.run()
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py", line 982, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/sched.py", line 151, in run
action(*argument, **kwargs)
File "/Users/simonlang/PycharmProjects/FreeDATA/freedata_server/schedule_manager.py", line 42, in schedule_event
event_function() # Execute the event function
^^^^^^^^^^^^^^^^
File "/Users/simonlang/PycharmProjects/FreeDATA/freedata_server/schedule_manager.py", line 91, in push_to_explorer
self.config = self.config_manager.read()
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/simonlang/PycharmProjects/FreeDATA/freedata_server/config.py", line 235, in read
result = {s: dict(self.parser.items(s)) for s in self.parser.sections()}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/simonlang/PycharmProjects/FreeDATA/freedata_server/config.py", line 235, in <dictcomp>
result = {s: dict(self.parser.items(s)) for s in self.parser.sections()}
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/configparser.py", line 875, in items
return [(option, value_getter(option)) for option in orig_keys]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2025-02-28 17:14:49 [info ] [DatabaseManagerMessages]: Updating station list with DJ2LS-0
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/configparser.py", line 875, in <listcomp>
return [(option, value_getter(option)) for option in orig_keys]
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/configparser.py", line 871, in <lambda>
value_getter = lambda option: self._interpolation.before_get(self,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/configparser.py", line 396, in before_get
self._interpolate_some(parser, option, L, value, section, defaults, 1)
File "/usr/local/Cellar/[email protected]/3.11.10/Frameworks/Python.framework/Versions/3.11/lib/python3.11/configparser.py", line 413, in _interpolate_some
p = rest.find("%")
^^^^^^^^^
AttributeError: 'list' object has no attribute 'find'
"""
self.config = self.config_manager.read()
if self.config['STATION']['enable_explorer'] and self.state_manager.is_modem_running:
try:
Expand Down
3 changes: 3 additions & 0 deletions freedata_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@


from message_system_db_manager import DatabaseManager
from message_system_db_attachments import DatabaseManagerAttachments
from schedule_manager import ScheduleManager

from api.general import router as general_router
Expand Down Expand Up @@ -206,6 +207,8 @@ def main():
app.modem_service.put("start")
DatabaseManager(app.event_manager).initialize_default_values()
DatabaseManager(app.event_manager).database_repair_and_cleanup()
DatabaseManagerAttachments(app.event_manager).clean_orphaned_attachments()

app.wsm = websocket_manager.wsm()
app.wsm.startWorkerThreads(app)

Expand Down

0 comments on commit 4bed7da

Please sign in to comment.