tools.win_pg_dump_controller
0:be791d354d2a
Go to Latest
tools.win_pg_dump_controller/win_pg_dump_controller/store_controller.py
..init
4 from datetime import datetime
5 from os.path import exists, join as p_join, basename
6 from os import remove as file_remove
7 from typing import Dict, List
9 from .error import Error
10 from .config import DBTask
13 class StoreError(Error):
17 class StoreControllerBase(object):
18 def get_filename(self, filename: str) -> str:
19 raise NotImplemented()
22 class IndexItem(object):
23 def __init__(self, controller: StoreControllerBase, time: int, filename: str):
24 self._controller = controller
25 self.filename = filename
30 if self._my_path is None:
31 self._my_path = self._controller.get_filename(self.filename)
35 def get_datetime(self):
36 return datetime.fromtimestamp(self.time)
38 def is_exists(self) -> bool:
39 return exists(self.get_path())
41 def to_dict(self) -> dict:
42 return dict((i, getattr(self, i)) for i in ('filename', 'time'))
45 def from_dict(cls, controller: StoreControllerBase, d: Dict):
47 controller=controller,
52 def new(cls, controller: StoreControllerBase, short_filename: str):
54 time_prefix = time.strftime('%Y-%m-%d_%H-%M-%S')
55 filename = f'{time_prefix} - {short_filename}'
58 controller=controller,
59 time=int(time.timestamp()),
64 class StoreController(StoreControllerBase):
65 def __init__(self, task: DBTask):
67 self.index_name = self.get_filename(f'{task.name}.index')
68 self.idx: Dict[int, IndexItem] = {}
70 if exists(self.index_name):
73 def get_filename(self, filename: str) -> str:
74 return str(p_join(self.task.dst_dir, filename))
76 def load_index(self) -> None:
77 with open(self.index_name, 'r') as IN:
79 for itm in json.load(IN):
80 itm = IndexItem.from_dict(self, itm)
82 result[itm.time] = itm
86 def save_index(self) -> None:
87 with open(self.index_name, 'w') as OUT:
88 json.dump(list(map(lambda x: x.to_dict(), self.idx.values())), OUT)
90 def new_item(self) -> IndexItem:
91 return IndexItem.new(self, f'{self.task.name}.backup')
93 def add_item(self, item: IndexItem) -> None:
94 item_path = item.get_path()
95 if not item.is_exists():
96 raise StoreError(f'Storing to index file not found: {item.get_path()}')
98 if item.time in self.idx and self.idx[item.time].filename != item.filename:
99 if self.idx[item.time].is_exists():
100 file_remove(self.idx[item.time].get_path())
102 self.idx[item.time] = item
104 def remove(self, item: IndexItem) -> None:
105 if item.time in self.idx:
106 del self.idx[item.time]
109 file_remove(item.get_path())
112 for i in sorted(self.idx):
115 def clean(self, tier1_days: int, tier2_copies_interval: int, tier2_store_days: int) -> List[str]:
121 if not item.is_exists():
122 to_remove.append(item)
125 storing_days = (now - item.get_datetime()).days
127 if not storing_days <= tier1_days:
128 if storing_days > tier2_store_days:
129 to_remove.append(item)
132 # Магия: Делим старые резервные копии на эпохи. Эпоха - целая часть от деления количества
133 # дней, которое лежит резервная копия, на интервал, за который нам нужно хранить
134 # хотя бы одну копию. В одной эпохе старший файл вытесняет младший. Из вычислений
135 # убираем период tier1
137 storing_days -= tier1_days
139 _epoch = divmod(storing_days, tier2_copies_interval)[0]
140 if _epoch in tier2_idx:
141 to_remove.append(tier2_idx[_epoch])
143 tier2_idx[_epoch] = item
146 for item in to_remove:
147 file_remove(item.get_path())
148 result.append(item.filename)