consolidator
/
GuidsSync.py
291 строка · 12.4 Кб
1
2import json
3import math
4import os
5import paramiko
6import datetime
7
8import requests
9
10class GuidDescr(object):
11def __init__(self,name:str,atime:float,mtime:float,size:int):
12self.name = name
13self.mtime = mtime
14self.atime = atime
15self.size = size
16@property
17def mtime_dt(self):
18return datetime.datetime.fromtimestamp(self.mtime)
19
20def guid_is_changed(self,input_guid)-> tuple[bool,bool]:
21"""Сравнение двух справочников по времени последней модификации
22
23Args:
24input_guid (GuidDescr): Входной справочник для сравнения
25
26Returns:
27tuple[bool,bool]: результат сравнения (Удаленный изменен, Локальный изменен)
28"""
29try:
30ing:GuidDescr = input_guid
31delta = ing.mtime-self.mtime
32# print(f"{ing.name}: {delta} : r {self.size}: l {ing.size}")
33# if self.size!=ing.size or delta>3 :
34# print(f"!!! {self.size!=ing.size} : {delta>3}")
35# return True
36# else: return False
37if delta>3: return (False,True)
38elif delta<0: return (True,False)
39else: return (False,False)
40except: raise
41
42class GuidsSync(object):
43""" Класс для синхронизации справочников с сетевым хранилищем """
44def __init__(self,host:str,user:str,password:str):
45"""Конструктор класс (инициализация соединения по ssh)
46
47Args:
48host (str): хост
49user (str): пользователь
50password (str): пароль
51"""
52
53try:
54self.rhost=host
55self.ruser=user
56self.rpass=password
57self.ssh:paramiko.SSHClient = paramiko.SSHClient()
58self.ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
59self.ssh.connect(host,username=user,password=password)
60self.sftp:paramiko.SFTPClient = self.ssh.open_sftp()
61self.local_changed:dict[str,GuidDescr] = {}
62self.remote_changed:dict[str,GuidDescr] = {}
63self.remote_dir = None
64self.local_dir = None
65except: raise
66
67def set_remote_dir(self,dir:str):
68""" Установить директорию справочников на удаленном сервере """
69self.remote_dir=dir
70
71def set_local_dir(self,dir:str):
72""" Установить локальную директорию справочников """
73self.local_dir=dir
74
75
76def close_connection(self):
77""" Закрытие соединения """
78try:
79self.sftp.close()
80self.ssh.close()
81except: raise
82
83def get_remote_files(self,dir:str)->list[GuidDescr]:
84try:
85lst=[]
86for f in self.sftp.listdir(dir):
87itemName=f"{dir}/{f}"
88stat = self.sftp.stat(itemName)
89lst.append(GuidDescr(f,stat.st_atime,stat.st_mtime,stat.st_size))
90return lst
91except: raise
92
93def get_local_files(self,dir:str)->list[GuidDescr]:
94try:
95lst=[]
96for dirname, subdirs, files in os.walk(dir):
97for f in files:
98itemName=os.path.join(dirname, f)
99stat =os.stat(itemName)
100lst.append(GuidDescr(f,stat.st_atime,stat.st_mtime,stat.st_size))
101return lst
102except: raise
103
104def check(self)->tuple[bool,bool]:
105"""Сравнение справочников на удаленном сервере с локальными
106
107Raises:
108Exception: _description_
109
110Returns:
111tuple[bool,bool]: (Есть изменения на сервере, Есть изменения локально)
112"""
113try:
114if self.remote_dir is None or self.local_dir is None:
115raise Exception("Не указаны пути к удаленным или локальным справочника")
116result = self.compare_files(self.remote_dir,self.local_dir)
117return (bool(result[0]),bool(result[1]))
118except: raise
119
120def compare_files(self,remote_dir:str,local_dir:str)->tuple[dict[str,GuidDescr],dict[str,GuidDescr]]:
121try:
122rlist = self.get_remote_files(remote_dir)
123llist = self.get_local_files(local_dir)
124rdict = {}
125ldict = {}
126for g in rlist: rdict[g.name]=g
127for g in llist: ldict[g.name]=g
128for key in ldict.keys():
129rguid:GuidDescr = rdict.get(key,None)
130if rguid is not None:
131changed = rguid.guid_is_changed(ldict[key])
132# print(f"{key} [{changed}]")
133if changed[0]: self.remote_changed[f"{remote_dir}/{key}"] = rdict[key]
134if changed[1]: self.local_changed[f"{local_dir}\\{key}"] = ldict[key]
135self.remote_dir=remote_dir
136self.local_dir=local_dir
137return (self.remote_changed,self.local_changed)
138except: raise
139
140def send_to_remote(self):
141try:
142if self.remote_dir is not None:
143for local_file in self.local_changed.keys():
144times = (self.local_changed[local_file].atime,self.local_changed[local_file].mtime)
145remote_file = f"{self.remote_dir}/{self.local_changed[local_file].name}"
146self.sftp.put(local_file,remote_file)
147self.sftp.utime(remote_file,times)
148print(f"{self.local_changed[local_file].name} [sended]")
149except: raise
150
151def get_from_remote(self):
152try:
153if self.local_dir is not None:
154for remote_file in self.remote_changed.keys():
155local_file = f"{self.local_dir}\\{self.remote_changed[remote_file].name}"
156self.sftp.get(remote_file,local_file)
157print(f"{self.remote_changed[remote_file].name} [accepted]")
158except: raise
159
160def reconnect(self):
161try:
162self.sftp.close()
163self.ssh.close()
164self.ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
165self.ssh.connect(hostname=self.rhost,username=self.ruser,password=self.rpass)
166self.sftp = self.ssh.open_sftp()
167self.local_changed:dict[str,GuidDescr] = {}
168self.remote_changed:dict[str,GuidDescr] = {}
169except: raise
170
171class ServerList(object):
172""" Список файлов на сервере (nginx + json) """
173def __init__(self,data:bytes) -> None:
174try:
175self.bytes_data = data
176tmp_str = self.bytes_data.decode("utf-8")
177self.json_data = json.loads(tmp_str)
178self.files = {}
179for item in self.json_data:
180dt = datetime.datetime.strptime(item["mtime"],"%a, %d %b %Y %H:%M:%S %Z")+datetime.timedelta(hours=3)
181self.files[item["name"]]=(dt.timestamp(),item["size"])
182self.is_empty = not bool(self.files)
183except: raise
184
185class LocalList(object):
186""" Локальный список файлов """
187def __init__(self,dir:str,filter:str = ".xlsx") -> None:
188try:
189self.dir_list = [fi for fi in os.listdir(dir) if "~$" not in fi and filter in fi]
190self.files={}
191for file in self.dir_list:
192fullName= f"{dir}\\{file}"
193stat = os.stat(fullName)
194self.files[file]=(stat.st_mtime,stat.st_size)
195self.is_empty = not bool(self.files)
196except: raise
197
198class Synchronizer(object):
199""" Синхронизатор файлов """
200def __init__(self,repo_url:str,local_dir:str,file_filter:str=".xlsx") -> None:
201"""_summary_
202
203Args:
204repo_url (str): URL удаленного репозитория
205local_dir (str): Локальная папка
206file_filter (str, optional): Фильтр локальных файлов. Defaults to ".xlsx".
207"""
208try:
209self.repo = repo_url
210self.local_dir = local_dir
211self.server_files_empty = True
212try:
213r = requests.get(self.repo)
214self.server_files = ServerList(r.content)
215self.server_files_empty = self.server_files.is_empty
216except: raise Exception("Bad repo")
217self.local_files = LocalList(self.local_dir,filter=file_filter)
218self.local_files_empty = self.local_files.is_empty
219self.newer_server = {}
220self.newer_local = {}
221self.newer_server_exist = False
222self.newer_local_exist = False
223if not self.local_files_empty and not self.server_files_empty:
224for key in self.local_files.files.keys():
225if (self.local_files.files[key][0]-self.server_files.files[key][0]) > 1:
226self.newer_local[key]=self.local_files.files[key]
227self.newer_local_exist = True
228elif (self.local_files.files[key][0]-self.server_files.files[key][0]) < -1:
229self.newer_server[key]=self.server_files.files[key]
230self.newer_server_exist = True
231except: raise
232
233def sync_local(self):
234""" Синхронизировать локальные файлы на сервер """
235try:
236if self.newer_local_exist:
237for key in self.newer_local.keys():
238fullName=f"{self.local_dir}\\{key}"
239stat = os.stat(fullName)
240with open(fullName,"rb") as datafile:
241r = requests.put(f"{self.repo}{key}",data=datafile)
242dt= math.trunc(datetime.datetime.now().timestamp())
243datafile.close()
244os.utime(fullName,(stat.st_atime,dt))
245except: raise
246
247def sync_server(self):
248""" Синхронизировать серверные файлы на локальную машину """
249try:
250if self.newer_server_exist:
251for key in self.newer_server.keys():
252fullName=f"{self.local_dir}\\{key}"
253stat = os.stat(fullName)
254url = f"{self.repo}{key}"
255r = requests.get(url)
256open(fullName,"wb").write(r.content)
257os.utime(fullName,(stat.st_atime,self.newer_server[key][0]))
258except: raise
259
260def sync_all(self):
261""" Синхронизировать все файлы """
262try:
263self.sync_local()
264self.sync_server()
265except: raise
266
267def init_local(self):
268""" Загрузить файлы с удаленного хоста в локальную папку """
269try:
270for key in self.server_files.files.keys():
271fullName=f"{self.local_dir}\\{key}"
272url = f"{self.repo}{key}"
273r = requests.get(url)
274open(fullName,"wb").write(r.content)
275os.utime(fullName,(self.server_files.files[key][0],self.server_files.files[key][0]))
276print(f"'{key}' downloaded {self.server_files.files[key][1]} bytes")
277except: raise
278
279def init_server(self):
280""" Загрузить файлы на удаленный хост из локального хранилища """
281try:
282for key in self.local_files.files.keys():
283fullName=f"{self.local_dir}\\{key}"
284stat = os.stat(fullName)
285url = f"{self.repo}{key}"
286with open(fullName,"rb") as datafile:
287r = requests.put(url,data=datafile)
288dt= math.trunc(datetime.datetime.now().timestamp())
289datafile.close()
290os.utime(fullName,(stat.st_atime,dt))
291print(f"'{key}' uploaded {self.local_files.files[key][1]} bytes")
292except: raise
293
294