tools.win_pg_dump_controller
tools.win_pg_dump_controller/win_pg_dump_controller/store_controller.py
Добавлен GIT репозиторий для тестов
| 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@4 | 23 def __init__(self, controller: StoreControllerBase, time: int, filename: str, epoch: int = 0): |
| awgur@0 | 24 self._controller = controller |
| awgur@0 | 25 self.filename = filename |
| awgur@0 | 26 self.time = time |
| awgur@4 | 27 self.epoch = epoch |
| awgur@0 | 28 self._my_path = None |
| awgur@0 | 29 |
| awgur@0 | 30 def get_path(self): |
| awgur@0 | 31 if self._my_path is None: |
| awgur@0 | 32 self._my_path = self._controller.get_filename(self.filename) |
| awgur@0 | 33 |
| awgur@0 | 34 return self._my_path |
| awgur@0 | 35 |
| awgur@4 | 36 def set_epoch(self, num_days: int): |
| awgur@4 | 37 """\ |
| awgur@4 | 38 Устанавливает значение эпохи для элемиента в соответствии с количеством дней в эпохе |
| awgur@4 | 39 """ |
| awgur@4 | 40 self.epoch = divmod(self.time, num_days * 86400)[0] # 86400 - секунд в сутках |
| awgur@4 | 41 |
| awgur@1 | 42 def __str__(self) -> str: |
| awgur@1 | 43 return self.filename |
| awgur@1 | 44 |
| awgur@4 | 45 def full_desc(self) -> str: |
| awgur@4 | 46 return f'{self.filename} [epoch="{self.epoch}" created={self.get_datetime()}]' |
| awgur@4 | 47 |
| awgur@0 | 48 def get_datetime(self): |
| awgur@0 | 49 return datetime.fromtimestamp(self.time) |
| awgur@0 | 50 |
| awgur@0 | 51 def is_exists(self) -> bool: |
| awgur@0 | 52 return exists(self.get_path()) |
| awgur@0 | 53 |
| awgur@0 | 54 def to_dict(self) -> dict: |
| awgur@4 | 55 return dict((i, getattr(self, i)) for i in ('filename', 'time', 'epoch')) |
| awgur@0 | 56 |
| awgur@0 | 57 @classmethod |
| awgur@0 | 58 def from_dict(cls, controller: StoreControllerBase, d: Dict): |
| awgur@0 | 59 return cls( |
| awgur@0 | 60 controller=controller, |
| awgur@0 | 61 **d |
| awgur@0 | 62 ) |
| awgur@0 | 63 |
| awgur@0 | 64 @classmethod |
| awgur@0 | 65 def new(cls, controller: StoreControllerBase, short_filename: str): |
| awgur@0 | 66 time = datetime.now() |
| awgur@0 | 67 time_prefix = time.strftime('%Y-%m-%d_%H-%M-%S') |
| awgur@0 | 68 filename = f'{time_prefix} - {short_filename}' |
| awgur@0 | 69 |
| awgur@0 | 70 return cls( |
| awgur@0 | 71 controller=controller, |
| awgur@0 | 72 time=int(time.timestamp()), |
| awgur@0 | 73 filename=filename |
| awgur@0 | 74 ) |
| awgur@0 | 75 |
| awgur@0 | 76 |
| awgur@0 | 77 class StoreController(StoreControllerBase): |
| awgur@0 | 78 def __init__(self, task: DBTask): |
| awgur@0 | 79 self.task = task |
| awgur@0 | 80 self.index_name = self.get_filename(f'{task.name}.index') |
| awgur@0 | 81 self.idx: Dict[int, IndexItem] = {} |
| awgur@1 | 82 self.op_adv_status = [] |
| awgur@0 | 83 |
| awgur@0 | 84 if exists(self.index_name): |
| awgur@0 | 85 self.load_index() |
| awgur@0 | 86 |
| awgur@0 | 87 def get_filename(self, filename: str) -> str: |
| awgur@0 | 88 return str(p_join(self.task.dst_dir, filename)) |
| awgur@0 | 89 |
| awgur@0 | 90 def load_index(self) -> None: |
| awgur@0 | 91 with open(self.index_name, 'r') as IN: |
| awgur@0 | 92 result = {} |
| awgur@0 | 93 for itm in json.load(IN): |
| awgur@0 | 94 itm = IndexItem.from_dict(self, itm) |
| awgur@0 | 95 if itm.is_exists(): |
| awgur@0 | 96 result[itm.time] = itm |
| awgur@0 | 97 |
| awgur@0 | 98 self.idx = result |
| awgur@0 | 99 |
| awgur@4 | 100 def set_epoch(self, num_days: int): |
| awgur@4 | 101 for i in self.idx.values(): |
| awgur@4 | 102 i.set_epoch(num_days) |
| awgur@4 | 103 |
| awgur@0 | 104 def save_index(self) -> None: |
| awgur@0 | 105 with open(self.index_name, 'w') as OUT: |
| awgur@0 | 106 json.dump(list(map(lambda x: x.to_dict(), self.idx.values())), OUT) |
| awgur@0 | 107 |
| awgur@1 | 108 def add_op_status(self, msg: str) -> None: |
| awgur@1 | 109 self.op_adv_status.append(msg) |
| awgur@1 | 110 |
| awgur@0 | 111 def new_item(self) -> IndexItem: |
| awgur@0 | 112 return IndexItem.new(self, f'{self.task.name}.backup') |
| awgur@0 | 113 |
| awgur@0 | 114 def add_item(self, item: IndexItem) -> None: |
| awgur@0 | 115 if not item.is_exists(): |
| awgur@0 | 116 raise StoreError(f'Storing to index file not found: {item.get_path()}') |
| awgur@0 | 117 |
| awgur@0 | 118 if item.time in self.idx and self.idx[item.time].filename != item.filename: |
| awgur@0 | 119 if self.idx[item.time].is_exists(): |
| awgur@0 | 120 file_remove(self.idx[item.time].get_path()) |
| awgur@0 | 121 |
| awgur@0 | 122 self.idx[item.time] = item |
| awgur@0 | 123 |
| awgur@0 | 124 def remove(self, item: IndexItem) -> None: |
| awgur@0 | 125 if item.time in self.idx: |
| awgur@0 | 126 del self.idx[item.time] |
| awgur@0 | 127 |
| awgur@0 | 128 if item.is_exists(): |
| awgur@0 | 129 file_remove(item.get_path()) |
| awgur@0 | 130 |
| awgur@0 | 131 def __iter__(self): |
| awgur@1 | 132 for i in sorted(self.idx, reverse=True): |
| awgur@0 | 133 yield self.idx[i] |
| awgur@0 | 134 |
| awgur@4 | 135 def clean(self, tier1_days: int, tier2_store_days: int, epoch_days: int) -> List[str]: |
| awgur@0 | 136 to_remove = [] |
| awgur@0 | 137 tier2_idx = {} |
| awgur@0 | 138 now = datetime.now() |
| awgur@0 | 139 |
| awgur@4 | 140 self.set_epoch(epoch_days) |
| awgur@4 | 141 |
| awgur@0 | 142 for item in self: |
| awgur@0 | 143 if not item.is_exists(): |
| awgur@0 | 144 to_remove.append(item) |
| awgur@0 | 145 |
| awgur@0 | 146 else: |
| awgur@0 | 147 storing_days = (now - item.get_datetime()).days |
| awgur@0 | 148 |
| awgur@0 | 149 if not storing_days <= tier1_days: |
| awgur@0 | 150 if storing_days > tier2_store_days: |
| awgur@0 | 151 to_remove.append(item) |
| awgur@0 | 152 |
| awgur@0 | 153 else: |
| awgur@4 | 154 tier2_storing_days = storing_days - tier1_days |
| awgur@0 | 155 |
| awgur@4 | 156 self.add_op_status(f'"{item.full_desc()}": storing_days={storing_days}' |
| awgur@4 | 157 f' tier2_storing_days={tier2_storing_days}') |
| awgur@0 | 158 |
| awgur@4 | 159 if item.epoch in tier2_idx: |
| awgur@4 | 160 to_remove.append(tier2_idx[item.epoch]) |
| awgur@1 | 161 |
| awgur@4 | 162 tier2_idx[item.epoch] = item |
| awgur@0 | 163 |
| awgur@0 | 164 result = [] |
| awgur@0 | 165 for item in to_remove: |
| awgur@0 | 166 file_remove(item.get_path()) |
| awgur@0 | 167 result.append(item.filename) |
| awgur@0 | 168 |
| awgur@0 | 169 return result |