更好的 StoreService

This commit is contained in:
qwerdvd 2023-09-29 22:14:04 +08:00
parent 6ce4d3e590
commit ab3caab461
4 changed files with 505 additions and 349 deletions

View File

@ -1,3 +1,4 @@
import asyncio
from pathlib import Path
from gsuid_core.bot import Bot
@ -7,7 +8,7 @@ from gsuid_core.models import Event
from gsuid_core.sv import SV
from ..utils.resource.download_all_resource import download_all_resource
from .cachedata import CacheData
from .memoryStore import store
from .constants import Excel
sv_download_config = SV('下载资源', pm=2)
@ -25,9 +26,11 @@ async def startup():
await download_all_resource()
logger.info('[资源文件下载] 检查完毕, 正在加载 gamedata')
tasks = []
for file_path in Path(
get_res_path(['ArknightsUID', 'resource', 'gamedata'])
).rglob('*.json'):
CacheData.readFile(file_path)
tasks.append(store.get_file(Path(file_path)))
await asyncio.gather(*tasks)
Excel.preload_table()
await Excel.preload_table()

View File

@ -1,67 +0,0 @@
import json
from functools import cache
from pathlib import Path
from time import time
from typing import Any, ClassVar, Dict, Union
from loguru import logger
from ..utils.file import read_json
class StoreData:
data: Dict[Any, Any]
modification_time: float
def __init__(self, data: Dict[Any, Any], modification_time: float) -> None:
self.data = data
self.modification_time = modification_time
class CacheData:
cached_data: ClassVar[Dict[str, StoreData]] = {}
@classmethod
@cache
def get_cache(cls, local_path: Path) -> Dict[Any, Any]:
data_name = local_path.stem
if data_name in cls.cached_data:
current_modification_time = local_path.stat().st_mtime
if current_modification_time == cls.cached_data[data_name].modification_time:
return cls.cached_data[data_name].data
return cls.set_cache(local_path, data_name)
@classmethod
def set_cache(
cls, local_path: Union[Path, None], data_name: str, memory_data: Union[Dict, None] = None
) -> Dict[Any, Any]:
data = read_json(local_path) if local_path else memory_data
if data is None:
raise FileNotFoundError
modification_time = local_path.stat().st_mtime if local_path else time()
cls.cached_data[data_name] = StoreData(data, modification_time)
return cls.cached_data[data_name].data
@classmethod
def readFile(cls, local_path: Path) -> Dict[Any, Any]:
try:
if isinstance(local_path, str):
local_path = Path(local_path)
return cls.get_cache(local_path)
except json.decoder.JSONDecodeError as e:
logger.error(f'Could not load file "{local_path}".')
raise FileNotFoundError from e
@classmethod
def readExcel(cls, table_name: str) -> Dict[Any, Any]:
logger.debug(f'loading: {table_name}.json')
if table_name not in cls.cached_data:
return {}
return cls.cached_data[table_name].data
@classmethod
def readBytesExcel(cls, table_name: str) -> bytes:
logger.debug(f'loading: {table_name}.json')
if table_name not in cls.cached_data:
return bytes({})
return json.dumps(cls.cached_data[table_name].data).encode('utf-8')

View File

