From ad5397f324001ba79b59c05b7337d64fea6307f6 Mon Sep 17 00:00:00 2001 From: Niki Date: Mon, 29 Sep 2025 08:01:39 +0200 Subject: [PATCH] session management in CLI --- gamiki.py | 32 ++++++++++++++++++++++++ gamiki/builder.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++- gamiki/game.py | 8 ++++-- gamiki/session.py | 36 ++++++++++++++++++++++++--- 4 files changed, 132 insertions(+), 7 deletions(-) diff --git a/gamiki.py b/gamiki.py index abbcf82..a37b2db 100755 --- a/gamiki.py +++ b/gamiki.py @@ -24,6 +24,18 @@ global_actions.add_argument("--list-tags", help="list all known games and their tags", action="store_true" ) +global_actions.add_argument("--session-time", + help="time played per game", + action="store_true" +) +global_actions.add_argument("--export-sessions", + help="export play sessions of all known games", + action="store_true" +) +global_actions.add_argument("--import-sessions", + help="import (and merge) play sessions", + action="store_true" +) # Select a game game_selection = parser.add_argument_group('game selection') @@ -66,6 +78,10 @@ game_actions.add_argument("--info-icon", help="get the icon of the game", metavar="GAME" ) +game_actions.add_argument("--info-sessions", + help="get the play sessions of the game", + metavar="GAME" +) args = parser.parse_args() @@ -79,6 +95,15 @@ elif (args.list_tag): elif (args.list_tags): builder.list_tags() exit(0) +elif (args.session_time): + builder.session_time() + exit(0) +elif (args.export_sessions): + builder.export_sessions() + exit(0) +elif (args.import_sessions): + builder.import_sessions() + exit(0) g = "" if (args.info): @@ -95,6 +120,8 @@ elif (args.info_options): g = args.info_options elif (args.info_icon): g = args.info_icon +elif (args.info_sessions): + g = args.info_sessions else: g = args.GAME @@ -135,6 +162,11 @@ elif (args.info_options): elif (args.info_icon): if ("Icon" in game): print(game["Icon"]) +elif (args.info_sessions): + print("Listing play sessions of:", game.name) + game.read_sessions() + for sess in game.sessions: + print(sess) else: game.run(args.OPTION) diff --git a/gamiki/builder.py b/gamiki/builder.py index 1e3fc2c..3556c07 100644 --- a/gamiki/builder.py +++ b/gamiki/builder.py @@ -1,6 +1,7 @@ +from sys import stdin, stderr from pathlib import Path, PurePath -from gamiki import Game, Support, Library +from gamiki import Game, Support, Library, Session from gamiki.support import ( SupportDos, SupportWin31, SupportGog, SupportExt, SupportWin ) @@ -61,6 +62,66 @@ class Builder: "(" + ";".join(game.tags) +")") i += 1 + def session_time(self): + i = 1 + for game in self.games: + if (game.total_time > 0): + num = "{0:6d}".format(i) + print(f"{num} {game.total_htime}: {game.name}"); + i += 1 + + def export_sessions(self): + for lib in self.libraries: + for game in lib: + game.read_sessions() + if (len(game.sessions)): + print(">", lib.name + "/" + game.code) + for sess in game.sessions: + print(sess) + + def import_sessions(self): + sgam = "" + slib = "" + curr_lib = None + curr_gam = None + sss = None + + for ln in stdin: + ln = ln.strip() + if (ln and ln[0] == ">"): + slib = ln.split("/")[0][2:] + sgam = ln.split("/", maxsplit=1)[1] + + # TODO fix speed + if (curr_lib and curr_lib.name != slib): + curr_lib = None + if (not curr_lib): + for lib in self.libraries: + if (lib.name == slib): + curr_lib = lib + if (curr_gam and curr_gam.code != sgam): + Session.rewrite_sessions(curr_gam.dir, list(sss.values())) + curr_gam = None + if (curr_lib and not curr_gam): + for gam in curr_lib: + if (gam.code == sgam): + curr_gam = gam + else: + if (curr_gam): + if (sss == None): + curr_gam.read_sessions() + sss = {} + for sess in curr_gam.sessions: + sss[sess.when] = sess + + new_sess = Session.from_str(ln) + sss[new_sess.when] = new_sess + else: + print("Ignoring unknown game:", slib + "/" + sgam, file=stderr) + + if (curr_gam): + Session.rewrite_sessions(curr_gam.dir, list(sss.values())) + def find(self, key: str) -> Game: game: Game = None diff --git a/gamiki/game.py b/gamiki/game.py index 035a340..c805a3f 100644 --- a/gamiki/game.py +++ b/gamiki/game.py @@ -30,6 +30,7 @@ class Game(dict): opts : dict = None # option name -> description desc : str = "" total_time : int = 0 + total_htime : str = "" # human readable time (HH:MM:SS) total_sessions : int = 0 sessions : list = None # list:Session (call read_sessions() first) icon : Path = None # "main" icon @@ -93,8 +94,11 @@ class Game(dict): except FileNotFoundError: pass - self.total, self.sessions = Session.read_total(self.dir) - # TODO: read the total per option times + # TODO: read the total per option times? + tot, htot, sessions = Session.read_total(self.dir) + self.total_time = tot + self.total_htime = htot + self.total_sessions = sessions def _read_icon(self, suffix: str = "") -> PurePath: """ diff --git a/gamiki/session.py b/gamiki/session.py index 9574569..42b3d01 100644 --- a/gamiki/session.py +++ b/gamiki/session.py @@ -49,13 +49,17 @@ class Session(): humanf = Session._human(self.elapsed) return f"Session of {dat} ({hour}) lasted: {humanf}{oopt}" - def read_total(dir: PurePath) -> (int, int): + def read_total(dir: PurePath) -> (int, int, int): """ Read a session.total file. @param dir: the game directory where to find the session.total file - @return (total play time in second, number of sessions played) + @return ( + total play time in second, + total play time in HH:MM:SS format, + number of sessions played + ) """ ln1 = "(not read yet)" @@ -74,7 +78,7 @@ class Session(): ) # TODO: read the total per option times? - return (total, num_sessions) + return (total, Session._human(total), num_sessions) except FileNotFoundError: pass except Exception as e: @@ -94,7 +98,7 @@ class Session(): num_sessions += 1 total += sess.elapsed - return (total, num_sessions) + return (total, Session._human(total), num_sessions) def read_sessions(dir: PurePath) -> list['Session']: sessions = [] @@ -165,6 +169,30 @@ class Session(): Session.write_total(dir, sessions) except Exception as e: print(f"Cannot save total time: {e}", file=stderr) + + def rewrite_sessions(dir: PurePath, sessions: list): + """ + Rewrite the list of sessions, overriding the previous one -- will also + update the sessions.total file or create it if needed. + + @param dir: the game directory where to find the session.total file + @param sessions: the new play sessions (will be used to overwrite + the previous information, be careful!) + + """ + + # Can throw: + file = dir.joinpath("sessions.txt") + with open(file, "w", encoding="utf-8") as data: + for sess in sessions: + data.write(str(sess)) + data.write("\n") + + # We catch the exceptions, as this file can be recomputed if needed + try: + Session.write_total(dir, sessions) + except Exception as e: + print(f"Cannot save total time: {e}", file=stderr) def _human(seconds: int) -> str: ss = (seconds % 60) -- 2.27.0