E-mail box, как хранилище файлов для синхронизации папок

Часто на хостингах сайтов предоставляются почтовые корпоративные ящики, мне например для почты предоставлено 100МБ в день на рассылку и всего 100ГБ на все домены моего аккаунта.

Я не пользуюсь почтой с такой интенсивностью и могу использовать её как хранилище для своих файлов. Нижу пример скрипта, как это реализовать.

sync_send.py - отправка

Скрипт лезет на почту, сканирует ваши письма в папке “входящие” по ним понимает какие там файлы аттачем прикручены и от какой они даты, потом сканирует папку, понимает какие файлы там есть.

Сопоставляет одно с другим по определяет какие файлы надо закинуть в почту, потому что а) их там еще нет б) на диске более свежая версия.

Потом он составляет письма спец вида, и аттачем туда прикрепляет gzip сжатые файлы и отправляет их от вас - вам же, так они попадают в почтовый ящик во входящие.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import os, io, gzip, json
from datetime import datetime
import smtplib
import imaplib
import email
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication

# Настройки для IMAP и SMTP сервера
IMAP_SERVER = 'впишите имап сервер'
SMTP_SERVER = 'впишите другой сервер'
EMAIL_ADDRESS = 'впишите адрес почты'
EMAIL_PASSWORD = 'впишите пароль'
DIRECTORY = 'files' # папка из которой файлы отправлять


def get_file_modification_time(file_path):
# Получаем время последнего изменения файла
modification_time = os.path.getmtime(file_path)
# Преобразуем время из эпохи в объект datetime
modification_time_dt = datetime.fromtimestamp(modification_time)
# Форматируем datetime в строку
modification_time_str = modification_time_dt.strftime("%Y-%m-%d %H:%M:%S")
return modification_time_str
def string_to_datetime(date_str):
# Преобразуем строку в объект datetime
modification_time_dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")

# Преобразуем объект datetime в временную метку
timestamp = modification_time_dt.timestamp()

return modification_time_dt, timestamp


def get_email_subjects_and_ids():
subjects_and_ids = []
with imaplib.IMAP4(IMAP_SERVER) as imap:
imap.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
imap.select('inbox')
result, data = imap.search(None, 'ALL')
email_ids = data[0].split()

for e_id in email_ids:
result, msg_data = imap.fetch(e_id, "(BODY[HEADER.FIELDS (SUBJECT)])")
print('result, msg_data', result, msg_data)
msg = email.message_from_bytes(msg_data[0][1])
print('msg', msg)
subjects_and_ids.append((msg['Subject'], e_id))
return subjects_and_ids
def download_file(e_id, subject):
result, msg_data = imap.fetch(e_id, '(RFC822)')
msg = email.message_from_bytes(msg_data[0][1])

for part in msg.walk():
if part.get_content_disposition() == 'attachment':
filename = part.get_filename()
if filename:
filepath = os.path.join(TARGET_FOLDER, filename)

# Сохранение файла на диск
with open(filepath, 'wb') as f:
f.write(part.get_payload(decode=True))
return True
return False


def get_file_list(root_directory):
"""Возвращает список файлов с их относительными путями."""
file_paths = []
for dirpath, _, filenames in os.walk(root_directory):
for filename in filenames:
full_path = os.path.join(dirpath, filename)
relative_path = os.path.relpath(full_path, root_directory)
file_paths.append(relative_path)
return file_paths



def get_mail_attachments():
"""Возвращает список вложенных файлов из писем на почтовом сервере."""
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
mail.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
mail.select('inbox')

result, data = mail.search(None, 'ALL')
message_ids = data[0].split()

attached_files = []
lmts = {}
for message_id in message_ids:
result, msg_data = mail.fetch(message_id, '(RFC822)')
msg = email.message_from_bytes(msg_data[0][1]) # Вместо получения вложений, получаем только subject
subject = json.loads(msg['subject'])
path=subject['path']; lmt=subject['lmt']

attached_files.append( path )
# самая свежая версия этого файла в почтовом сервере
if (path not in lmts.keys()) or (path in lmts.keys() and string_to_datetime(lmt) > lmts[path]):
lmts[path] = lmt

mail.logout()
#print(lmts)
return attached_files, lmts

def send_email_with_attachment(filename):
"""Отправляет файл в письме на почтовый сервер."""
msg = MIMEMultipart()
msg['From'] = EMAIL_ADDRESS
msg['To'] = EMAIL_ADDRESS
msg['Subject'] = json.dumps( {'path':os.path.basename(filename).strip(),'lmt':get_file_modification_time(filename)} )

def gzipIt(file):
file_content = file.read()
# Создаем поток для записи сжатых данных
buffer = io.BytesIO()
# Сжимаем данные и записываем в буфер
with gzip.GzipFile(fileobj=buffer, mode='wb') as gz:
gz.write(file_content)
# Получаем сжатые данные из буфера
return buffer.getvalue()

with open(filename, 'rb') as file:
part = MIMEApplication(gzipIt(file), Name=os.path.basename(filename))
part['Content-Disposition'] = f'attachment; filename="{os.path.basename(filename)}.gz"'
msg.attach(part)

#print(msg)
#return

with smtplib.SMTP(SMTP_SERVER) as server:
server.starttls()
server.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
server.send_message(msg)

