diff --git a/plextraktsync/db/Database.py b/plextraktsync/db/Database.py new file mode 100644 index 0000000000..3ad6b4264f --- /dev/null +++ b/plextraktsync/db/Database.py @@ -0,0 +1,51 @@ +import sqlite3 + +from plextraktsync.factory import logging + + +class Database: + _uncommited = False + + def __init__(self, database_path: str): + self.logger = logging.getLogger("PlexTraktSync.Database") + try: + self.filename = database_path + self._connection = sqlite3.connect(database_path) + self._cursor = self._connection.cursor() + self._cursor.execute('ANALYZE') + + except sqlite3.OperationalError as e: + self.logger.error(e) + raise e + + except sqlite3.DatabaseError as e: + self.logger.error(e) + raise e + + @property + def cursor(self): + return self._cursor + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self._uncommited: + self.commit() + + self._connection.close() + + def execute(self, query, *args): + self._uncommited = True + return self.cursor.execute(query, *args) + + def commit(self): + self._connection.commit() + self._uncommited = False + + def rollback(self): + self._connection.rollback() + self._uncommited = False + + def has_uncommited(self): + return self._uncommited diff --git a/plextraktsync/db/SyncDatabase.py b/plextraktsync/db/SyncDatabase.py new file mode 100644 index 0000000000..698a3e0871 --- /dev/null +++ b/plextraktsync/db/SyncDatabase.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from plextraktsync.db.Database import Database +from plextraktsync.db.SyncRecord import SyncRecord + +if TYPE_CHECKING: + from plextraktsync.media import Media + + +class SyncDatabase: + table_name = "sync" + # fields: + # A. Media ID + # B. Plex timestamp watched + # C. seen on Plex sync? + # D. Trakt timestamp watched + # E. seen on Trakt sync? + # F. result + schema = """ + id PRIMARY KEY, + media_id, + plex_timestamp_watched, + seen_on_plex_sync, + trakt_timestamp_watched, + seen_on_trakt_sync, + result + """ + + def __init__(self, con: Database): + self.con = con + with self.con as con: + self._create_table(con) + + # Initial CREATE TABLE must happen in shared connection; subsequent queries will use thread-local connections + def _create_table(self, con: Database): + con.execute(f'CREATE TABLE IF NOT EXISTS {self.table_name} ({self.schema})') + + def insert(self, record: SyncRecord): + pass + + def update(self, m: Media): + record = SyncRecord( + media_id=m.trakt_id, + plex_timestamp_watched=m.watched_on_plex, + seen_on_plex_sync=m.watched_on_plex, + trakt_timestamp_watched=m.watched_on_trakt, + seen_on_trakt_sync=m.watched_on_trakt, + result="", + ) + print(record) diff --git a/plextraktsync/db/SyncRecord.py b/plextraktsync/db/SyncRecord.py new file mode 100644 index 0000000000..84e281f0e5 --- /dev/null +++ b/plextraktsync/db/SyncRecord.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass + + +@dataclass +class SyncRecord: + media_id: str + plex_timestamp_watched: str + seen_on_plex_sync: str + trakt_timestamp_watched: str + seen_on_trakt_sync: str + result: str + + id: str = None diff --git a/plextraktsync/sync/Sync.py b/plextraktsync/sync/Sync.py index 9c36b0c58a..b47fa67fb4 100644 --- a/plextraktsync/sync/Sync.py +++ b/plextraktsync/sync/Sync.py @@ -11,16 +11,18 @@ from plextraktsync.plan.Walker import Walker from plextraktsync.plex.PlexApi import PlexApi from plextraktsync.trakt.TraktApi import TraktApi + from plextraktsync.db.SyncDatabase import SyncDatabase class Sync: logger = logging.getLogger(__name__) - def __init__(self, config: SyncConfig, plex: PlexApi, trakt: TraktApi): + def __init__(self, config: SyncConfig, plex: PlexApi, trakt: TraktApi, sync_state: SyncDatabase): self.config = config self.plex = plex self.trakt = trakt self.walker = None + self.sync_state = sync_state @cached_property def trakt_lists(self): @@ -47,9 +49,11 @@ async def sync(self, walker: Walker, dry_run=False): if self.config.need_library_walk: async for movie in walker.find_movies(): + self.sync_state.update(movie) await pm.ahook.walk_movie(movie=movie, dry_run=dry_run) async for episode in walker.find_episodes(): + self.sync_state.update(episode) await pm.ahook.walk_episode(episode=episode, dry_run=dry_run) await pm.ahook.fini(walker=walker, dry_run=dry_run) diff --git a/plextraktsync/util/Factory.py b/plextraktsync/util/Factory.py index e16c0969ec..8a289fa403 100644 --- a/plextraktsync/util/Factory.py +++ b/plextraktsync/util/Factory.py @@ -140,7 +140,7 @@ def sync(self): plex = self.plex_api trakt = self.trakt_api - return Sync(self.sync_config, plex, trakt) + return Sync(self.sync_config, plex, trakt, self.sync_database) @cached_property def progressbar(self): @@ -300,6 +300,18 @@ def sync_config(self): return SyncConfig(self.config, self.server_config) + @cached_property + def sync_database(self): + from os.path import join + + from plextraktsync.db.Database import Database + from plextraktsync.db.SyncDatabase import SyncDatabase + from plextraktsync.path import cache_dir + + db_path = join(cache_dir, "sync.sqlite") + + return SyncDatabase(Database(db_path)) + @cached_property def queue(self): from plextraktsync.queue.BackgroundTask import BackgroundTask