tools.win_pg_dump_controller
2022-07-11
Parent:a38a008ce3e8
tools.win_pg_dump_controller/win_pg_dump_controller/store_controller.py
.. import D6
| awgur@0 | 1 # coding: utf-8 |
| awgur@0 | 2 |
| awgur@0 | 3 import json |
| awgur@0 | 4 from datetime import datetime |
| awgur@7 | 5 from os.path import exists, join as p_join |
| awgur@0 | 6 from os import remove as file_remove |
| awgur@0 | 7 from typing import Dict, List |
| awgur@7 | 8 from dataclasses import dataclass, asdict |
| awgur@7 | 9 from os import stat as f_stat |
| awgur@0 | 10 |
| awgur@0 | 11 from .error import Error |
| awgur@0 | 12 from .config import DBTask |
| awgur@0 | 13 |
| awgur@0 | 14 |
| awgur@0 | 15 class StoreError(Error): |
| awgur@0 | 16 pass |
| awgur@0 | 17 |
| awgur@0 | 18 |
| awgur@0 | 19 class StoreControllerBase(object): |
| awgur@0 | 20 def get_filename(self, filename: str) -> str: |
| awgur@0 | 21 raise NotImplemented() |
| awgur@0 | 22 |
| awgur@0 | 23 |
| awgur@0 | 24 class IndexItem(object): |
| awgur@4 | 25 def __init__(self, controller: StoreControllerBase, time: int, filename: str, epoch: int = 0): |
| awgur@0 | 26 self._controller = controller |
| awgur@0 | 27 self.filename = filename |
| awgur@0 | 28 self.time = time |
| awgur@4 | 29 self.epoch = epoch |
| awgur@0 | 30 self._my_path = None |
| awgur@0 | 31 |
| awgur@0 | 32 def get_path(self): |
| awgur@0 | 33 if self._my_path is None: |
| awgur@0 | 34 self._my_path = self._controller.get_filename(self.filename) |
| awgur@0 | 35 |
| awgur@0 | 36 return self._my_path |
| awgur@0 | 37 |
| awgur@4 | 38 def set_epoch(self, num_days: int): |
| awgur@4 | 39 """\ |
| awgur@4 | 40 Устанавливает значение эпохи для элемиента в соответствии с количеством дней в эпохе |
| awgur@4 | 41 """ |
| awgur@4 | 42 self.epoch = divmod(self.time, num_days * 86400)[0] # 86400 - секунд в сутках |
| awgur@4 | 43 |
| awgur@1 | 44 def __str__(self) -> str: |
| awgur@1 | 45 return self.filename |
| awgur@1 | 46 |
| awgur@4 | 47 def full_desc(self) -> str: |
| awgur@4 | 48 return f'{self.filename} [epoch="{self.epoch}" created={self.get_datetime()}]' |
| awgur@4 | 49 |
| awgur@0 | 50 def get_datetime(self): |
| awgur@0 | 51 return datetime.fromtimestamp(self.time) |
| awgur@0 | 52 |
| awgur@0 | 53 def is_exists(self) -> bool: |
| awgur@0 | 54 return exists(self.get_path()) |
| awgur@0 | 55 |
| awgur@0 | 56 def to_dict(self) -> dict: |
| awgur@4 | 57 return dict((i, getattr(self, i)) for i in ('filename', 'time', 'epoch')) |
| awgur@0 | 58 |
| awgur@0 | 59 @classmethod |
| awgur@0 | 60 def from_dict(cls, controller: StoreControllerBase, d: Dict): |
| awgur@0 | 61 return cls( |
| awgur@0 | 62 controller=controller, |
| awgur@0 | 63 **d |
| awgur@0 | 64 ) |
| awgur@0 | 65 |
| awgur@0 | 66 @classmethod |
| awgur@0 | 67 def new(cls, controller: StoreControllerBase, short_filename: str): |
| awgur@0 | 68 time = datetime.now() |
| awgur@0 | 69 time_prefix = time.strftime('%Y-%m-%d_%H-%M-%S') |
| awgur@0 | 70 filename = f'{time_prefix} - {short_filename}' |
| awgur@0 | 71 |
| awgur@0 | 72 return cls( |
| awgur@0 | 73 controller=controller, |
| awgur@0 | 74 time=int(time.timestamp()), |
| awgur@0 | 75 filename=filename |
| awgur@0 | 76 ) |
| awgur@0 | 77 |
| awgur@0 | 78 |
| awgur@7 | 79 @dataclass() |
| awgur@7 | 80 class TaskStatus(object): |
| awgur@7 | 81 status: bool = False |
| awgur@7 | 82 size: int = 0 |
| awgur@7 | 83 time: int = 0 |
| awgur@7 | 84 |
| awgur@7 | 85 |
| awgur@0 | 86 class StoreController(StoreControllerBase): |
| awgur@0 | 87 def __init__(self, task: DBTask): |
| awgur@0 | 88 self.task = task |
| awgur@0 | 89 self.index_name = self.get_filename(f'{task.name}.index') |
| awgur@7 | 90 self.status_name = self.get_filename(f'{task.name}.status') |
| awgur@0 | 91 self.idx: Dict[int, IndexItem] = {} |
| awgur@1 | 92 self.op_adv_status = [] |
| awgur@0 | 93 |
| awgur@0 | 94 if exists(self.index_name): |
| awgur@0 | 95 self.load_index() |
| awgur@0 | 96 |
| awgur@0 | 97 def get_filename(self, filename: str) -> str: |
| awgur@0 | 98 return str(p_join(self.task.dst_dir, filename)) |
| awgur@0 | 99 |
| awgur@0 | 100 def load_index(self) -> None: |
| awgur@0 | 101 with open(self.index_name, 'r') as IN: |
| awgur@0 | 102 result = {} |
| awgur@0 | 103 for itm in json.load(IN): |
| awgur@0 | 104 itm = IndexItem.from_dict(self, itm) |
| awgur@0 | 105 if itm.is_exists(): |
| awgur@0 | 106 result[itm.time] = itm |
| awgur@0 | 107 |
| awgur@0 | 108 self.idx = result |
| awgur@0 | 109 |
| awgur@4 | 110 def set_epoch(self, num_days: int): |
| awgur@4 | 111 for i in self.idx.values(): |
| awgur@4 | 112 i.set_epoch(num_days) |
| awgur@4 | 113 |
| awgur@0 | 114 def save_index(self) -> None: |
| awgur@0 | 115 with open(self.index_name, 'w') as OUT: |
| awgur@0 | 116 json.dump(list(map(lambda x: x.to_dict(), self.idx.values())), OUT) |
| awgur@0 | 117 |
| awgur@1 | 118 def add_op_status(self, msg: str) -> None: |
| awgur@1 | 119 self.op_adv_status.append(msg) |
| awgur@1 | 120 |
| awgur@0 | 121 def new_item(self) -> IndexItem: |
| awgur@0 | 122 return IndexItem.new(self, f'{self.task.name}.backup') |
| awgur@0 | 123 |
| awgur@0 | 124 def add_item(self, item: IndexItem) -> None: |
| awgur@0 | 125 if not item.is_exists(): |
| awgur@0 | 126 raise StoreError(f'Storing to index file not found: {item.get_path()}') |
| awgur@0 | 127 |
| awgur@0 | 128 if item.time in self.idx and self.idx[item.time].filename != item.filename: |
| awgur@0 | 129 if self.idx[item.time].is_exists(): |
| awgur@0 | 130 file_remove(self.idx[item.time].get_path()) |
| awgur@0 | 131 |
| awgur@0 | 132 self.idx[item.time] = item |
| awgur@0 | 133 |
| awgur@0 | 134 def remove(self, item: IndexItem) -> None: |
| awgur@0 | 135 if item.time in self.idx: |
| awgur@0 | 136 del self.idx[item.time] |
| awgur@0 | 137 |
| awgur@0 | 138 if item.is_exists(): |
| awgur@0 | 139 file_remove(item.get_path()) |
| awgur@0 | 140 |
| awgur@7 | 141 def status(self, last_status: bool): |
| awgur@7 | 142 res = TaskStatus(status=last_status) |
| awgur@7 | 143 |
| awgur@7 | 144 for i in self.idx.values(): |
| awgur@7 | 145 if res.time < i.time: |
| awgur@7 | 146 res.time = i.time |
| awgur@7 | 147 |
| awgur@7 | 148 res.size += f_stat(i.get_path()).st_size |
| awgur@7 | 149 |
| awgur@7 | 150 with open(self.status_name, 'w') as OUT: |
| awgur@7 | 151 json.dump(asdict(res), OUT) |
| awgur@7 | 152 |
| awgur@0 | 153 def __iter__(self): |
| awgur@1 | 154 for i in sorted(self.idx, reverse=True): |
| awgur@0 | 155 yield self.idx[i] |
| awgur@0 | 156 |
| awgur@4 | 157 def clean(self, tier1_days: int, tier2_store_days: int, epoch_days: int) -> List[str]: |
| awgur@0 | 158 to_remove = [] |
| awgur@0 | 159 tier2_idx = {} |
| awgur@0 | 160 now = datetime.now() |
| awgur@0 | 161 |
| awgur@4 | 162 self.set_epoch(epoch_days) |
| awgur@4 | 163 |
| awgur@0 | 164 for item in self: |
| awgur@0 | 165 if not item.is_exists(): |
| awgur@0 | 166 to_remove.append(item) |
| awgur@0 | 167 |
| awgur@0 | 168 else: |
| awgur@0 | 169 storing_days = (now - item.get_datetime()).days |
| awgur@0 | 170 |
| awgur@0 | 171 if not storing_days <= tier1_days: |
| awgur@0 | 172 if storing_days > tier2_store_days: |
| awgur@0 | 173 to_remove.append(item) |
| awgur@0 | 174 |
| awgur@0 | 175 else: |
| awgur@4 | 176 tier2_storing_days = storing_days - tier1_days |
| awgur@0 | 177 |
| awgur@4 | 178 self.add_op_status(f'"{item.full_desc()}": storing_days={storing_days}' |
| awgur@4 | 179 f' tier2_storing_days={tier2_storing_days}') |
| awgur@0 | 180 |
| awgur@4 | 181 if item.epoch in tier2_idx: |
| awgur@4 | 182 to_remove.append(tier2_idx[item.epoch]) |
| awgur@1 | 183 |
| awgur@4 | 184 tier2_idx[item.epoch] = item |
| awgur@0 | 185 |
| awgur@0 | 186 result = [] |
| awgur@0 | 187 for item in to_remove: |
| awgur@0 | 188 file_remove(item.get_path()) |
| awgur@0 | 189 result.append(item.filename) |
| awgur@0 | 190 |
| awgur@0 | 191 return result |