update: Небольшое обновление, направленное на повышение надежности хранения архивов и поддержку регулярных дозагрузок писем в одну и ту же папку.

This commit is contained in:
SUGAN-PC\Evgeniy Sygonyako
2026-09-10 21:49:41 +03:00
parent 7aab8e5c32
commit 72a4eb0ee7
2 changed files with 44 additions and 17 deletions

View File

@@ -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("Данные письма повреждены или пусты.")

View File

@@ -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')
]
)
]