def main():
all_files = get_file_list(DIRECTORY)
email_attachments, lmts = get_mail_attachments()

for file_name in all_files:
if file_name not in email_attachments:
send_email_with_attachment(os.path.join(DIRECTORY, file_name))
else:
if string_to_datetime(get_file_modification_time(os.path.join(DIRECTORY, filename))) > string_to_datetime(lmts[filename]):
send_email_with_attachment(os.path.join(DIRECTORY, file_name))

if __name__ == '__main__':
main()

Чтобы оттуда забрать файлы, Вам нужен другой скрипт, который будет далее.

sync_download.py - получение файлов с почты

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import os, io, gzip, json
import imaplib
import email
from email import policy
from datetime import datetime

# Настройки для IMAP и SMTP сервера
IMAP_SERVER = 'впишите имап сервер'
SMTP_SERVER = 'впишите другой сервер'
EMAIL_ADDRESS = 'впишите адрес почты'
EMAIL_PASSWORD = 'впишите пароль'
DIRECTORY = 'files' # папка из которой файлы отправлять

def get_file_modification_time(file_path) -> str:
# Получаем время последнего изменения файла
modification_time = os.path.getmtime(file_path)
# Преобразуем время из эпохи в объект datetime
modification_time_dt = datetime.fromtimestamp(modification_time)
# Форматируем datetime в строку
modification_time_str = modification_time_dt.strftime("%Y-%m-%d %H:%M:%S")
return modification_time_str
def string_to_datetime(date_str):
# Преобразуем строку в объект datetime
modification_time_dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")

# Преобразуем объект datetime в временную метку
timestamp = modification_time_dt.timestamp()

return modification_time_dt, timestamp

def get_email_subjects_and_ids():
subjects_and_ids = []
with imaplib.IMAP4(IMAP_SERVER) as imap:
imap.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
imap.select('inbox')
result, data = imap.search(None, 'ALL')
email_ids = data[0].split()

for e_id in email_ids:
result, msg_data = imap.fetch(e_id, "(BODY[HEADER.FIELDS (SUBJECT)])")
msg = email.message_from_bytes(msg_data[0][1])
subjects_and_ids.append( (msg['Subject'], e_id) )
return subjects_and_ids, imap

def download_file(e_id, subject):
with imaplib.IMAP4(IMAP_SERVER) as imap:
imap.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
imap.select('inbox')

result, msg_data = imap.fetch(e_id, '(RFC822)')
msg = email.message_from_bytes(msg_data[0][1])

# Функция для распаковки Gzip данных из bytes
def decompress_gzip_to_bytes(gzip_data):
# Создаем буфер из входных Gzip данных
buffer = io.BytesIO(gzip_data)
# Распаковываем данные
with gzip.GzipFile(fileobj=buffer, mode='rb') as gz:
decompressed_data = gz.read() # Читаем распакованные данные
return decompressed_data

for part in msg.walk():
if part.get_content_disposition() == 'attachment':
filename = part.get_filename().split('.gz')[0]
if filename:
filepath = os.path.join(DIRECTORY, filename)

# Сохранение файла на диск
with open(filepath, 'wb') as f:
gzipedContent = part.get_payload(decode=True)

#f.write(gzipedContent)

# Распакуем Gzip данные
decompressed_data = decompress_gzip_to_bytes(gzipedContent)
f.write(decompressed_data)
print('записал файл', filepath)
return True
return False



# deprecated
def download_attachments():
"""Скачивает вложения из писем на почтовом сервере. всё ВООБЩЕ получает получает по сети"""
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
mail.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
mail.select('inbox')

result, data = mail.search(None, 'ALL')

message_ids = data[0].split()

for message_id in message_ids:
result, msg_data = mail.fetch(message_id, '(RFC822)')
msg = email.message_from_bytes(msg_data[0][1], policy=policy.default)

subject = msg['subject']
file_path = os.path.join(DIRECTORY, subject) # Путь для сохранения

# Проверьте, существуетли файл в каталоге
if not os.path.exists(file_path):
for part in msg.iter_parts():
if part.get_content_maintype() == 'application':
filename = part.get_filename()
with open(file_path, 'wb') as f:
f.write(part.get_payload(decode=True))

mail.logout()

def main2():
subjects_and_ids , imap = get_email_subjects_and_ids()
existing_files = set(os.listdir(DIRECTORY))

for subject, e_id in subjects_and_ids:
try:
obj = json.loads(subject)
path = obj['path']
lmt = string_to_datetime(obj['lmt'])
except ValueError:
continue

isNotInDisk=False
try:
lmtRealFile = string_to_datetime(get_file_modification_time(os.path.join(DIRECTORY, path)))
except FileNotFoundError:
isNotInDisk=True

if isNotInDisk or path not in existing_files or lmtRealFile < lmt:
if download_file(e_id, subject):
pass
else:
print(f'Не удалось скачать файл: {subject}.')




if __name__ == '__main__':
main2()


Итог

Этими скриптами можно реализовать хранилище файлов в сжатом виде в почтовом ящике с append only логикой , и ими же можно синхронизировать одну папку с другой периодическими запусками.

Этого достаточно, чтобы реализовать “сетевой транспорт” для системы korni которая “транспорт агностик” и работает поверх синхронизированных любым способом данных в папках