import os import json import sqlite3 import threading import time import logging LOG = logging.getLogger(__name__) class BotFileDb: def __init__(self, path_record_file: str): self.path_record_file = path_record_file pass def check_has_record(self): record = {} if os.path.exists(self.path_record_file): try: with open(self.path_record_file, "r", encoding="utf8") as f: record = json.load(f) except Exception as e: LOG.error(f"Failed to load the saved records file: {e}") return None, False, False if not record or record == {}: return None, False, False is_stars_exists = False is_vs_exists = False if ( "stars" in record.keys() and record["stars"] != [] and len(record["stars"]) > 0 ): is_stars_exists = True if "vs" in record.keys() and record["vs"] != [] and len(record["vs"]) > 0: is_vs_exists = True return record, is_stars_exists, is_vs_exists def check_star_exists_by_id(self, star_id: str): record, exists, _ = self.check_has_record() if not record or not exists: return False stars = record["stars"] for star in stars: if star["id"].lower() == star_id.lower(): return True def check_id_exists(self, id: str): record, _, exists = self.check_has_record() if not record or not exists: return False vs = record["vs"] for v in vs: if v["id"].lower() == id.lower(): return True def renew_record(self, record: dict): try: with open(self.path_record_file, "w", encoding="utf8") as f: json.dump( record, f, separators=(",", ": "), indent=4, ensure_ascii=False ) return True except Exception as e: LOG.error(f"Failed to update the saved records file: {e}") return False def record_star_by_name_id(self, star_name: str, star_id: str): record, is_stars_exists, _ = self.check_has_record() if not record: record, stars = {}, [] else: if not is_stars_exists: stars = [] else: stars = record["stars"] for star in stars: if star["id"].lower() == star_id.lower(): return True stars.append({"name": star_name, "id": star_id.lower()}) record["stars"] = stars return self.renew_record(record) def record_id_by_id_stars(self, id: str, stars: list): record, _, is_vs_exists = self.check_has_record() if not record: record, vs = {}, [] else: if not is_vs_exists: vs = [] else: vs = record["vs"] for v in vs: if v["id"].lower() == id.lower(): return True vs.append({"id": id.lower(), "stars": stars}) record["vs"] = vs return self.renew_record(record) def undo_record_star_by_id(self, star_id: str): record, exists, _ = self.check_has_record() if not record or not exists: return False stars = record["stars"] exists = False for i, star in enumerate(stars): if star["id"].lower() == star_id.lower(): del stars[i] exists = True break if exists: record["stars"] = stars return self.renew_record(record) return True def undo_record_id(self, id: str): record, _, exists = self.check_has_record() if not record or not exists: return False vs = record["vs"] exists = False for i, v in enumerate(vs): if v["id"].lower() == id.lower(): del vs[i] exists = True break if exists: record["vs"] = vs return self.renew_record(record) return True class BotCacheDb: CACHE_BT = { "prefix": "bt-", "expire": 3600 * 24 * 30, } CACHE_V = { "prefix": "v-", "expire": 3600 * 24 * 30, } CACHE_STAR = { "prefix": "star-", "expire": 0, # never expire } CACHE_RANK = { "prefix": "rank-", "expire": 3600 * 24 * 7, } CACHE_SAMPLE = { "prefix": "sample-", "expire": 3600 * 24 * 30, } CACHE_MAGNET = { "prefix": "magnet-", "expire": 3600 * 24 * 5, } CACHE_PV = { "prefix": "pv-", "expire": 3600 * 24 * 15, } CACHE_FV = { "prefix": "fv-", "expire": 3600 * 24 * 15, } CACHE_STARS_MSG = { "prefix": "stars-msg-", "expire": 3600 * 24 * 5, } CACHE_COMMENT = {"prefix": "comment-", "expire": 3600 * 24 * 30} CACHE_NICE_VS_OF_STAR = { "prefix": "nice-vs-of-star-", "expire": 3600 * 24 * 15, } CACHE_JLIB_PAGE_NICE_VS = { "prefix": "jlib-page-nice-vs-", "expire": 3600 * 24 * 7, } CACHE_JLIB_PAGE_NEW_VS = { "prefix": "jlib-page-new-vs-", "expire": 3600 * 24 * 2, } CACHE_STAR_JA_NAME = {"prefix": "star-ja-name-", "expire": 3600 * 24 * 30 * 6} CACHE_NEW_VS_OF_STAR = { "prefix": "new-vs-of-star-", "expire": 3600 * 24 * 12, } TYPE_V = 1 TYPE_STAR = 2 TYPE_RANK = 3 TYPE_SAMPLE = 4 TYPE_MAGNET = 5 TYPE_PV = 6 TYPE_FV = 7 TYPE_STARS_MSG = 8 TYPE_COMMENT = 10 TYPE_NICE_VS_OF_STAR = 11 TYPE_JLIB_PAGE_NICE_VS = 12 TYPE_JLIB_PAGE_NEW_VS = 13 TYPE_STAR_JA_NAME = 14 TYPE_NEW_VS_OF_STAR = 16 TYPE_BT = 17 TYPE_MAP = { TYPE_V: CACHE_V, TYPE_STAR: CACHE_STAR, TYPE_RANK: CACHE_RANK, TYPE_SAMPLE: CACHE_SAMPLE, TYPE_MAGNET: CACHE_MAGNET, TYPE_PV: CACHE_PV, TYPE_FV: CACHE_FV, TYPE_STARS_MSG: CACHE_STARS_MSG, TYPE_COMMENT: CACHE_COMMENT, TYPE_NICE_VS_OF_STAR: CACHE_NICE_VS_OF_STAR, TYPE_JLIB_PAGE_NICE_VS: CACHE_JLIB_PAGE_NICE_VS, TYPE_JLIB_PAGE_NEW_VS: CACHE_JLIB_PAGE_NEW_VS, TYPE_STAR_JA_NAME: CACHE_STAR_JA_NAME, TYPE_NEW_VS_OF_STAR: CACHE_NEW_VS_OF_STAR, TYPE_BT: CACHE_BT, } def __init__(self, path_cache_file: str, use_cache: str = "1"): self.use_cache = use_cache self.path_cache_file = path_cache_file self.cache = None self._lock = threading.Lock() if self.use_cache == "1": try: os.makedirs(os.path.dirname(self.path_cache_file), exist_ok=True) self.cache = sqlite3.connect( self.path_cache_file, check_same_thread=False ) self.cache.execute( "CREATE TABLE IF NOT EXISTS cache (" "key TEXT PRIMARY KEY, value TEXT NOT NULL, expire_at INTEGER DEFAULT 0)" ) self.cache.commit() LOG.info(f"Connecting to the SQLite cache: {self.path_cache_file}") except Exception as e: self.cache = None LOG.error(f"Unable to open SQLite cache: {self.path_cache_file}: {e}") def remove_cache(self, key: str, type: int): if self.use_cache == "0" or not self.cache: return key = str(key).lower() cache_key = f"{BotCacheDb.TYPE_MAP[type]['prefix']}{key}" try: with self._lock: self.cache.execute("DELETE FROM cache WHERE key=?", (cache_key,)) self.cache.commit() except Exception as e: LOG.error(f"Failed to delete cache: {cache_key}: {e}") def set_cache(self, key: str, value, type: int, expire=None): """ Set cache. :param str key: Key :param any value: Value :param int type: Cache type :param int expire: Cache expiration time (in seconds), defaults to using predefined time """ if self.use_cache == "0" or not self.cache: return key = str(key).lower() if not expire: expire = BotCacheDb.TYPE_MAP[type]["expire"] prefix = BotCacheDb.TYPE_MAP[type]["prefix"] cache_key = f"{prefix}{key}" expire_at = 0 if expire == 0 else int(time.time()) + expire try: with self._lock: self.cache.execute( "INSERT OR REPLACE INTO cache (key, value, expire_at) VALUES (?, ?, ?)", (cache_key, json.dumps(value), expire_at), ) self.cache.commit() except Exception as e: LOG.error(f"Failed to set cache: {cache_key}: {e}") def get_cache(self, key, type: int): if self.use_cache == "0" or not self.cache: return key = str(key).lower() cache_key = f"{BotCacheDb.TYPE_MAP[type]['prefix']}{key}" try: with self._lock: cur = self.cache.execute( "SELECT value, expire_at FROM cache WHERE key=?", (cache_key,) ) row = cur.fetchone() if not row: return value, expire_at = row if expire_at and expire_at < int(time.time()): with self._lock: self.cache.execute("DELETE FROM cache WHERE key=?", (cache_key,)) self.cache.commit() return return json.loads(value) except Exception as e: LOG.error(f"Failed to retrieve cache: {cache_key}: {e}")