更好的 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 pathlib import Path
from gsuid_core.bot import Bot from gsuid_core.bot import Bot
@ -7,7 +8,7 @@ from gsuid_core.models import Event
from gsuid_core.sv import SV from gsuid_core.sv import SV
from ..utils.resource.download_all_resource import download_all_resource from ..utils.resource.download_all_resource import download_all_resource
from .cachedata import CacheData from .memoryStore import store
from .constants import Excel from .constants import Excel
sv_download_config = SV('下载资源', pm=2) sv_download_config = SV('下载资源', pm=2)
@ -25,9 +26,11 @@ async def startup():
await download_all_resource() await download_all_resource()
logger.info('[资源文件下载] 检查完毕, 正在加载 gamedata') logger.info('[资源文件下载] 检查完毕, 正在加载 gamedata')
tasks = []
for file_path in Path( for file_path in Path(
get_res_path(['ArknightsUID', 'resource', 'gamedata']) get_res_path(['ArknightsUID', 'resource', 'gamedata'])
).rglob('*.json'): ).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 import inspect
from typing import Dict, Union
from msgspec import json as msgjson
from ..utils.models.gamedata.ActivityTable import ActivityTable from ..utils.models.gamedata.ActivityTable import ActivityTable
from ..utils.models.gamedata.AudioData import AudioData 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.BuildingData import BuildingData
from ..utils.models.gamedata.CampaignTable import CampaignTable from ..utils.models.gamedata.CampaignTable import CampaignTable
from ..utils.models.gamedata.ChapterTable import ChapterData from ..utils.models.gamedata.ChapterTable import ChapterTable
from ..utils.models.gamedata.CharacterTable import CharacterData from ..utils.models.gamedata.CharacterTable import CharacterTable
from ..utils.models.gamedata.CharMetaTable import CharMetaTable from ..utils.models.gamedata.CharMetaTable import CharMetaTable
from ..utils.models.gamedata.CharmTable import CharmTable from ..utils.models.gamedata.CharmTable import CharmTable
from ..utils.models.gamedata.CharPatchTable import CharPatchTable 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.GamedataConst import GamedataConst
from ..utils.models.gamedata.HandbookInfoTable import HandbookInfoTable from ..utils.models.gamedata.HandbookInfoTable import HandbookInfoTable
from ..utils.models.gamedata.HandbookTable import HandbookTable 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.ItemTable import ItemTable
from ..utils.models.gamedata.MedalTable import MedalTable from ..utils.models.gamedata.MedalTable import MedalTable
from ..utils.models.gamedata.MissionTable import MissionTable from ..utils.models.gamedata.MissionTable import MissionTable
from ..utils.models.gamedata.OpenServerTable import OpenServerTable from ..utils.models.gamedata.OpenServerTable import OpenServerTable
from ..utils.models.gamedata.PlayerAvatarTable import PlayerAvatarTable from ..utils.models.gamedata.PlayerAvatarTable import PlayerAvatarTable
from ..utils.models.gamedata.RangeTable import Stage from ..utils.models.gamedata.RangeTable import RangeTable
from ..utils.models.gamedata.ReplicateTable import ReplicateList from ..utils.models.gamedata.ReplicateTable import ReplicateTable
from ..utils.models.gamedata.RetroTable import RetroTable from ..utils.models.gamedata.RetroTable import RetroTable
from ..utils.models.gamedata.RoguelikeTable import RoguelikeTable from ..utils.models.gamedata.RoguelikeTable import RoguelikeTable
from ..utils.models.gamedata.RoguelikeTopicTable import RoguelikeTopicTable from ..utils.models.gamedata.RoguelikeTopicTable import RoguelikeTopicTable
from ..utils.models.gamedata.SandboxTable import SandboxTable from ..utils.models.gamedata.SandboxTable import SandboxTable
from ..utils.models.gamedata.ShopClientTable import ShopClientTable 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.SkinTable import SkinTable
from ..utils.models.gamedata.StageTable import StageTable from ..utils.models.gamedata.StageTable import StageTable
from ..utils.models.gamedata.StoryReviewMetaTable import StoryReviewMetaTable from ..utils.models.gamedata.StoryReviewMetaTable import StoryReviewMetaTable
from ..utils.models.gamedata.StoryReviewTable import StoryReviewGroupClientData from ..utils.models.gamedata.StoryReviewTable import StoryReviewTable
from ..utils.models.gamedata.StoryTable import StoryData from ..utils.models.gamedata.StoryTable import StoryTable
from ..utils.models.gamedata.TechBuffTable import TechBuffTable from ..utils.models.gamedata.TechBuffTable import TechBuffTable
from ..utils.models.gamedata.TipTable import TipTable 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.UniequipData import UniequipData
from ..utils.models.gamedata.UniequipTable import UniEquipTable from ..utils.models.gamedata.UniequipTable import UniEquipTable
from ..utils.models.gamedata.ZoneTable import ZoneTable from ..utils.models.gamedata.ZoneTable import ZoneTable
from .cachedata import CacheData from .memoryStore import store
class ExcelTableManager: class ExcelTableManager:
activity_table_: Union[ActivityTable, None] = None activity_table_: ActivityTable
audio_data_: Union[AudioData, None] = None audio_data_: AudioData
battle_equip_table_: Union[Dict[str, BattleEquipData], None] = None battle_equip_table_: BattleEquipTable
building_data_: Union[BuildingData, None] = None building_data_: BuildingData
campaign_table_: Union[CampaignTable, None] = None campaign_table_: CampaignTable
chapter_table_: Union[Dict[str, ChapterData], None] = None chapter_table_: ChapterTable
character_table_: Union[Dict[str, CharacterData], None] = None character_table_: CharacterTable
char_meta_table_: Union[CharMetaTable, None] = None char_meta_table_: CharMetaTable
charm_table_: Union[CharmTable, None] = None charm_table_: CharmTable
char_patch_table_: Union[CharPatchTable, None] = None char_patch_table_: CharPatchTable
charword_table_: Union[CharwordTable, None] = None charword_table_: CharwordTable
checkin_table_: Union[CheckinTable, None] = None checkin_table_: CheckinTable
climb_tower_table_: Union[ClimbTowerTable, None] = None climb_tower_table_: ClimbTowerTable
clue_data_: Union[ClueData, None] = None clue_data_: ClueData
crisis_table_: Union[CrisisTable, None] = None crisis_table_: CrisisTable
display_meta_table_: Union[DisplayMetaTable, None] = None display_meta_table_: DisplayMetaTable
enemy_handbook_table_: Union[EnemyHandbookTable, None] = None enemy_handbook_table_: EnemyHandbookTable
favor_table_: Union[FavorTable, None] = None favor_table_: FavorTable
gacha_table_: Union[GachaTable, None] = None gacha_table_: GachaTable
gamedata_const_: Union[GamedataConst, None] = None gamedata_const_: GamedataConst
handbook_info_table_: Union[HandbookInfoTable, None] = None handbook_info_table_: HandbookInfoTable
handbook_table_: Union[HandbookTable, None] = None handbook_table_: HandbookTable
handbook_team_table_: Union[Dict[str, HandbookTeam], None] = None handbook_team_table_: HandbookTeamTable
item_table_: Union[ItemTable, None] = None item_table_: ItemTable
medal_table_: Union[MedalTable, None] = None medal_table_: MedalTable
mission_table_: Union[MissionTable, None] = None mission_table_: MissionTable
open_server_table_: Union[OpenServerTable, None] = None open_server_table_: OpenServerTable
player_avatar_table_: Union[PlayerAvatarTable, None] = None player_avatar_table_: PlayerAvatarTable
range_table_: Union[Dict[str, Stage], None] = None range_table_: RangeTable
replicate_table_: Union[Dict[str, ReplicateList], None] = None replicate_table_: ReplicateTable
retro_table_: Union[RetroTable, None] = None retro_table_: RetroTable
roguelike_table_: Union[RoguelikeTable, None] = None roguelike_table_: RoguelikeTable
roguelike_topic_table_: Union[RoguelikeTopicTable, None] = None roguelike_topic_table_: RoguelikeTopicTable
sandbox_table_: Union[SandboxTable, None] = None sandbox_table_: SandboxTable
shop_client_table_: Union[ShopClientTable, None] = None shop_client_table_: ShopClientTable
skill_table_: Union[Dict[str, SkillDataBundle], None] = None skill_table_: SkillTable
skin_table_: Union[SkinTable, None] = None skin_table_: SkinTable
stage_table_: Union[StageTable, None] = None stage_table_: StageTable
story_review_meta_table_: Union[StoryReviewMetaTable, None] = None story_review_meta_table_: StoryReviewMetaTable
story_review_table_: Union[Dict[str, StoryReviewGroupClientData], None] = None story_review_table_: StoryReviewTable
story_table_: Union[Dict[str, StoryData], None] = None story_table_: StoryTable
tech_buff_table_: Union[TechBuffTable, None] = None tech_buff_table_: TechBuffTable
tip_table_: Union[TipTable, None] = None tip_table_: TipTable
token_table_: Union[Dict[str, TokenCharacterData], None] = None token_table_: TokenTable
uniequip_data_: Union[UniequipData, None] = None uniequip_data_: UniequipData
uniequip_table_: Union[UniEquipTable, None] = None uniequip_table_: UniEquipTable
zone_table_: Union[ZoneTable, None] = None zone_table_: ZoneTable
async def activity_table(self) -> None:
self.activity_table_ = ActivityTable.convert(
await store.get_excel("activity_table")
)
@property @property
def ACTIVITY_TABLE(self) -> ActivityTable: def ACTIVITY_TABLE(self) -> ActivityTable:
if not self.activity_table_:
self.activity_table_ = ActivityTable.convert(
CacheData.readExcel('activity_table')
)
return self.activity_table_ return self.activity_table_
async def audio_data(self) -> None:
self.audio_data_ = AudioData.convert(
await store.get_excel("audio_data")
)
@property @property
def AUDIO_DATA(self) -> AudioData: def AUDIO_DATA(self) -> AudioData:
if not self.audio_data_:
self.audio_data_ = AudioData.convert(
CacheData.readExcel('audio_data')
)
return self.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 @property
def BATTLE_EQUIP_TABLE(self) -> Dict[str, BattleEquipData]: def BATTLE_EQUIP_TABLE(self) -> BattleEquipTable:
if not self.battle_equip_table_:
self.battle_equip_table_ = msgjson.decode(
CacheData.readBytesExcel('battle_equip_table'),
type=Dict[str, BattleEquipData]
)
return self.battle_equip_table_ return self.battle_equip_table_
async def building_data(self) -> None:
self.building_data_ = BuildingData.convert(
await store.get_excel("building_data")
)
@property @property
def BUILDING_DATA(self) -> BuildingData: def BUILDING_DATA(self) -> BuildingData:
if not self.building_data_:
self.building_data_ = BuildingData.convert(
CacheData.readExcel('building_data')
)
return self.building_data_ return self.building_data_
async def campaign_table(self) -> None:
self.campaign_table_ = CampaignTable.convert(
await store.get_excel("campaign_table")
)
@property @property
def CAMPAIGN_TABLE(self) -> CampaignTable: def CAMPAIGN_TABLE(self) -> CampaignTable:
if not self.campaign_table_:
self.campaign_table_ = CampaignTable.convert(
CacheData.readExcel('campaign_table')
)
return self.campaign_table_ return self.campaign_table_
@property async def chapter_table(self) -> None:
def CHAPTER_TABLE(self) -> Dict[str, ChapterData]: self.chapter_table_ = ChapterTable.convert(
if not self.chapter_table_: {"chapters": await store.get_excel("chapter_table")}
self.chapter_table_ = msgjson.decode( )
CacheData.readBytesExcel('chapter_table'),
type=Dict[str, ChapterData]
)
return self.chapter_table_
@property @property
def CHARATER_TABLE(self) -> Dict[str, CharacterData]: def CHAPTER_TABLE(self) -> ChapterTable:
if not self.character_table_: return self.chapter_table_
self.character_table_ = msgjson.decode(
CacheData.readBytesExcel('character_table'), async def character_table(self) -> None:
type=Dict[str, CharacterData] self.character_table_ = CharacterTable.convert(
) {"chars": await store.get_excel("character_table")}
)
@property
def CHARATER_TABLE(self) -> CharacterTable:
return self.character_table_ 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 @property
def CHAR_META_TABLE(self) -> CharMetaTable: 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_ return self.char_meta_table_
async def charm_table(self) -> None:
self.charm_table_ = CharmTable.convert(
await store.get_excel("charm_table")
)
@property @property
def CHARM_TABLE(self) -> CharmTable: def CHARM_TABLE(self) -> CharmTable:
if not self.charm_table_:
self.charm_table_ = CharmTable.convert(
CacheData.readExcel('charm_table')
)
return self.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 @property
def CHAR_PATH_TABLE(self) -> CharPatchTable: 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_ return self.char_patch_table_
async def charword_table(self) -> None:
self.charword_table_ = CharwordTable.convert(
await store.get_excel("charword_table")
)
@property @property
def CHARWORD_TABLE(self) -> CharwordTable: def CHARWORD_TABLE(self) -> CharwordTable:
if not self.charword_table_:
self.charword_table_ = CharwordTable.convert(
CacheData.readExcel('charword_table')
)
return self.charword_table_ return self.charword_table_
async def checkin_table(self) -> None:
self.checkin_table_ = CheckinTable.convert(
await store.get_excel("checkin_table")
)
@property @property
def CHECKIN_TABLE(self) -> CheckinTable: def CHECKIN_TABLE(self) -> CheckinTable:
if not self.checkin_table_:
self.checkin_table_ = CheckinTable.convert(
CacheData.readExcel('checkin_table')
)
return self.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 @property
def CLIMB_TOWER_TABLE(self) -> ClimbTowerTable: 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_ return self.climb_tower_table_
async def clue_data(self) -> None:
self.clue_data_ = ClueData.convert(
await store.get_excel("clue_data")
)
@property @property
def CLUE_DATA(self) -> ClueData: def CLUE_DATA(self) -> ClueData:
if not self.clue_data_:
self.clue_data_ = ClueData.convert(
CacheData.readExcel('clue_data')
)
return self.clue_data_ return self.clue_data_
async def crisis_table(self) -> None:
self.crisis_table_ = CrisisTable.convert(
await store.get_excel("crisis_table")
)
@property @property
def CRISIS_TABLE(self) -> CrisisTable: def CRISIS_TABLE(self) -> CrisisTable:
if not self.crisis_table_:
self.crisis_table_ = CrisisTable.convert(
CacheData.readExcel('crisis_table')
)
return self.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 @property
def DISPLAY_META_TABLE(self) -> DisplayMetaTable: 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_ 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 @property
def ENEMY_HANDBOOK_TABLE(self) -> EnemyHandbookTable: 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_ return self.enemy_handbook_table_
async def favor_table(self) -> None:
self.favor_table_ = FavorTable.convert(
await store.get_excel("favor_table")
)
@property @property
def FAVOR_TABLE(self) -> FavorTable: def FAVOR_TABLE(self) -> FavorTable:
if not self.favor_table_:
self.favor_table_ = FavorTable.convert(
CacheData.readExcel('favor_table')
)
return self.favor_table_ return self.favor_table_
async def gacha_table(self) -> None:
self.gacha_table_ = GachaTable.convert(
await store.get_excel("gacha_table")
)
@property @property
def GACHA_TABLE(self) -> GachaTable: def GACHA_TABLE(self) -> GachaTable:
if not self.gacha_table_:
self.gacha_table_ = GachaTable.convert(
CacheData.readExcel('gacha_table')
)
return self.gacha_table_ return self.gacha_table_
async def gamedata_const(self) -> None:
self.gamedata_const_ = GamedataConst.convert(
await store.get_excel("gamedata_const")
)
@property @property
def GAMEDATA_CONST(self) -> GamedataConst: def GAMEDATA_CONST(self) -> GamedataConst:
if not self.gamedata_const_:
self.gamedata_const_ = GamedataConst.convert(
CacheData.readExcel('gamedata_const')
)
return self.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 @property
def HANDBOOK_INFO_TABLE(self) -> HandbookInfoTable: 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_ return self.handbook_info_table_
async def handbook_table(self) -> None:
self.handbook_table_ = HandbookTable.convert(
await store.get_excel("handbook_table")
)
@property @property
def HANDBOOK_TABLE(self) -> HandbookTable: def HANDBOOK_TABLE(self) -> HandbookTable:
if not self.handbook_table_:
self.handbook_table_ = HandbookTable.convert(
CacheData.readExcel('handbook_table')
)
return self.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 @property
def HANDBOOK_TEAM_TABLE(self) -> Dict[str, HandbookTeam]: def HANDBOOK_TEAM_TABLE(self) -> HandbookTeamTable:
if not self.handbook_team_table_:
self.handbook_team_table_ = msgjson.decode(
CacheData.readBytesExcel('handbook_team_table'),
type=Dict[str, HandbookTeam]
)
return self.handbook_team_table_ return self.handbook_team_table_
async def item_table(self) -> None:
self.item_table_ = ItemTable.convert(
await store.get_excel("item_table")
)
@property @property
def ITEM_TABLE(self) -> ItemTable: def ITEM_TABLE(self) -> ItemTable:
if not self.item_table_:
self.item_table_ = ItemTable.convert(
CacheData.readExcel('item_table')
)
return self.item_table_ return self.item_table_
async def medal_table(self) -> None:
self.medal_table_ = MedalTable.convert(
await store.get_excel("medal_table")
)
@property @property
def MEDAL_TABLE(self) -> MedalTable: def MEDAL_TABLE(self) -> MedalTable:
if not self.medal_table_:
self.medal_table_ = MedalTable.convert(
CacheData.readExcel('medal_table')
)
return self.medal_table_ return self.medal_table_
async def mission_table(self) -> None:
self.mission_table_ = MissionTable.convert(
await store.get_excel("mission_table")
)
@property @property
def MISSION_TABLE(self) -> MissionTable: def MISSION_TABLE(self) -> MissionTable:
if not self.mission_table_:
self.mission_table_ = MissionTable.convert(
CacheData.readExcel('mission_table')
)
return self.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 @property
def OPEN_SERVER_TABLE(self) -> OpenServerTable: 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_ 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 @property
def PLAYER_AVATAR_TABLE(self) -> PlayerAvatarTable: 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_ return self.player_avatar_table_
@property async def range_table(self) -> None:
def RANGE_TABLE(self) -> Dict[str, Stage]: self.range_table_ = RangeTable.convert(
if not self.range_table_: {"range": await store.get_excel("range_table")}
self.range_table_ = msgjson.decode( )
CacheData.readBytesExcel('range_table'),
type=Dict[str, Stage]
)
return self.range_table_
@property @property
def REPLICATE_TABLE(self) -> Dict[str, ReplicateList]: def RANGE_TABLE(self) -> RangeTable:
if not self.replicate_table_: return self.range_table_
self.replicate_table_ = msgjson.decode(
CacheData.readBytesExcel('replicate_table'), async def replicate_table(self) -> None:
type=Dict[str, ReplicateList] self.replicate_table_ = ReplicateTable.convert(
) {"replicate": await store.get_excel("replicate_table")}
)
@property
def REPLICATE_TABLE(self) -> ReplicateTable:
return self.replicate_table_ return self.replicate_table_
async def retro_table(self) -> None:
self.retro_table_ = RetroTable.convert(
await store.get_excel("retro_table")
)
@property @property
def RETRO_TABLE(self) -> RetroTable: def RETRO_TABLE(self) -> RetroTable:
if not self.retro_table_:
self.retro_table_ = RetroTable.convert(
CacheData.readExcel('retro_table')
)
return self.retro_table_ return self.retro_table_
async def roguelike_table(self) -> None:
self.roguelike_table_ = RoguelikeTable.convert(
await store.get_excel("roguelike_table")
)
@property @property
def ROGUELIKE_TABLE(self) -> RoguelikeTable: def ROGUELIKE_TABLE(self) -> RoguelikeTable:
if not self.roguelike_table_:
self.roguelike_table_ = RoguelikeTable.convert(
CacheData.readExcel('roguelike_table')
)
return self.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 @property
def ROGUELIKE_TOPIC_TABLE(self) -> RoguelikeTopicTable: 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_ return self.roguelike_topic_table_
async def sandbox_table(self) -> None:
self.sandbox_table_ = SandboxTable.convert(
await store.get_excel("sandbox_table")
)
@property @property
def SANDBOX_TABLE(self) -> SandboxTable: def SANDBOX_TABLE(self) -> SandboxTable:
if not self.sandbox_table_:
self.sandbox_table_ = SandboxTable.convert(
CacheData.readExcel('sandbox_table')
)
return self.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 @property
def SHOP_CLIENT_TABLE(self) -> ShopClientTable: 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_ return self.shop_client_table_
async def skill_table(self) -> None:
self.skill_table_ = SkillTable.convert(
{"skills": await store.get_excel("skill_table")}
)
@property @property
def SKILL_TABLE(self) -> Dict[str, SkillDataBundle]: def SKILL_TABLE(self) -> SkillTable:
if not self.skill_table_:
self.skill_table_ = msgjson.decode(
CacheData.readBytesExcel('skill_table'),
type=Dict[str, SkillDataBundle]
)
return self.skill_table_ return self.skill_table_
async def skin_table(self) -> None:
self.skin_table_ = SkinTable.convert(
await store.get_excel("skin_table")
)
@property @property
def SKIN_TABLE(self) -> SkinTable: def SKIN_TABLE(self) -> SkinTable:
if not self.skin_table_:
self.skin_table_ = SkinTable.convert(
CacheData.readExcel('skin_table')
)
return self.skin_table_ return self.skin_table_
async def stage_table(self) -> None:
self.stage_table_ = StageTable.convert(
await store.get_excel("stage_table")
)
@property @property
def STAGE_TABLE(self) -> StageTable: def STAGE_TABLE(self) -> StageTable:
if not self.stage_table_:
self.stage_table_ = StageTable.convert(
CacheData.readExcel('stage_table')
)
return self.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 @property
def STORY_REVIEW_META_TABLE(self) -> StoryReviewMetaTable: 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_ return self.story_review_meta_table_
@property async def story_review_table(self) -> None:
def STORY_REVIEW_TABLE(self) -> Dict[str, StoryReviewGroupClientData]: self.story_review_table_ = StoryReviewTable.convert(
if not self.story_review_table_: {"storyreviewtable": await store.get_excel("story_review_table")}
self.story_review_table_ = msgjson.decode( )
CacheData.readBytesExcel('story_review_table'),
type=Dict[str, StoryReviewGroupClientData]
)
return self.story_review_table_
@property @property
def STORY_TABLE(self) -> Dict[str, StoryData]: def STORY_REVIEW_TABLE(self) -> StoryReviewTable:
if not self.story_table_: return self.story_review_table_
self.story_table_ = msgjson.decode(
CacheData.readBytesExcel('story_table'), async def story_table(self) -> None:
type=Dict[str, StoryData] self.story_table_ = StoryTable.convert(
) {"stories": await store.get_excel("story_table")}
)
@property
def STORY_TABLE(self) -> StoryTable:
return self.story_table_ 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 @property
def TECH_BUFF_TABLE(self) -> TechBuffTable: 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_ return self.tech_buff_table_
async def tip_table(self) -> None:
self.tip_table_ = TipTable.convert(
await store.get_excel("tip_table")
)
@property @property
def TIP_TABLE(self) -> TipTable: def TIP_TABLE(self) -> TipTable:
if not self.tip_table_:
self.tip_table_ = TipTable.convert(
CacheData.readExcel('tip_table')
)
return self.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 @property
def TOKEN_TABLE(self) -> Dict[str, TokenCharacterData]: def TOKEN_TABLE(self) -> TokenTable:
if not self.token_table_:
self.token_table_ = msgjson.decode(
CacheData.readBytesExcel('token_table'),
type=Dict[str, TokenCharacterData]
)
return self.token_table_ return self.token_table_
async def uniequip_data(self) -> None:
self.uniequip_data_ = UniequipData.convert(
await store.get_excel("uniequip_data")
)
@property @property
def UNIEQUIP_DATA(self) -> UniequipData: def UNIEQUIP_DATA(self) -> UniequipData:
if not self.uniequip_data_:
self.uniequip_data_ = UniequipData.convert(
CacheData.readExcel('uniequip_data')
)
return self.uniequip_data_ return self.uniequip_data_
async def uniequip_table(self) -> None:
self.uniequip_table_ = UniEquipTable.convert(
await store.get_excel("uniequip_table")
)
@property @property
def UNIEQUIP_TABLE(self) -> UniEquipTable: def UNIEQUIP_TABLE(self) -> UniEquipTable:
if not self.uniequip_table_:
self.uniequip_table_ = UniEquipTable.convert(
CacheData.readExcel('uniequip_table')
)
return self.uniequip_table_ return self.uniequip_table_
async def zone_table(self) -> None:
self.zone_table_ = ZoneTable.convert(
await store.get_excel("zone_table")
)
@property @property
def ZONE_TABLE(self) -> ZoneTable: def ZONE_TABLE(self) -> ZoneTable:
if not self.zone_table_:
self.zone_table_ = ZoneTable.convert(
CacheData.readExcel('zone_table')
)
return self.zone_table_ return self.zone_table_
def preload_table(self) -> None: async def preload_table(self) -> None:
for name, method in inspect.getmembers(self, predicate=inspect.ismethod): tasks = []
if callable(method) and not name.startswith('__') \ for name, method in inspect.getmembers(self):
and name != 'preload_table': if (
method() inspect.iscoroutinefunction(method)
and not name.startswith("__")
and name != "preload_table"
):
await method()
await asyncio.gather(*tasks)
Excel = ExcelTableManager() 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()