| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | """Store matching history""" | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | from datetime import datetime | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | from schema import Schema, And, Use, Optional | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  | from typing import Protocol | 
					
						
							| 
									
										
										
										
											2024-08-10 10:58:31 +01:00
										 |  |  | import files | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | import copy | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | _FILE = "history.json" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Warning: Changing any of the below needs proper thought to ensure backwards compatibility | 
					
						
							|  |  |  | _DEFAULT_DICT = { | 
					
						
							|  |  |  |     "history": {}, | 
					
						
							|  |  |  |     "matchees": {} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | _TIME_FORMAT = "%a %b %d %H:%M:%S %Y" | 
					
						
							|  |  |  | _SCHEMA = Schema( | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         Optional("history"): { | 
					
						
							|  |  |  |             Optional(str): {  # a datetime | 
					
						
							|  |  |  |                 "groups": [ | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         "members": [ | 
					
						
							|  |  |  |                             # The ID of each matchee in the match | 
					
						
							|  |  |  |                             And(Use(int)) | 
					
						
							|  |  |  |                         ] | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         Optional("matchees"): { | 
					
						
							|  |  |  |             Optional(str): { | 
					
						
							|  |  |  |                 Optional("matches"): { | 
					
						
							|  |  |  |                     # Matchee ID and Datetime pair | 
					
						
							|  |  |  |                     Optional(str): And(Use(str)) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  | class Member(Protocol): | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def id(self) -> int: | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | def ts_to_datetime(ts: str) -> datetime: | 
					
						
							|  |  |  |     """Convert a ts to datetime using the history format""" | 
					
						
							|  |  |  |     return datetime.strptime(ts, _TIME_FORMAT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def validate(dict: dict): | 
					
						
							|  |  |  |     """Initialise and validate the history""" | 
					
						
							|  |  |  |     _SCHEMA.validate(dict) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | class History(): | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |     def __init__(self, data: dict = _DEFAULT_DICT): | 
					
						
							|  |  |  |         """Initialise and validate the history""" | 
					
						
							|  |  |  |         validate(data) | 
					
						
							|  |  |  |         self.__dict__ = copy.deepcopy(data) | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |     def history(self) -> list[dict]: | 
					
						
							|  |  |  |         return self.__dict__["history"] | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  |     def matchees(self) -> dict[str, dict]: | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  |         return self.__dict__["matchees"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def save(self) -> None: | 
					
						
							|  |  |  |         """Save out the history""" | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |         files.save(_FILE, self.__dict__) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def oldest(self) -> datetime: | 
					
						
							|  |  |  |         """Grab the oldest timestamp in history""" | 
					
						
							|  |  |  |         if not self.history: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         times = (ts_to_datetime(dt) for dt in self.history.keys()) | 
					
						
							|  |  |  |         return sorted(times)[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def log_groups_to_history(self, groups: list[list[Member]], ts: datetime = datetime.now()) -> None: | 
					
						
							|  |  |  |         """Log the groups""" | 
					
						
							|  |  |  |         tmp_history = History(self.__dict__) | 
					
						
							|  |  |  |         ts = datetime.strftime(ts, _TIME_FORMAT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Grab or create the hitory item for this set of groups | 
					
						
							|  |  |  |         history_item = {} | 
					
						
							|  |  |  |         tmp_history.history[ts] = history_item | 
					
						
							|  |  |  |         history_item_groups = [] | 
					
						
							|  |  |  |         history_item["groups"] = history_item_groups | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  |         for group in groups: | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Add the group data | 
					
						
							|  |  |  |             history_item_groups.append({ | 
					
						
							|  |  |  |                 "members": list(m.id for m in group) | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  |             }) | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Update the matchee data with the matches | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  |             for m in group: | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |                 matchee = tmp_history.matchees.get(str(m.id), {}) | 
					
						
							|  |  |  |                 matchee_matches = matchee.get("matches", {}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  |                 for o in (o for o in group if o.id != m.id): | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |                     matchee_matches[str(o.id)] = ts | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |                 matchee["matches"] = matchee_matches | 
					
						
							|  |  |  |                 tmp_history.matchees[str(m.id)] = matchee | 
					
						
							| 
									
										
										
										
											2024-08-10 10:55:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |         # Validate before storing the result | 
					
						
							|  |  |  |         validate(self.__dict__) | 
					
						
							|  |  |  |         self.__dict__ = tmp_history.__dict__ | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  |     def save_groups_to_history(self, groups: list[list[Member]]) -> None: | 
					
						
							|  |  |  |         """Save out the groups to the history file""" | 
					
						
							|  |  |  |         self.log_groups_to_history(groups) | 
					
						
							|  |  |  |         self.save() | 
					
						
							| 
									
										
										
										
											2024-08-10 10:45:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-10 15:12:14 +01:00
										 |  |  | def load() -> History: | 
					
						
							|  |  |  |     """Load the history""" | 
					
						
							|  |  |  |     return History(files.load(_FILE) if os.path.isfile(_FILE) else _DEFAULT_DICT) |