tools.win_pg_dump_controller

Yohn Y. 2022-01-30 Child:a22dd63ba19e

0:be791d354d2a Go to Latest

tools.win_pg_dump_controller/win_pg_dump_controller/store_controller.py

..init

History
awgur@0 1 # coding: utf-8
awgur@0 2
awgur@0 3 import json
awgur@0 4 from datetime import datetime
awgur@0 5 from os.path import exists, join as p_join, basename
awgur@0 6 from os import remove as file_remove
awgur@0 7 from typing import Dict, List
awgur@0 8
awgur@0 9 from .error import Error
awgur@0 10 from .config import DBTask
awgur@0 11
awgur@0 12
awgur@0 13 class StoreError(Error):
awgur@0 14 pass
awgur@0 15
awgur@0 16
awgur@0 17 class StoreControllerBase(object):
awgur@0 18 def get_filename(self, filename: str) -> str:
awgur@0 19 raise NotImplemented()
awgur@0 20
awgur@0 21
awgur@0 22 class IndexItem(object):
awgur@0 23 def __init__(self, controller: StoreControllerBase, time: int, filename: str):
awgur@0 24 self._controller = controller
awgur@0 25 self.filename = filename
awgur@0 26 self.time = time
awgur@0 27 self._my_path = None
awgur@0 28
awgur@0 29 def get_path(self):
awgur@0 30 if self._my_path is None:
awgur@0 31 self._my_path = self._controller.get_filename(self.filename)
awgur@0 32
awgur@0 33 return self._my_path
awgur@0 34
awgur@0 35 def get_datetime(self):
awgur@0 36 return datetime.fromtimestamp(self.time)
awgur@0 37
awgur@0 38 def is_exists(self) -> bool:
awgur@0 39 return exists(self.get_path())
awgur@0 40
awgur@0 41 def to_dict(self) -> dict:
awgur@0 42 return dict((i, getattr(self, i)) for i in ('filename', 'time'))
awgur@0 43
awgur@0 44 @classmethod
awgur@0 45 def from_dict(cls, controller: StoreControllerBase, d: Dict):
awgur@0 46 return cls(
awgur@0 47 controller=controller,
awgur@0 48 **d
awgur@0 49 )
awgur@0 50
awgur@0 51 @classmethod
awgur@0 52 def new(cls, controller: StoreControllerBase, short_filename: str):
awgur@0 53 time = datetime.now()
awgur@0 54 time_prefix = time.strftime('%Y-%m-%d_%H-%M-%S')
awgur@0 55 filename = f'{time_prefix} - {short_filename}'
awgur@0 56
awgur@0 57 return cls(
awgur@0 58 controller=controller,
awgur@0 59 time=int(time.timestamp()),
awgur@0 60 filename=filename
awgur@0 61 )
awgur@0 62
awgur@0 63
awgur@0 64 class StoreController(StoreControllerBase):
awgur@0 65 def __init__(self, task: DBTask):
awgur@0 66 self.task = task
awgur@0 67 self.index_name = self.get_filename(f'{task.name}.index')
awgur@0 68 self.idx: Dict[int, IndexItem] = {}
awgur@0 69
awgur@0 70 if exists(self.index_name):
awgur@0 71 self.load_index()
awgur@0 72
awgur@0 73 def get_filename(self, filename: str) -> str:
awgur@0 74 return str(p_join(self.task.dst_dir, filename))
awgur@0 75
awgur@0 76 def load_index(self) -> None:
awgur@0 77 with open(self.index_name, 'r') as IN:
awgur@0 78 result = {}
awgur@0 79 for itm in json.load(IN):
awgur@0 80 itm = IndexItem.from_dict(self, itm)
awgur@0 81 if itm.is_exists():
awgur@0 82 result[itm.time] = itm
awgur@0 83
awgur@0 84 self.idx = result
awgur@0 85
awgur@0 86 def save_index(self) -> None:
awgur@0 87 with open(self.index_name, 'w') as OUT:
awgur@0 88 json.dump(list(map(lambda x: x.to_dict(), self.idx.values())), OUT)
awgur@0 89
awgur@0 90 def new_item(self) -> IndexItem:
awgur@0 91 return IndexItem.new(self, f'{self.task.name}.backup')
awgur@0 92
awgur@0 93 def add_item(self, item: IndexItem) -> None:
awgur@0 94 item_path = item.get_path()
awgur@0 95 if not item.is_exists():
awgur@0 96 raise StoreError(f'Storing to index file not found: {item.get_path()}')
awgur@0 97
awgur@0 98 if item.time in self.idx and self.idx[item.time].filename != item.filename:
awgur@0 99 if self.idx[item.time].is_exists():
awgur@0 100 file_remove(self.idx[item.time].get_path())
awgur@0 101
awgur@0 102 self.idx[item.time] = item
awgur@0 103
awgur@0 104 def remove(self, item: IndexItem) -> None:
awgur@0 105 if item.time in self.idx:
awgur@0 106 del self.idx[item.time]
awgur@0 107
awgur@0 108 if item.is_exists():
awgur@0 109 file_remove(item.get_path())
awgur@0 110
awgur@0 111 def __iter__(self):
awgur@0 112 for i in sorted(self.idx):
awgur@0 113 yield self.idx[i]
awgur@0 114
awgur@0 115 def clean(self, tier1_days: int, tier2_copies_interval: int, tier2_store_days: int) -> List[str]:
awgur@0 116 to_remove = []
awgur@0 117 tier2_idx = {}
awgur@0 118 now = datetime.now()
awgur@0 119
awgur@0 120 for item in self:
awgur@0 121 if not item.is_exists():
awgur@0 122 to_remove.append(item)
awgur@0 123
awgur@0 124 else:
awgur@0 125 storing_days = (now - item.get_datetime()).days
awgur@0 126
awgur@0 127 if not storing_days <= tier1_days:
awgur@0 128 if storing_days > tier2_store_days:
awgur@0 129 to_remove.append(item)
awgur@0 130
awgur@0 131 else:
awgur@0 132 # Магия: Делим старые резервные копии на эпохи. Эпоха - целая часть от деления количества
awgur@0 133 # дней, которое лежит резервная копия, на интервал, за который нам нужно хранить
awgur@0 134 # хотя бы одну копию. В одной эпохе старший файл вытесняет младший. Из вычислений
awgur@0 135 # убираем период tier1
awgur@0 136
awgur@0 137 storing_days -= tier1_days
awgur@0 138
awgur@0 139 _epoch = divmod(storing_days, tier2_copies_interval)[0]
awgur@0 140 if _epoch in tier2_idx:
awgur@0 141 to_remove.append(tier2_idx[_epoch])
awgur@0 142
awgur@0 143 tier2_idx[_epoch] = item
awgur@0 144
awgur@0 145 result = []
awgur@0 146 for item in to_remove:
awgur@0 147 file_remove(item.get_path())
awgur@0 148 result.append(item.filename)
awgur@0 149
awgur@0 150 return result