pakt 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pakt/__init__.py +3 -0
- pakt/__main__.py +6 -0
- pakt/assets/icon.png +0 -0
- pakt/assets/icon.svg +10 -0
- pakt/assets/logo.png +0 -0
- pakt/cli.py +814 -0
- pakt/config.py +222 -0
- pakt/models.py +109 -0
- pakt/plex.py +758 -0
- pakt/scheduler.py +153 -0
- pakt/sync.py +1490 -0
- pakt/trakt.py +575 -0
- pakt/tray.py +137 -0
- pakt/web/__init__.py +5 -0
- pakt/web/app.py +991 -0
- pakt/web/templates/index.html +2327 -0
- pakt-0.2.1.dist-info/METADATA +207 -0
- pakt-0.2.1.dist-info/RECORD +20 -0
- pakt-0.2.1.dist-info/WHEEL +4 -0
- pakt-0.2.1.dist-info/entry_points.txt +2 -0
pakt/scheduler.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Scheduler for automatic sync operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
8
|
+
|
|
9
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
10
|
+
from apscheduler.triggers.interval import IntervalTrigger
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from pakt.config import Config
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SyncScheduler:
|
|
19
|
+
"""Manages scheduled sync operations."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
config: Config,
|
|
24
|
+
sync_func: Callable[[], Any],
|
|
25
|
+
is_running_func: Callable[[], bool],
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Initialize the scheduler.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
config: Application configuration
|
|
31
|
+
sync_func: Async function to run sync
|
|
32
|
+
is_running_func: Function to check if sync is currently running
|
|
33
|
+
"""
|
|
34
|
+
self.config = config
|
|
35
|
+
self.sync_func = sync_func
|
|
36
|
+
self.is_running_func = is_running_func
|
|
37
|
+
self.scheduler = AsyncIOScheduler()
|
|
38
|
+
self._job_id = "pakt_sync"
|
|
39
|
+
self._last_run: datetime | None = None
|
|
40
|
+
self._next_run: datetime | None = None
|
|
41
|
+
|
|
42
|
+
def start(self) -> None:
|
|
43
|
+
"""Start the scheduler if enabled in config."""
|
|
44
|
+
if not self.config.scheduler.enabled:
|
|
45
|
+
logger.info("Scheduler disabled in config")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
if self.config.scheduler.interval_hours <= 0:
|
|
49
|
+
logger.warning("Scheduler interval must be > 0, scheduler disabled")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
self._add_job()
|
|
53
|
+
self.scheduler.start()
|
|
54
|
+
logger.info(
|
|
55
|
+
f"Scheduler started with {self.config.scheduler.interval_hours}h interval"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def stop(self) -> None:
|
|
59
|
+
"""Stop the scheduler."""
|
|
60
|
+
if self.scheduler.running:
|
|
61
|
+
self.scheduler.shutdown(wait=False)
|
|
62
|
+
logger.info("Scheduler stopped")
|
|
63
|
+
|
|
64
|
+
def _add_job(self) -> None:
|
|
65
|
+
"""Add or update the sync job."""
|
|
66
|
+
trigger = IntervalTrigger(hours=self.config.scheduler.interval_hours)
|
|
67
|
+
|
|
68
|
+
if self.scheduler.get_job(self._job_id):
|
|
69
|
+
self.scheduler.reschedule_job(self._job_id, trigger=trigger)
|
|
70
|
+
else:
|
|
71
|
+
self.scheduler.add_job(
|
|
72
|
+
self._run_sync,
|
|
73
|
+
trigger=trigger,
|
|
74
|
+
id=self._job_id,
|
|
75
|
+
name="Pakt Sync",
|
|
76
|
+
replace_existing=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
job = self.scheduler.get_job(self._job_id)
|
|
80
|
+
if job:
|
|
81
|
+
self._next_run = job.next_run_time
|
|
82
|
+
|
|
83
|
+
async def _run_sync(self) -> None:
|
|
84
|
+
"""Execute the sync if not already running."""
|
|
85
|
+
if self.is_running_func():
|
|
86
|
+
logger.info("Scheduled sync skipped - sync already running")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
logger.info("Starting scheduled sync")
|
|
90
|
+
self._last_run = datetime.now()
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
await self.sync_func()
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Scheduled sync failed: {e}")
|
|
96
|
+
finally:
|
|
97
|
+
job = self.scheduler.get_job(self._job_id)
|
|
98
|
+
if job:
|
|
99
|
+
self._next_run = job.next_run_time
|
|
100
|
+
|
|
101
|
+
def update_config(self, enabled: bool, interval_hours: int) -> None:
|
|
102
|
+
"""Update scheduler configuration.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
enabled: Whether scheduler should be enabled
|
|
106
|
+
interval_hours: Hours between syncs
|
|
107
|
+
"""
|
|
108
|
+
self.config.scheduler.enabled = enabled
|
|
109
|
+
self.config.scheduler.interval_hours = interval_hours
|
|
110
|
+
|
|
111
|
+
if not enabled:
|
|
112
|
+
if self.scheduler.get_job(self._job_id):
|
|
113
|
+
self.scheduler.remove_job(self._job_id)
|
|
114
|
+
self._next_run = None
|
|
115
|
+
logger.info("Scheduler disabled")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
if interval_hours <= 0:
|
|
119
|
+
logger.warning("Invalid interval, scheduler disabled")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if not self.scheduler.running:
|
|
123
|
+
self._add_job()
|
|
124
|
+
self.scheduler.start()
|
|
125
|
+
else:
|
|
126
|
+
self._add_job()
|
|
127
|
+
|
|
128
|
+
logger.info(f"Scheduler updated: {interval_hours}h interval")
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def next_run(self) -> datetime | None:
|
|
132
|
+
"""Get the next scheduled run time."""
|
|
133
|
+
job = self.scheduler.get_job(self._job_id)
|
|
134
|
+
return job.next_run_time if job else None
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def last_run(self) -> datetime | None:
|
|
138
|
+
"""Get the last run time."""
|
|
139
|
+
return self._last_run
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def is_enabled(self) -> bool:
|
|
143
|
+
"""Check if scheduler is enabled and running."""
|
|
144
|
+
return self.scheduler.running and self.scheduler.get_job(self._job_id) is not None
|
|
145
|
+
|
|
146
|
+
def get_status(self) -> dict[str, Any]:
|
|
147
|
+
"""Get scheduler status."""
|
|
148
|
+
return {
|
|
149
|
+
"enabled": self.is_enabled,
|
|
150
|
+
"interval_hours": self.config.scheduler.interval_hours,
|
|
151
|
+
"next_run": self.next_run.isoformat() if self.next_run else None,
|
|
152
|
+
"last_run": self._last_run.isoformat() if self._last_run else None,
|
|
153
|
+
}
|