diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a043e9d..0e9c7e6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,13 +13,17 @@ jobs: uses: actions/setup-python@v2 with: python-version: "3.12.3" + - name: Install GCC + uses: egor-tensin/setup-gcc@v1.3 - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel twine + - name: Build and Publish env: TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} run: | + gcc -fPIC -shared -o flomo\session_id.so flomo\session_id.c python setup.py sdist bdist_wheel twine upload dist/* diff --git a/MANIFEST.in b/MANIFEST.in index 945326a..8346f54 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include docs/README.md include LICENSE include flomo/beep.mp3 +include flomo/session_id.so diff --git a/flomo/cli.py b/flomo/cli.py index b02c729..a81a4a6 100644 --- a/flomo/cli.py +++ b/flomo/cli.py @@ -1,5 +1,4 @@ import datetime -import gc import sys from typing import Tuple @@ -107,16 +106,23 @@ def delete(session_ids: Tuple): db = tracker.Tracker() if not db.get_sessions(): raise errors.NoSessionsError() - click.confirm("Are you sure you want to delete the session(s)?", abort=True) + session_1 = len(session_ids) == 1 + session_0 = len(session_ids) == 0 + click.confirm( + f"Are you sure you want to delete the {f'session{'' if session_1 else 's'}: {", ".join(session_ids)}' if not session_0 else 'last session'}?", + abort=True, + ) db.delete_session(session_ids) db.conn.close() - if len(session_ids) == 0: + if session_0: return print("Deleted the last session.") - print(f"Deleted session(s): {', '.join(map(str, session_ids))}") + print( + f"Deleted session{'' if session_1 else 's'}: {', '.join(map(str, session_ids))}" + ) except ( errors.DBFileNotFoundError, errors.NoSessionError, - errors.NoSessionsError + errors.NoSessionsError, ) as e: helpers.error_log(str(e)) print(e) @@ -132,7 +138,7 @@ def change(session_id: str, tag: str | None, name: str | None): """ try: db = tracker.Tracker() - db.update_session(int(session_id), tag, name) + db.update_session(session_id, tag, name) db.conn.close() except ( errors.DBFileNotFoundError, diff --git a/flomo/errors.py b/flomo/errors.py index 0884a48..fb3e6d7 100644 --- a/flomo/errors.py +++ b/flomo/errors.py @@ -19,5 +19,5 @@ def __init__(self): class NoSessionError(Exception): - def __init__(self, session_id: int): + def __init__(self, session_id: str): super().__init__(f"No session with ID {session_id} was found.") diff --git a/flomo/session_id.c b/flomo/session_id.c new file mode 100644 index 0000000..04d02da --- /dev/null +++ b/flomo/session_id.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +const char ALPHANUMERIC[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +const int BASE = 62; + +char *encode_timestamp(unsigned long long timestamp) +{ + char *encoded = (char *)malloc((sizeof(timestamp) * CHAR_BIT + 5) / 6 + 1); + int index = 0; + + if (timestamp == 0) + { + encoded[index++] = ALPHANUMERIC[0]; + } + + do + { + int rem = timestamp % BASE; + encoded[index++] = ALPHANUMERIC[rem]; + timestamp /= BASE; + } while (timestamp > 0); + + for (int i = 0; i < index / 2; i++) + { + char temp = encoded[i]; + encoded[i] = encoded[index - i - 1]; + encoded[index - i - 1] = temp; + } + + encoded[index] = '\0'; + return encoded; +} + +unsigned long long decode_timestamp(const char *encoded) +{ + unsigned long long timestamp = 0; + size_t length = strlen(encoded); + + for (size_t i = 0; i < length; i++) + { + char *ptr = strchr(ALPHANUMERIC, encoded[i]); + if (ptr) + { + int index = ptr - ALPHANUMERIC; + timestamp = timestamp * BASE + index; + } + else + { + fprintf(stderr, "Error: Invalid character '%c' in encoded string.\n", encoded[i]); + return 0; + } + } + + return timestamp; +} diff --git a/flomo/tracker.py b/flomo/tracker.py index e0cc268..dc11585 100644 --- a/flomo/tracker.py +++ b/flomo/tracker.py @@ -1,3 +1,4 @@ +import ctypes import datetime import sqlite3 from typing import Tuple @@ -8,6 +9,8 @@ import flomo.errors as errors import flomo.helpers as helpers +# TODO: Setup Dev & Prod Database Environments! + class Tracker: def __init__(self, initializing: bool = False): @@ -29,9 +32,18 @@ def create_table(self): ) self.conn.commit() - def create_session(self, tag: str, name: str, start_time: datetime.datetime) -> int: - # TODO: Better way of generating session_id - session_id = int(start_time.timestamp() % 1000000) + def create_session(self, tag: str, name: str, start_time: datetime.datetime) -> str: + lib = ctypes.CDLL(helpers.get_path("session_id.so")) + + lib.encode_timestamp.argtypes = [ctypes.c_ulonglong] + lib.encode_timestamp.restype = ctypes.c_char_p + lib.decode_timestamp.argtypes = [ctypes.c_char_p] + lib.decode_timestamp.restype = ctypes.c_ulonglong + + _session_id: bytes = lib.encode_timestamp( + ctypes.c_ulonglong(int(start_time.timestamp())) + ) + session_id = _session_id.decode("utf-8") self.cursor.execute( "INSERT INTO sessions (id, date_time, tag, name) VALUES (?, ?, ?, ?)", (session_id, start_time.strftime("%Y-%m-%d %H:%M:%S"), tag, name), @@ -39,7 +51,7 @@ def create_session(self, tag: str, name: str, start_time: datetime.datetime) -> self.conn.commit() return session_id - def end_session(self, session_id: int, end_time: datetime.datetime): + def end_session(self, session_id: str, end_time: datetime.datetime): date_time = self.get_session(session_id)[1] total_time = end_time - datetime.datetime.strptime( date_time, "%Y-%m-%d %H:%M:%S" @@ -57,19 +69,18 @@ def get_sessions(self): self.cursor.execute("SELECT * FROM sessions") return self.cursor.fetchall() - def get_session(self, session_id: int): + def get_session(self, session_id: str): self.cursor.execute("SELECT * FROM sessions WHERE id = ?", (session_id,)) return self.cursor.fetchone() def delete_session(self, session_ids: Tuple[str] | Tuple): if len(session_ids) == 0: self.cursor.execute( - "DELETE FROM sessions WHERE id = (SELECT MAX(id) FROM sessions)" + "DELETE FROM sessions WHERE date_time = (SELECT MAX(date_time) FROM sessions)" ) self.conn.commit() for session_id in session_ids: - session_id = int(session_id) if not self.get_session(session_id): raise errors.NoSessionError(session_id) @@ -81,7 +92,7 @@ def delete_session(self, session_ids: Tuple[str] | Tuple): ) self.conn.commit() - def update_session(self, session_id: int, tag: str | None, name: str | None): + def update_session(self, session_id: str, tag: str | None, name: str | None): if not self.get_session(session_id): raise errors.NoSessionError(session_id) @@ -98,7 +109,7 @@ def update_session(self, session_id: int, tag: str | None, name: str | None): self.conn.commit() -def end_session(session_id: int): +def end_session(session_id: str): db = Tracker() db.end_session(session_id, datetime.datetime.now()) db.conn.close() diff --git a/flomo/ui.py b/flomo/ui.py index 02f8dba..0b0a54d 100644 --- a/flomo/ui.py +++ b/flomo/ui.py @@ -79,7 +79,7 @@ def get_input(self): return self.terminal.inkey().lower() -def main(tag: str, name: str, session_id: int): +def main(tag: str, name: str, session_id: str): # TODO: Do something with the Terminal close issue try: while True: