export session management to Session
authorniki <niki@nikiroo.be>
Sun, 28 Sep 2025 20:30:10 +0000 (22:30 +0200)
committerniki <niki@nikiroo.be>
Sun, 28 Sep 2025 20:30:10 +0000 (22:30 +0200)
gamiki/__init__.py
gamiki/game.py
gamiki/session.py [new file with mode: 0644]

index 595436ad9b33d3fc900fc0cf2befa4bf791df0ca..55bf1b722fe028c6094b0ebb65fd117e42de8259 100644 (file)
@@ -1,4 +1,5 @@
 # Order the dependencies in the right way, and make public classes accessible
+from .session         import Session
 from .game            import Game
 from .support         import Support
 from .library         import Library
index 9c6354e9ebec5948cd6e26293fb5177dd798b574..035a340ded9c32929df715dfb862a41e6cd30155 100644 (file)
@@ -4,6 +4,7 @@ from pathlib  import PurePath, Path
 from time     import time
 from datetime import datetime
 from random   import choice
+from gamiki   import Session
 
 class Game(dict):
     """Game object generated from a directory."""
@@ -30,7 +31,7 @@ class Game(dict):
     desc           : str      = ""
     total_time     : int      = 0 
     total_sessions : int      = 0
-    sessions       : list     = None # list of (YYYY-MM-DD, HH:MM, secs, opt)
+    sessions       : list     = None # list:Session (call read_sessions() first)
     icon           : Path     = None # "main" icon
     icons          : dict     = None # all found icons
     intro          : list     = None # all found intro videos
@@ -92,27 +93,8 @@ class Game(dict):
         except FileNotFoundError:
             pass
         