@ -1,14 +1,13 @@
import asyncio
import inspect
from typing import Dict, Union
from msgspec import json as msgjson
from ..utils.models.gamedata.ActivityTable import ActivityTable
from ..utils.models.gamedata.AudioData import AudioData
from ..utils.models.gamedata.BattleEquipTable import BattleEquipData
from ..utils.models.gamedata.BattleEquipTable import BattleEquipTable
from ..utils.models.gamedata.BuildingData import BuildingData
from ..utils.models.gamedata.CampaignTable import CampaignTable
from ..utils.models.gamedata.ChapterTable import ChapterData
from ..utils.models.gamedata.CharacterTable import CharacterData
from ..utils.models.gamedata.ChapterTable import ChapterTable
from ..utils.models.gamedata.CharacterTable import CharacterTable
from ..utils.models.gamedata.CharMetaTable import CharMetaTable
from ..utils.models.gamedata.CharmTable import CharmTable
from ..utils.models.gamedata.CharPatchTable import CharPatchTable
@ -24,474 +23,516 @@ from ..utils.models.gamedata.GachaTable import GachaTable
from ..utils.models.gamedata.GamedataConst import GamedataConst
from ..utils.models.gamedata.HandbookInfoTable import HandbookInfoTable
from ..utils.models.gamedata.HandbookTable import HandbookTable
from ..utils.models.gamedata.HandbookTeamTable import HandbookTeam
from ..utils.models.gamedata.HandbookTeamTable import HandbookTeamTable
from ..utils.models.gamedata.ItemTable import ItemTable
from ..utils.models.gamedata.MedalTable import MedalTable
from ..utils.models.gamedata.MissionTable import MissionTable
from ..utils.models.gamedata.OpenServerTable import OpenServerTable
from ..utils.models.gamedata.PlayerAvatarTable import PlayerAvatarTable
from ..utils.models.gamedata.RangeTable import Stage
from ..utils.models.gamedata.ReplicateTable import ReplicateList
from ..utils.models.gamedata.RangeTable import RangeTable
from ..utils.models.gamedata.ReplicateTable import ReplicateTable
from ..utils.models.gamedata.RetroTable import RetroTable
from ..utils.models.gamedata.RoguelikeTable import RoguelikeTable
from ..utils.models.gamedata.RoguelikeTopicTable import RoguelikeTopicTable
from ..utils.models.gamedata.SandboxTable import SandboxTable
from ..utils.models.gamedata.ShopClientTable import ShopClientTable
from ..utils.models.gamedata.SkillTable import SkillDataBundle
from ..utils.models.gamedata.SkillTable import SkillTable
from ..utils.models.gamedata.SkinTable import SkinTable
from ..utils.models.gamedata.StageTable import StageTable
from ..utils.models.gamedata.StoryReviewMetaTable import StoryReviewMetaTable
from ..utils.models.gamedata.StoryReviewTable import StoryReviewGroupClientData
from ..utils.models.gamedata.StoryTable import StoryData
from ..utils.models.gamedata.StoryReviewTable import StoryReviewTable
from ..utils.models.gamedata.StoryTable import StoryTable
from ..utils.models.gamedata.TechBuffTable import TechBuffTable
from ..utils.models.gamedata.TipTable import TipTable
from ..utils.models.gamedata.TokenTable import TokenCharacterData
from ..utils.models.gamedata.TokenTable import TokenTable
from ..utils.models.gamedata.UniequipData import UniequipData
from ..utils.models.gamedata.UniequipTable import UniEquipTable
from ..utils.models.gamedata.ZoneTable import ZoneTable
from .cachedata import CacheData
from .memoryStore import store
class ExcelTableManager:
activity_table_: Union[ActivityTable, None] = None
audio_data_: Union[AudioData, None] = None
battle_equip_table_: Union[Dict[str, BattleEquipData], None] = None
building_data_: Union[BuildingData, None] = None
campaign_table_: Union[CampaignTable, None] = None
chapter_table_: Union[Dict[str, ChapterData], None] = None
character_table_: Union[Dict[str, CharacterData], None] = None
char_meta_table_: Union[CharMetaTable, None] = None
charm_table_: Union[CharmTable, None] = None
char_patch_table_: Union[CharPatchTable, None] = None
charword_table_: Union[CharwordTable, None] = None
checkin_table_: Union[CheckinTable, None] = None
climb_tower_table_: Union[ClimbTowerTable, None] = None
clue_data_: Union[ClueData, None] = None
crisis_table_: Union[CrisisTable, None] = None
display_meta_table_: Union[DisplayMetaTable, None] = None
enemy_handbook_table_: Union[EnemyHandbookTable, None] = None
favor_table_: Union[FavorTable, None] = None
gacha_table_: Union[GachaTable, None] = None
gamedata_const_: Union[GamedataConst, None] = None
handbook_info_table_: Union[HandbookInfoTable, None] = None
handbook_table_: Union[HandbookTable, None] = None
handbook_team_table_: Union[Dict[str, HandbookTeam], None] = None
item_table_: Union[ItemTable, None] = None
medal_table_: Union[MedalTable, None] = None
mission_table_: Union[MissionTable, None] = None
open_server_table_: Union[OpenServerTable, None] = None
player_avatar_table_: Union[PlayerAvatarTable, None] = None
range_table_: Union[Dict[str, Stage], None] = None
replicate_table_: Union[Dict[str, ReplicateList], None] = None
retro_table_: Union[RetroTable, None] = None
roguelike_table_: Union[RoguelikeTable, None] = None
roguelike_topic_table_: Union[RoguelikeTopicTable, None] = None
sandbox_table_: Union[SandboxTable, None] = None
shop_client_table_: Union[ShopClientTable, None] = None
skill_table_: Union[Dict[str, SkillDataBundle], None] = None
skin_table_: Union[SkinTable, None] = None
stage_table_: Union[StageTable, None] = None
story_review_meta_table_: Union[StoryReviewMetaTable, None] = None
story_review_table_: Union[Dict[str, StoryReviewGroupClientData], None] = None
story_table_: Union[Dict[str, StoryData], None] = None
tech_buff_table_: Union[TechBuffTable, None] = None
tip_table_: Union[TipTable, None] = None
token_table_: Union[Dict[str, TokenCharacterData], None] = None
uniequip_data_: Union[UniequipData, None] = None
uniequip_table_: Union[UniEquipTable, None] = None
zone_table_: Union[ZoneTable, None] = None
activity_table_: ActivityTable
audio_data_: AudioData
battle_equip_table_: BattleEquipTable
building_data_: BuildingData
campaign_table_: CampaignTable
chapter_table_: ChapterTable
character_table_: CharacterTable
char_meta_table_: CharMetaTable
charm_table_: CharmTable
char_patch_table_: CharPatchTable
charword_table_: CharwordTable
checkin_table_: CheckinTable
climb_tower_table_: ClimbTowerTable
clue_data_: ClueData
crisis_table_: CrisisTable
display_meta_table_: DisplayMetaTable
enemy_handbook_table_: EnemyHandbookTable
favor_table_: FavorTable
gacha_table_: GachaTable
gamedata_const_: GamedataConst
handbook_info_table_: HandbookInfoTable
handbook_table_: HandbookTable
handbook_team_table_: HandbookTeamTable
item_table_: ItemTable
medal_table_: MedalTable
mission_table_: MissionTable
open_server_table_: OpenServerTable
player_avatar_table_: PlayerAvatarTable
range_table_: RangeTable
replicate_table_: ReplicateTable
retro_table_: RetroTable
roguelike_table_: RoguelikeTable
roguelike_topic_table_: RoguelikeTopicTable
sandbox_table_: SandboxTable
shop_client_table_: ShopClientTable
skill_table_: SkillTable
skin_table_: SkinTable
stage_table_: StageTable
story_review_meta_table_: StoryReviewMetaTable
story_review_table_: StoryReviewTable
story_table_: StoryTable
tech_buff_table_: TechBuffTable
tip_table_: TipTable
token_table_: TokenTable
uniequip_data_: UniequipData
uniequip_table_: UniEquipTable
zone_table_: ZoneTable
async def activity_table(self) -> None:
self.activity_table_ = ActivityTable.convert(
await store.get_excel("activity_table")
)
@property
def ACTIVITY_TABLE(self) -> ActivityTable:
if not self.activity_table_:
self.activity_table_ = ActivityTable.convert(
CacheData.readExcel('activity_table')
)
return self.activity_table_
async def audio_data(self) -> None:
self.audio_data_ = AudioData.convert(
await store.get_excel("audio_data")
)
@property
def AUDIO_DATA(self) -> AudioData:
if not self.audio_data_:
self.audio_data_ = AudioData.convert(
CacheData.readExcel('audio_data')
)
return self.audio_data_
async def battle_equip_table(self) -> None:
self.battle_equip_table_ = BattleEquipTable.convert(
{"equips": await store.get_excel("battle_equip_table")}
)
@property
def BATTLE_EQUIP_TABLE(self) -> Dict[str, BattleEquipData]:
if not self.battle_equip_table_:
self.battle_equip_table_ = msgjson.decode(
CacheData.readBytesExcel('battle_equip_table'),
type=Dict[str, BattleEquipData]
)
def BATTLE_EQUIP_TABLE(self) -> BattleEquipTable:
return self.battle_equip_table_
async def building_data(self) -> None:
self.building_data_ = BuildingData.convert(
await store.get_excel("building_data")
)
@property
def BUILDING_DATA(self) -> BuildingData:
if not self.building_data_:
self.building_data_ = BuildingData.convert(
CacheData.readExcel('building_data')
)
return self.building_data_
async def campaign_table(self) -> None:
self.campaign_table_ = CampaignTable.convert(
await store.get_excel("campaign_table")
)
@property
def CAMPAIGN_TABLE(self) -> CampaignTable:
if not self.campaign_table_:
self.campaign_table_ = CampaignTable.convert(
CacheData.readExcel('campaign_table')
)
return self.campaign_table_
@property
def CHAPTER_TABLE(self) -> Dict[str, ChapterData]:
if not self.chapter_table_:
self.chapter_table_ = msgjson.decode(
CacheData.readBytesExcel('chapter_table'),
type=Dict[str, ChapterData]
)
return self.chapter_table_
async def chapter_table(self) -> None:
self.chapter_table_ = ChapterTable.convert(
{"chapters": await store.get_excel("chapter_table")}
)
@property
def CHARATER_TABLE(self) -> Dict[str, CharacterData]:
if not self.character_table_:
self.character_table_ = msgjson.decode(
CacheData.readBytesExcel('character_table'),
type=Dict[str, CharacterData]
)
def CHAPTER_TABLE(self) -> ChapterTable:
return self.chapter_table_
async def character_table(self) -> None:
self.character_table_ = CharacterTable.convert(
{"chars": await store.get_excel("character_table")}
)
@property
def CHARATER_TABLE(self) -> CharacterTable:
return self.character_table_
async def char_meta_table(self) -> None:
self.char_meta_table_ = CharMetaTable.convert(
await store.get_excel("char_meta_table")
)
@property
def CHAR_META_TABLE(self) -> CharMetaTable:
if not self.char_meta_table_:
self.char_meta_table_ = CharMetaTable.convert(
CacheData.readExcel('char_meta_table')
)
return self.char_meta_table_
async def charm_table(self) -> None:
self.charm_table_ = CharmTable.convert(
await store.get_excel("charm_table")
)
@property
def CHARM_TABLE(self) -> CharmTable:
if not self.charm_table_:
self.charm_table_ = CharmTable.convert(
CacheData.readExcel('charm_table')
)
return self.charm_table_
async def char_patch_table(self) -> None:
self.char_patch_table_ = CharPatchTable.convert(
await store.get_excel("char_patch_table")
)
@property
def CHAR_PATH_TABLE(self) -> CharPatchTable:
if not self.char_patch_table_:
self.char_patch_table_ = CharPatchTable.convert(
CacheData.readExcel('char_patch_table')
)
return self.char_patch_table_
async def charword_table(self) -> None:
self.charword_table_ = CharwordTable.convert(
await store.get_excel("charword_table")
)
@property
def CHARWORD_TABLE(self) -> CharwordTable:
if not self.charword_table_:
self.charword_table_ = CharwordTable.convert(
CacheData.readExcel('charword_table')
)
return self.charword_table_
async def checkin_table(self) -> None:
self.checkin_table_ = CheckinTable.convert(
await store.get_excel("checkin_table")
)
@property
def CHECKIN_TABLE(self) -> CheckinTable:
if not self.checkin_table_:
self.checkin_table_ = CheckinTable.convert(
CacheData.readExcel('checkin_table')
)
return self.checkin_table_
async def climb_tower_table(self) -> None:
self.climb_tower_table_ = ClimbTowerTable.convert(
await store.get_excel("climb_tower_table")
)
@property
def CLIMB_TOWER_TABLE(self) -> ClimbTowerTable:
if not self.climb_tower_table_:
self.climb_tower_table_ = ClimbTowerTable.convert(
CacheData.readExcel('climb_tower_table')
)
return self.climb_tower_table_
async def clue_data(self) -> None:
self.clue_data_ = ClueData.convert(
await store.get_excel("clue_data")
)
@property
def CLUE_DATA(self) -> ClueData:
if not self.clue_data_:
self.clue_data_ = ClueData.convert(
CacheData.readExcel('clue_data')
)
return self.clue_data_
async def crisis_table(self) -> None:
self.crisis_table_ = CrisisTable.convert(
await store.get_excel("crisis_table")
)
@property
def CRISIS_TABLE(self) -> CrisisTable:
if not self.crisis_table_:
self.crisis_table_ = CrisisTable.convert(
CacheData.readExcel('crisis_table')
)
return self.crisis_table_
async def display_meta_table(self) -> None:
self.display_meta_table_ = DisplayMetaTable.convert(
await store.get_excel("display_meta_table")
)
@property
def DISPLAY_META_TABLE(self) -> DisplayMetaTable:
if not self.display_meta_table_:
self.display_meta_table_ = DisplayMetaTable.convert(
CacheData.readExcel('display_meta_table')
)
return self.display_meta_table_
async def enemy_handbook_table(self) -> None:
self.enemy_handbook_table_ = EnemyHandbookTable.convert(
await store.get_excel("enemy_handbook_table")
)
@property
def ENEMY_HANDBOOK_TABLE(self) -> EnemyHandbookTable:
if not self.enemy_handbook_table_:
self.enemy_handbook_table_ = EnemyHandbookTable.convert(
CacheData.readExcel('enemy_handbook_table')
)
return self.enemy_handbook_table_
async def favor_table(self) -> None:
self.favor_table_ = FavorTable.convert(
await store.get_excel("favor_table")
)
@property
def FAVOR_TABLE(self) -> FavorTable:
if not self.favor_table_:
self.favor_table_ = FavorTable.convert(
CacheData.readExcel('favor_table')
)
return self.favor_table_
async def gacha_table(self) -> None:
self.gacha_table_ = GachaTable.convert(
await store.get_excel("gacha_table")
)
@property
def GACHA_TABLE(self) -> GachaTable:
if not self.gacha_table_:
self.gacha_table_ = GachaTable.convert(
CacheData.readExcel('gacha_table')
)
return self.gacha_table_
async def gamedata_const(self) -> None:
self.gamedata_const_ = GamedataConst.convert(
await store.get_excel("gamedata_const")
)
@property
def GAMEDATA_CONST(self) -> GamedataConst:
if not self.gamedata_const_:
self.gamedata_const_ = GamedataConst.convert(
CacheData.readExcel('gamedata_const')
)
return self.gamedata_const_
async def handbook_info_table(self) -> None:
self.handbook_info_table_ = HandbookInfoTable.convert(
await store.get_excel("handbook_info_table")
)
@property
def HANDBOOK_INFO_TABLE(self) -> HandbookInfoTable:
if not self.handbook_info_table_:
self.handbook_info_table_ = HandbookInfoTable.convert(
CacheData.readExcel('handbook_info_table')
)
return self.handbook_info_table_
async def handbook_table(self) -> None:
self.handbook_table_ = HandbookTable.convert(
await store.get_excel("handbook_table")
)
@property
def HANDBOOK_TABLE(self) -> HandbookTable:
if not self.handbook_table_:
self.handbook_table_ = HandbookTable.convert(
CacheData.readExcel('handbook_table')
)
return self.handbook_table_
async def handbook_team_table(self) -> None:
self.handbook_team_table_ = HandbookTeamTable.convert(
{"team": await store.get_excel("handbook_team_table")}
)
@property
def HANDBOOK_TEAM_TABLE(self) -> Dict[str, HandbookTeam]:
if not self.handbook_team_table_:
self.handbook_team_table_ = msgjson.decode(
CacheData.readBytesExcel('handbook_team_table'),
type=Dict[str, HandbookTeam]
)
def HANDBOOK_TEAM_TABLE(self) -> HandbookTeamTable:
return self.handbook_team_table_
async def item_table(self) -> None:
self.item_table_ = ItemTable.convert(
await store.get_excel("item_table")
)
@property
def ITEM_TABLE(self) -> ItemTable:
if not self.item_table_:
self.item_table_ = ItemTable.convert(
CacheData.readExcel('item_table')
)
return self.item_table_
async def medal_table(self) -> None:
self.medal_table_ = MedalTable.convert(
await store.get_excel("medal_table")
)
@property
def MEDAL_TABLE(self) -> MedalTable:
if not self.medal_table_:
self.medal_table_ = MedalTable.convert(
CacheData.readExcel('medal_table')
)
return self.medal_table_
async def mission_table(self) -> None:
self.mission_table_ = MissionTable.convert(
await store.get_excel("mission_table")
)
@property
def MISSION_TABLE(self) -> MissionTable:
if not self.mission_table_:
self.mission_table_ = MissionTable.convert(
CacheData.readExcel('mission_table')
)
return self.mission_table_
async def open_server_table(self) -> None:
self.open_server_table_ = OpenServerTable.convert(
await store.get_excel("open_server_table")
)
@property
def OPEN_SERVER_TABLE(self) -> OpenServerTable:
if not self.open_server_table_:
self.open_server_table_ = OpenServerTable.convert(
CacheData.readExcel('open_server_table')
)
return self.open_server_table_
async def player_avatar_table(self) -> None:
self.player_avatar_table_ = PlayerAvatarTable.convert(
await store.get_excel("player_avatar_table")
)
@property
def PLAYER_AVATAR_TABLE(self) -> PlayerAvatarTable:
if not self.player_avatar_table_:
self.player_avatar_table_ = PlayerAvatarTable.convert(
CacheData.readExcel('player_avatar_table')
)
return self.player_avatar_table_
@property
def RANGE_TABLE(self) -> Dict[str, Stage]:
if not self.range_table_:
self.range_table_ = msgjson.decode(
CacheData.readBytesExcel('range_table'),
type=Dict[str, Stage]
)
return self.range_table_
async def range_table(self) -> None:
self.range_table_ = RangeTable.convert(
{"range": await store.get_excel("range_table")}
)
@property
def REPLICATE_TABLE(self) -> Dict[str, ReplicateList]:
if not self.replicate_table_:
self.replicate_table_ = msgjson.decode(
CacheData.readBytesExcel('replicate_table'),
type=Dict[str, ReplicateList]
)
def RANGE_TABLE(self) -> RangeTable:
return self.range_table_
async def replicate_table(self) -> None:
self.replicate_table_ = ReplicateTable.convert(
{"replicate": await store.get_excel("replicate_table")}
)
@property
def REPLICATE_TABLE(self) -> ReplicateTable:
return self.replicate_table_
async def retro_table(self) -> None:
self.retro_table_ = RetroTable.convert(
await store.get_excel("retro_table")
)
@property
def RETRO_TABLE(self) -> RetroTable:
if not self.retro_table_:
self.retro_table_ = RetroTable.convert(
CacheData.readExcel('retro_table')
)
return self.retro_table_
async def roguelike_table(self) -> None:
self.roguelike_table_ = RoguelikeTable.convert(
await store.get_excel("roguelike_table")
)
@property
def ROGUELIKE_TABLE(self) -> RoguelikeTable:
if not self.roguelike_table_:
self.roguelike_table_ = RoguelikeTable.convert(
CacheData.readExcel('roguelike_table')
)
return self.roguelike_table_
async def roguelike_topic_table(self) -> None:
self.roguelike_topic_table_ = RoguelikeTopicTable.convert(
await store.get_excel("roguelike_topic_table")
)
@property
def ROGUELIKE_TOPIC_TABLE(self) -> RoguelikeTopicTable:
if not self.roguelike_topic_table_:
self.roguelike_topic_table_ = RoguelikeTopicTable.convert(
CacheData.readExcel('roguelike_topic_table')
)
return self.roguelike_topic_table_
async def sandbox_table(self) -> None:
self.sandbox_table_ = SandboxTable.convert(
await store.get_excel("sandbox_table")
)
@property
def SANDBOX_TABLE(self) -> SandboxTable:
if not self.sandbox_table_:
self.sandbox_table_ = SandboxTable.convert(
CacheData.readExcel('sandbox_table')
)
return self.sandbox_table_
async def shop_client_table(self) -> None:
self.shop_client_table_ = ShopClientTable.convert(
await store.get_excel("shop_client_table")
)
@property
def SHOP_CLIENT_TABLE(self) -> ShopClientTable:
if not self.shop_client_table_:
self.shop_client_table_ = ShopClientTable.convert(
CacheData.readExcel('shop_client_table')
)
return self.shop_client_table_
async def skill_table(self) -> None:
self.skill_table_ = SkillTable.convert(
{"skills": await store.get_excel("skill_table")}
)
@property
def SKILL_TABLE(self) -> Dict[str, SkillDataBundle]:
if not self.skill_table_:
self.skill_table_ = msgjson.decode(
CacheData.readBytesExcel('skill_table'),
type=Dict[str, SkillDataBundle]
)
def SKILL_TABLE(self) -> SkillTable:
return self.skill_table_
async def skin_table(self) -> None:
self.skin_table_ = SkinTable.convert(
await store.get_excel("skin_table")
)
@property
def SKIN_TABLE(self) -> SkinTable:
if not self.skin_table_:
self.skin_table_ = SkinTable.convert(
CacheData.readExcel('skin_table')
)
return self.skin_table_
async def stage_table(self) -> None:
self.stage_table_ = StageTable.convert(
await store.get_excel("stage_table")
)
@property
def STAGE_TABLE(self) -> StageTable:
if not self.stage_table_:
self.stage_table_ = StageTable.convert(
CacheData.readExcel('stage_table')
)
return self.stage_table_
async def story_review_meta_table(self) -> None:
self.story_review_meta_table_ = StoryReviewMetaTable.convert(
await store.get_excel("story_review_meta_table")
)
@property
def STORY_REVIEW_META_TABLE(self) -> StoryReviewMetaTable:
if not self.story_review_meta_table_:
self.story_review_meta_table_ = StoryReviewMetaTable.convert(
CacheData.readExcel('story_review_meta_table')
)
return self.story_review_meta_table_
@property
def STORY_REVIEW_TABLE(self) -> Dict[str, StoryReviewGroupClientData]:
if not self.story_review_table_:
self.story_review_table_ = msgjson.decode(
CacheData.readBytesExcel('story_review_table'),
type=Dict[str, StoryReviewGroupClientData]
)
return self.story_review_table_
async def story_review_table(self) -> None:
self.story_review_table_ = StoryReviewTable.convert(
{"storyreviewtable": await store.get_excel("story_review_table")}
)
@property
def STORY_TABLE(self) -> Dict[str, StoryData]:
if not self.story_table_:
self.story_table_ = msgjson.decode(
CacheData.readBytesExcel('story_table'),
type=Dict[str, StoryData]
)
def STORY_REVIEW_TABLE(self) -> StoryReviewTable:
return self.story_review_table_
async def story_table(self) -> None:
self.story_table_ = StoryTable.convert(
{"stories": await store.get_excel("story_table")}
)
@property
def STORY_TABLE(self) -> StoryTable:
return self.story_table_
async def tech_buff_table(self) -> None:
self.tech_buff_table_ = TechBuffTable.convert(
await store.get_excel("tech_buff_table")
)
@property
def TECH_BUFF_TABLE(self) -> TechBuffTable:
if not self.tech_buff_table_:
self.tech_buff_table_ = TechBuffTable.convert(
CacheData.readExcel('tech_buff_table')
)
return self.tech_buff_table_
async def tip_table(self) -> None:
self.tip_table_ = TipTable.convert(
await store.get_excel("tip_table")
)
@property
def TIP_TABLE(self) -> TipTable:
if not self.tip_table_:
self.tip_table_ = TipTable.convert(
CacheData.readExcel('tip_table')
)
return self.tip_table_
async def token_table(self) -> None:
self.token_table_ = TokenTable.convert(
{"tokens": await store.get_excel("token_table")}
)
@property
def TOKEN_TABLE(self) -> Dict[str, TokenCharacterData]:
if not self.token_table_:
self.token_table_ = msgjson.decode(
CacheData.readBytesExcel('token_table'),
type=Dict[str, TokenCharacterData]
)
def TOKEN_TABLE(self) -> TokenTable:
return self.token_table_
async def uniequip_data(self) -> None:
self.uniequip_data_ = UniequipData.convert(
await store.get_excel("uniequip_data")
)
@property
def UNIEQUIP_DATA(self) -> UniequipData:
if not self.uniequip_data_:
self.uniequip_data_ = UniequipData.convert(
CacheData.readExcel('uniequip_data')
)
return self.uniequip_data_
async def uniequip_table(self) -> None:
self.uniequip_table_ = UniEquipTable.convert(
await store.get_excel("uniequip_table")
)
@property
def UNIEQUIP_TABLE(self) -> UniEquipTable:
if not self.uniequip_table_:
self.uniequip_table_ = UniEquipTable.convert(
CacheData.readExcel('uniequip_table')
)
return self.uniequip_table_
async def zone_table(self) -> None:
self.zone_table_ = ZoneTable.convert(
await store.get_excel("zone_table")
)
@property
def ZONE_TABLE(self) -> ZoneTable:
if not self.zone_table_:
self.zone_table_ = ZoneTable.convert(
CacheData.readExcel('zone_table')
)
return self.zone_table_
def preload_table(self) -> None:
for name, method in inspect.getmembers(self, predicate=inspect.ismethod):
if callable(method) and not name.startswith('__') \
and name != 'preload_table':
method()
async def preload_table(self) -> None:
tasks = []
for name, method in inspect.getmembers(self):
if (
inspect.iscoroutinefunction(method)
and not name.startswith("__")
and name != "preload_table"
):
await method()
await asyncio.gather(*tasks)
Excel = ExcelTableManager()

