✨feat(mail_reader): add moving analyzed mails

add moving analyzed mails feature, upgrade  reports by adding date of generation
This commit is contained in:
2026-09-10 13:57:53 +02:00
parent 4a81b71eac
commit 68f6b298a5
3 changed files with 66 additions and 3 deletions
+2
View File
@@ -12,6 +12,7 @@ from logwatcher.mail_reader import (
connect_to_mailbox, connect_to_mailbox,
extract_logs_from_mails, extract_logs_from_mails,
fetch_log_messages, fetch_log_messages,
move_analyzed_mails,
) )
from logwatcher.models import LogEntry from logwatcher.models import LogEntry
from logwatcher.parser import parse_file, parse_lines from logwatcher.parser import parse_file, parse_lines
@@ -90,6 +91,7 @@ def from_mail(
write_log_report( write_log_report(
SourceType.MAIL, relevant, irrelevant, nb_files, output_dir=output_dir SourceType.MAIL, relevant, irrelevant, nb_files, output_dir=output_dir
) )
move_analyzed_mails(account, messages)
logger.info("============ logwatcher mail ended ============") logger.info("============ logwatcher mail ended ============")
+58 -2
View File
@@ -9,6 +9,7 @@ from exchangelib import (
Configuration, Configuration,
Credentials, Credentials,
FileAttachment, FileAttachment,
Folder,
Message, Message,
Version, Version,
) )
@@ -17,7 +18,8 @@ from exchangelib.errors import UnauthorizedError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MAILBOX_DIR = "Logs" LOG_DIR = "Logs"
ANALYZED_LOG_DIR = "Analyzed"
LOG_IN_ATTACHMENT_PATTERN = "Le compte-rendu contient plus de 100 lignes." LOG_IN_ATTACHMENT_PATTERN = "Le compte-rendu contient plus de 100 lignes."
@@ -138,7 +140,7 @@ def fetch_log_messages(account: Account) -> list[Message]:
Returns: Returns:
queryset of log-related mails queryset of log-related mails
""" """
logs_folder = account.msg_folder_root / MAILBOX_DIR logs_folder = account.msg_folder_root / LOG_DIR
return list(logs_folder.all()) return list(logs_folder.all())
@@ -178,3 +180,57 @@ def extract_logs_from_mails(messages: list[Message]) -> list[str]:
content = _get_mail_body(message) content = _get_mail_body(message)
logs.extend(extract_log_lines(content)) logs.extend(extract_log_lines(content))
return logs return logs
def _get_or_create_analyzed_folder(account: Account) -> Folder:
"""
Return the 'Analyzed' folder, creating it if it doesn't exist.
Args:
account: The mailbox account owning the Logs folder.
Returns:
The existing or newly created 'Analyzed' folder.
"""
log_folder = account.msg_folder_root / LOG_DIR
matches = list(log_folder.glob(ANALYZED_LOG_DIR))
if matches:
logger.info(f"\tFolder '{ANALYZED_LOG_DIR}' found.")
return matches[0]
logger.info(f"\tCreating '{ANALYZED_LOG_DIR}' folder.")
analyzed_folder = Folder(parent=log_folder, name=ANALYZED_LOG_DIR)
analyzed_folder.save()
return analyzed_folder
def move_analyzed_mails(account: Account, messages: list[Message]) -> None:
"""
Move processed mails from the Logs folder to the Analyzed subfolder.
Args:
account: The mailbox account owning the Logs folder.
messages: The processed mails to move.
Note:
Partial failure is possible: if a move fails mid-batch, some mails
may remain in Logs and be retried on the next run (at-least-once).
"""
if not messages:
logger.info("No mails to move.")
return
logger.info(
f"Moving {len(messages)} mails from '{LOG_DIR}' directory to '{ANALYZED_LOG_DIR}'"
)
analyzed_folder = _get_or_create_analyzed_folder(account)
ids = [(m.id, m.changekey) for m in messages]
account.bulk_move(ids=ids, to_folder=analyzed_folder)
logger.info(
f"Mails were successfully moved from '{LOG_DIR}' to '{ANALYZED_LOG_DIR}'."
)
+6 -1
View File
@@ -3,7 +3,7 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from string import Template from string import Template
from logwatcher.config import DATETIME_FORMAT, RESULT_PATH, SourceType from logwatcher.config import DATETIME_FORMAT, FRENCH_TIMEZONE, RESULT_PATH, SourceType
from logwatcher.models import LogEntry from logwatcher.models import LogEntry
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
BASE_TEMPLATE = Template(""" BASE_TEMPLATE = Template("""
RAPPORT D'ANALYSE DE LOGS RAPPORT D'ANALYSE DE LOGS
========================= =========================
Généré le\t : $today
Période\t : $start_date -> $end_date Période\t : $start_date -> $end_date
$source_type(s) lu(s)\t : $nb_files $source_type(s) lu(s)\t : $nb_files
Nombre total d'erreur(s)\t: $nb_errors Nombre total d'erreur(s)\t: $nb_errors
@@ -126,10 +127,12 @@ def build_reports(
logger.info("\t\t\tn2 and other reports were generated.") logger.info("\t\t\tn2 and other reports were generated.")
logger.info("\t\tbuilding reports job completed.") logger.info("\t\tbuilding reports job completed.")
today = datetime.now(FRENCH_TIMEZONE).strftime(DATETIME_FORMAT)
source_value = source_type.value.capitalize() source_value = source_type.value.capitalize()
return { return {
"n2": BASE_TEMPLATE.substitute( "n2": BASE_TEMPLATE.substitute(
today=today,
source_type=source_value, source_type=source_value,
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,
@@ -138,6 +141,7 @@ def build_reports(
sub_content=relevant_report, sub_content=relevant_report,
), ),
"other": BASE_TEMPLATE.substitute( "other": BASE_TEMPLATE.substitute(
today=today,
source_type=source_value, source_type=source_value,
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,
@@ -146,6 +150,7 @@ def build_reports(
sub_content=irrelevant_report, sub_content=irrelevant_report,
), ),
"all": BASE_TEMPLATE.substitute( "all": BASE_TEMPLATE.substitute(
today=today,
source_type=source_value, source_type=source_value,
start_date=start_date, start_date=start_date,
end_date=end_date, end_date=end_date,