-        file = self.dir.joinpath("sessions.total")
-        try:
-            with open(file, encoding="utf-8") as data:
-                ln = data.readline() # number of sessions
-                sessions = int(ln.split(":", maxsplit=1)[1].strip())
-                ln = data.readline() # total time in human formated time
-                human = ln.split(":", maxsplit=1)[1].strip()
-                tab = human.split(":")
-                total = int((int)(tab[0]) * 3600 
-                          + (int)(tab[1]) * 60 
-                          + (int)(tab[2])
-                )
-                
-                self.total_time = total
-                self.total_sessions = sessions
-                
-                # TODO: read the total per option times
-        except FileNotFoundError:
-            pass
-        except Exception as e:
-            print(f"Error when querying {file.as_posix()}: {e}", file=stderr)
+        self.total, self.sessions = Session.read_total(self.dir)
+        # TODO: read the total per option times
     
     def _read_icon(self, suffix: str = "") -> PurePath:
         """
@@ -152,96 +134,8 @@ class Game(dict):
     def get_icon(self, key: str) -> PurePath:
         return self.icons.get(key, self.icon)
     
-    def _read_sessions(self) -> list:
-        self.sessions = []
-        file = self.dir.joinpath("sessions.txt")
-        if (file.exists()):
-            try:
-                with open(file, "r", encoding="utf-8") as data:
-                    for ln in data:
-                        start_d = ""
-                        start_h = ""
-                        total = 0
-                        so = ""
-                        tab = ln.strip().split(" ")
-                        if (len(tab) >= 4):
-                            start_d = tab[2]
-                            start_h = tab[3].split("(")[1].split(")")[0]
-                        if (len(tab) >= 6):
-                            htab = tab[5].split(":")
-                            if (len(htab) == 3):
-                                total += int(htab[0]) * 3600 
-                                total += int(htab[1]) * 60 
-                                total += int(htab[2])
-                        if (len(tab) >= 8):
-                            so = tab[7]
-                        if (start_d and start_h):
-                            self.sessions.append((start_d, start_h, total, so))
-            except Exception as e:
-                print(f"Error when querying {file.as_posix()}: {e}",file=stderr)
-    
-    def add_session(self, when: datetime, elapsed: int, opt: str):
-        def human(seconds: int) -> str:
-            ss = (seconds % 60)
-            mm = (seconds // 60) % 60
-            hh = (seconds // 3600)
-            return f"{hh:02}:{mm:02}:{ss:02}"
-        
-        O = ""
-        if (opt):
-            O = f" - {opt}"
-        
-        start_d = when.strftime('%Y-%m-%d')
-        start_h = when.strftime('%H:%M')
-        text = f"Session of {start_d} ({start_h}) lasted: {human(elapsed)}{O}"
-        print(f"[{self.name}]: {text}")
-        
-        if (not self.sessions):
-            self._read_sessions()
-        
-        self.sessions.append((start_d, start_h, elapsed, opt))
-        
-        total = 0
-        for session in self.sessions:
-            total += session[2]
-        print(
-                f"[{self.name}]: Total time: "
-                f"{human(total)} (in {len(self.sessions)} session(s))"
-        )
-        
-        try:
-            file = self.dir.joinpath("sessions.txt")
-            with open(file, "a", encoding="utf-8") as data:
-                data.write(text)
-                data.write("\n")
-        except Exception as e:
-            print("Cannot save session time:", e, file=stderr)
-            total = None
-        
-        try:
-            if (total):
-                file = self.dir.joinpath("sessions.total")
-                with open(file, "w", encoding="utf-8") as data:
-                    data.write(f"Sessions: {len(self.sessions)}\n")
-                    data.write(f"Total time: {human(total)}\n")
-                    
-                    if (len(self.opts) > 1):
-                        data.write("\nTotal per option:\n")
-                        total = 0
-                        for session in self.sessions:
-                            if (session[3] == None or session[3] == ""):
-                                total += session[2]
-                        data.write(f"* {human(total)}\n");
-                        for opt in self.opts:
-                            total = 0
-                            for session in self.sessions:
-                                if (len(session) > 3):
-                                    if (session[3] == opt):
-                                        total += session[2]
-                            data.write(f"* {human(total)} - {opt}\n");
-                            
-        except:
-            print("Cannot save total time", file=stderr)
+    def read_sessions(self) -> list:
+        self.sessions = Session.read_sessions(self.dir)
 
     def run(self, opt: str = None):
         if (not self.support):
@@ -261,5 +155,9 @@ class Game(dict):
         
         elapsed = int(time() - begin)
         if (elapsed >= 60): # Ignore very short sessions
-            self.add_session(today, elapsed, opt)
+            if (self.sessions == None):
+                self.read_sessions()
+
+            new_sess = Session(today, elapsed, opt)
+            Session.write_new_session(self.dir, self.sessions, new_sess)
 
diff --git a/gamiki/session.py b/gamiki/session.py
new file mode 100644 (file)
index 0000000..9574569
--- /dev/null
@@ -0,0 +1,173 @@
+from pathlib  import PurePath, Path
+from sys      import stderr
+from time     import time
+from datetime import datetime
+
+class Session():
+    """Play session for a game."""
+
+    when    : datetime = None
+    elapsed : int      = None
+    opt     : str      = None
+
+    def __init__(self, when: datetime, elapsed: int, opt: str = None):
+        self.when    = when
+        self.elapsed = elapsed
+        self.opt     = opt
+
+    def from_str(ln: str):
+        dat = ""
+        hour = ""
+        total = 0
+        opt = ""
+        tab = ln.strip().split(" ")
+        if (len(tab) >= 4):
+            dat  = tab[2]
+            hour = tab[3].split("(")[1].split(")")[0]
+        if (len(tab) >= 6):
+            htab = tab[5].split(":")
+            if (len(htab) == 3):
+                total += int(htab[0]) * 3600
+                total += int(htab[1]) * 60
+                total += int(htab[2])
+        if (len(tab) >= 8):
+            opt = tab[7]
+        if (dat and hour):
+            return Session(
+                datetime.strptime(f"{dat} {hour}", "%Y-%m-%d %H:%M"), total, opt
+            )
+
+        return None
+
+    def __str__(self) -> str:
+        oopt = ""
+        if (self.opt):
+            oopt = f" - {opt}"
+
+        dat  = self.when.strftime('%Y-%m-%d')
+        hour = self.when.strftime('%H:%M')
+        humanf = Session._human(self.elapsed)
+        return f"Session of {dat} ({hour}) lasted: {humanf}{oopt}"
+
+    def read_total(dir: PurePath) -> (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)
+        """
+
+        ln1 = "(not read yet)"
+        ln2 = "(not read yet)"
+        file = dir.joinpath("sessions.total")
+        try:
+            with open(file, encoding="utf-8") as data:
+                ln1 = data.readline().strip() # number of sessions
+                num_sessions = int(ln1.split(":", maxsplit=1)[1].strip())
+                ln2 = data.readline().strip() # total time, human formated
+                humanf = ln2.split(":", maxsplit=1)[1].strip()
+                tab = humanf.split(":")
+                total = int((int)(tab[0]) * 3600
+                          + (int)(tab[1]) * 60
+                          + (int)(tab[2])
+                )
+
+                # TODO: read the total per option times?
+                return (total, num_sessions)
+        except FileNotFoundError:
+            pass
+        except Exception as e:
+            #raise ValueError(f"Error when querying {file.as_posix()}: {e}"
+            #    + "\n" + "First 2 lines:\n" + ln1 + "\n" + ln2 + "\n.\n"
+            #)
+            print(f"Error when reading total from {file.as_posix()}: {e}"
+                + "\n" + "First 2 lines:\n" + ln1 + "\n" + ln2 + "\n.\n"
+                + "We will try to re-read the whole session file...",
+                file=stderr
+            )
+
+        total        = 0
+        num_sessions = 0
+        sessions = Session.read_sessions(dir)
+        for sess in sessions:
+            num_sessions += 1
+            total        += sess.elapsed
+
+        return (total, num_sessions)
+
+    def read_sessions(dir: PurePath) -> list['Session']:
+        sessions = []
+        file = dir.joinpath("sessions.txt")
+        if (file.exists()):
+            try:
+                with open(file, "r", encoding="utf-8") as data:
+                    for ln in data:
+                        sess = Session.from_str(ln)
+                        if (sess):
+                            sessions.append(sess)
+            except Exception as e:
+                raise ValueError(f"Error when reading {file.as_posix()}: {e}")
+
+        return sessions
+
+    def write_total(dir: PurePath, sessions: list):
+        total = 0
+        opts = []
+        for sess in sessions:
+            total += sess.elapsed
+            if not sess.opt in opts:
+                opts.append(sess.opt)
+
+        file = dir.joinpath("sessions.total")
+        with open(file, "w", encoding="utf-8") as data:
+            data.write(f"Sessions: {len(sessions)}\n")
+            data.write(f"Total time: {Session._human(total)}\n")
+
+            if (len(opts) > 1):
+                data.write("\nTotal per option:\n")
+                total = 0
+                for session in sessions:
+                    if (session[3] == None or session[3] == ""):
+                        total += session[2]
+                data.write(f"* {_human(total)}\n");
+                for opt in opts:
+                    total = 0
+                    for session in sessions:
+                        if (len(session) > 3):
+                            if (session[3] == opt):
+                                total += session[2]
+                    data.write(f"* {Session._human(total)} - {opt}\n");
+
+    def write_new_session(dir: PurePath, sessions: list, new_sess: 'Session'):
+        """
+        Write a new session on the given game directory -- 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 previous play sessions (will be used to overwrite
+                         the previous information, be careful!) -- note that the
+                         new play session will also be appended on this one
+        @param new_sess: the new play session to add
+
+        """
+
+        # Can throw:
+        file = dir.joinpath("sessions.txt")
+        with open(file, "a", encoding="utf-8") as data:
+            data.write(str(new_sess))
+            data.write("\n")
+
+        sessions.append(new_sess)
+
+        # 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)
+        mm = (seconds // 60) % 60
+        hh = (seconds // 3600)
+        return f"{hh:02}:{mm:02}:{ss:02}"