tools.win_pg_dump_controller
2022-01-30
Child:a22dd63ba19e
tools.win_pg_dump_controller/win_pg_dump_controller/store_controller.py
..init
| 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 |