update: Небольшое обновление, направленное на повышение надежности хранения архивов и поддержку регулярных дозагрузок писем в одну и ту же папку.
This commit is contained in:
49
src/model.py
49
src/model.py
@@ -3,6 +3,8 @@ import email
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
||||
CONFIG_FILE = "config.json"
|
||||
|
||||
@@ -12,7 +14,7 @@ class MailArchiverModel:
|
||||
self.imap_port = 993
|
||||
|
||||
def load_config(self) -> dict:
|
||||
"""Загружает сохраненные настройки из JSON-файла."""
|
||||
"""Загружает сохраненные настройки из JSON-файла при старте программы."""
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
try:
|
||||
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
||||
@@ -22,7 +24,7 @@ class MailArchiverModel:
|
||||
return {"email": "", "password": "", "path": ""}
|
||||
|
||||
def save_config(self, email_user: str, password_app: str, output_dir: str):
|
||||
"""Сохраняет настройки формы в JSON-файл."""
|
||||
"""Сохраняет настройки формы в JSON-файл при нажатии кнопки 'ЗАПУСТИТЬ РЕЗЕРВНОЕ КОПИРОВАНИЕ'."""
|
||||
data = {
|
||||
"email": email_user,
|
||||
"password": password_app,
|
||||
@@ -35,8 +37,9 @@ class MailArchiverModel:
|
||||
print(f"Ошибка сохранения конфига: {e}")
|
||||
|
||||
def clean_filename(self, title: str) -> str:
|
||||
"""Очищает строку от запрещенных в Windows символов для имени файла."""
|
||||
return re.sub(r'[\\/*?:"<>|]', "", title)[:60]
|
||||
"""Очищает строку от запрещенных в Windows символов."""
|
||||
clean = re.sub(r'[\\/*?:"<>|]', "", title)
|
||||
return clean.strip()[:50]
|
||||
|
||||
def connect_and_fetch_ids(self, email_user: str, password_app: str, folder: str = "INBOX"):
|
||||
"""Подключается к серверу и возвращает список ID писем."""
|
||||
@@ -52,7 +55,10 @@ class MailArchiverModel:
|
||||
return mail, mail_ids
|
||||
|
||||
def download_single_email(self, mail_instance, mail_id: bytes, output_dir: str) -> str:
|
||||
"""Скачивает одно письмо по его ID и сохраняет в формате .eml."""
|
||||
"""
|
||||
Скачивает одно письмо по его ID и сохраняет в формате .eml.
|
||||
Имя файла гарантирует 100% уникальность на основе даты и Message-ID.
|
||||
"""
|
||||
status, msg_data = mail_instance.fetch(mail_id, "(RFC822)")
|
||||
if status != "OK":
|
||||
raise Exception(f"Ошибка при скачивании письма ID {mail_id.decode()}")
|
||||
@@ -62,9 +68,9 @@ class MailArchiverModel:
|
||||
raw_email = response_part[1]
|
||||
msg = email.message_from_bytes(raw_email)
|
||||
|
||||
raw_subject = msg.get("Subject", "NoSubject")
|
||||
# 1. Извлекаем тему письма
|
||||
raw_subject = msg.get("Subject", "Без темы")
|
||||
decoded_parts = email.header.decode_header(raw_subject)
|
||||
|
||||
subject_text = ""
|
||||
for part, encoding in decoded_parts:
|
||||
if isinstance(part, bytes):
|
||||
@@ -73,16 +79,37 @@ class MailArchiverModel:
|
||||
subject_text += str(part)
|
||||
|
||||
clean_subject = self.clean_filename(subject_text)
|
||||
mail_id_str = mail_id.decode()
|
||||
if not clean_subject:
|
||||
clean_subject = "Без_темы"
|
||||
|
||||
# 2. Получаем дату отправки письма
|
||||
date_tuple = email.utils.parsedate_tz(msg.get("Date"))
|
||||
if date_tuple:
|
||||
local_dt = datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
|
||||
date_prefix = local_dt.strftime("%Y-%m-%d_%H-%M")
|
||||
else:
|
||||
date_prefix = "0000-00-00_00-00"
|
||||
|
||||
# 3. Извлекаем глобальный глобально-уникальный Message-ID письма
|
||||
msg_id_header = msg.get("Message-ID", "").strip()
|
||||
if msg_id_header:
|
||||
# Создаем короткий уникальный хэш от Message-ID
|
||||
unique_hash = hashlib.md5(msg_id_header.encode('utf-8')).hexdigest()[:8]
|
||||
else:
|
||||
# Фоллбэк: если Message-ID отсутствует, используем хэш содержимого
|
||||
unique_hash = hashlib.md5(raw_email).hexdigest()[:8]
|
||||
|
||||
# 4. Формируем гарантированно уникальное имя файла
|
||||
# Пример: "2026-09-10_18-30_a1b2c3d4_Заказ_оформлен.eml"
|
||||
filename = f"{date_prefix}_{unique_hash}_{clean_subject}.eml"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
filename = f"mail_{mail_id_str}_{clean_subject}.eml"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(raw_email)
|
||||
|
||||
return subject_text if subject_text else "Без темы"
|
||||
|
||||
raise Exception("Данные письма повреждены или пусты.")
|
||||
10
version.txt
10
version.txt
@@ -1,8 +1,8 @@
|
||||
# UTF-8
|
||||
VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
filevers=(1, 0, 0, 0),
|
||||
prodvers=(1, 0, 0, 0),
|
||||
filevers=(1, 0, 0, 1),
|
||||
prodvers=(1, 0, 0, 1),
|
||||
mask=0x3f,
|
||||
flags=0x0,
|
||||
OS=0x40004,
|
||||
@@ -18,12 +18,12 @@ VSVersionInfo(
|
||||
[
|
||||
StringStruct(u'CompanyName', u'ООО "Альянс - Технологии Сервиса"'),
|
||||
StringStruct(u'FileDescription', u'Yandex Mail Archiver - Утилита резервного копирования почты Яндекс'),
|
||||
StringStruct(u'FileVersion', u'1.0.0.0'),
|
||||
StringStruct(u'FileVersion', u'1.0.0.1'),
|
||||
StringStruct(u'InternalName', u'YandexMailArchiver'),
|
||||
StringStruct(u'LegalCopyright', u'Copyright © 2026. Все права защищены.'),
|
||||
StringStruct(u'OriginalFilename', u'YandexMailArchiver.exe'),
|
||||
StringStruct(u'ProductName', u'Yandex Mail Archiver'),
|
||||
StringStruct(u'ProductVersion', u'1.0.0.0')
|
||||
StringStruct(u'ProductVersion', u'1.0.0.1')
|
||||
]
|
||||
),
|
||||
StringTable(
|
||||
@@ -36,7 +36,7 @@ VSVersionInfo(
|
||||
StringStruct(u'LegalCopyright', u'Copyright © 2026. Все права защищены.'),
|
||||
StringStruct(u'OriginalFilename', u'YandexMailArchiver.exe'),
|
||||
StringStruct(u'ProductName', u'Yandex Mail Archiver'),
|
||||
StringStruct(u'ProductVersion', u'1.0.0.0')
|
||||
StringStruct(u'ProductVersion', u'1.0.0.1')
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user