View File

@ -0,0 +1,179 @@
from __future__ import annotations
from abc import ABC, abstractmethod
import json
import os
import pickle
import shutil
from datetime import UTC, datetime, timedelta
from pathlib import Path
from tempfile import mkstemp
from typing import Any
import anyio
from anyio import Path as anyioPath
from anyio.to_thread import run_sync
from msgspec import Struct
from gsuid_core.logger import logger
def read_json(file_path: Path, **kwargs) -> dict:
"""
Read a JSON file and return its contents as a dictionary.
"""
try:
with Path.open(file_path, encoding="UTF-8", **kwargs) as file:
return json.load(file)
except (FileNotFoundError, json.JSONDecodeError) as e:
logger.error(f"Error reading JSON file: {e}")
return {}
class Store(ABC):
"""Thread and process safe asynchronous key/value store."""
@abstractmethod
async def set(self, key: str, value: str | bytes, expires_in: int | timedelta | None = None) -> None:
raise NotImplementedError
@abstractmethod
async def get(self, key: str, renew_for: int | timedelta | None = None) -> bytes | None:
raise NotImplementedError
@abstractmethod
async def delete(self, key: str) -> None:
raise NotImplementedError
@abstractmethod
async def delete_all(self) -> None:
raise NotImplementedError
@abstractmethod
async def exists(self, key: str) -> bool:
raise NotImplementedError
@abstractmethod
async def expires_in(self, key: str) -> int | None:
raise NotImplementedError
class StorageObject(Struct):
expires_at: datetime | None
data: Any
@classmethod
def new(cls, data: Any, expires_in: int | timedelta | None) -> StorageObject:
if expires_in is not None and not isinstance(expires_in, timedelta):
expires_in = timedelta(seconds=expires_in)
return cls(
data=data,
expires_at=(datetime.now(tz=UTC) + expires_in) if expires_in else None,
)
@property
def expired(self) -> bool:
return self.expires_at is not None and datetime.now(tz=UTC) >= self.expires_at
@property
def expires_in(self) -> int:
if self.expires_at:
return int(self.expires_at.timestamp() - datetime.now(tz=UTC).timestamp())
return -1
class StoreService(Store):
__slots__ = ("store", "lock", "path")
def __init__(self) -> None:
self.path: anyioPath = anyioPath('data')
self.store_: dict[str, StorageObject] = {}
self.lock: anyio.Lock = anyio.Lock()
self.last_cleared: datetime = datetime.utcnow()
@staticmethod
async def load_from_path(path: anyioPath) -> dict[str, StorageObject]:
try:
data = await path.read_bytes()
return pickle.loads(data)
except FileNotFoundError:
return {}
def _write_sync(self, target_file: anyioPath) -> None:
try:
tmp_file_fd, tmp_file_name = mkstemp(dir=self.path, prefix=f"{target_file.name}.cache")
renamed = False
try:
try:
os.write(tmp_file_fd, pickle.dumps(self.store_))
finally:
os.close(tmp_file_fd)
shutil.move(tmp_file_name, target_file)
renamed = True
finally:
if not renamed:
Path.unlink(Path(tmp_file_name))
except OSError:
pass
async def write(self, target_file: anyioPath) -> None:
await run_sync(self._write_sync, target_file)
async def set(self, key: str, value: Any, expires_in: int | timedelta | None = None) -> None:
if isinstance(value, str):
value = value.encode("UTF-8")
async with self.lock:
self.store_[key] = StorageObject.new(data=value, expires_in=expires_in)
async def get(self, key: str, renew_for: int | timedelta | None = None, default: Any = None) -> Any:
async with self.lock:
storage_obj = self.store_.get(key)
if not storage_obj:
return default
if storage_obj.expired:
self.store_.pop(key)
return None
if renew_for and storage_obj.expires_at:
storage_obj = StorageObject.new(data=storage_obj.data, expires_in=renew_for)
self.store_[key] = storage_obj
return storage_obj.data
async def get_excel(self, table_name: str) -> Any:
return await self.get(table_name, default=None)
async def get_file(self, local_path: Path, expires_in: int | timedelta | None = None) -> Any:
if not await self.exists(local_path.stem):
await self.set(local_path.stem, read_json(local_path), expires_in)
return await self.get(local_path.stem)
async def delete(self, key: str) -> None:
async with self.lock:
self.store_.pop(key, None)
async def delete_all(self) -> None:
async with self.lock:
self.store_.clear()
async def delete_expired(self) -> None:
async with self.lock:
new_store = {}
for i, (key, storage_obj) in enumerate(self.store_.items()):
if not storage_obj.expired:
new_store[key] = storage_obj
if i % 1000 == 0:
await anyio.sleep(0)
self.store_ = new_store
async def exists(self, key: str) -> bool:
return key in self.store_
async def keys(self) -> list[str]:
return list(self.store_.keys())
async def expires_in(self, key: str) -> int | None:
if storage_obj := self.store_.get(key):
return storage_obj.expires_in
return None
store = StoreService()