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/tray.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""System tray integration for Pakt."""
|
|
2
|
+
# pyright: reportPossiblyUnboundVariable=false
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
import webbrowser
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Callable
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pystray
|
|
13
|
+
from PIL import Image
|
|
14
|
+
|
|
15
|
+
from pakt.scheduler import SyncScheduler
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import pystray
|
|
19
|
+
from PIL import Image
|
|
20
|
+
|
|
21
|
+
TRAY_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
TRAY_AVAILABLE = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_icon_image() -> "Image.Image":
|
|
27
|
+
"""Load icon from assets or create fallback."""
|
|
28
|
+
if not TRAY_AVAILABLE:
|
|
29
|
+
raise ImportError("PIL is required")
|
|
30
|
+
|
|
31
|
+
# Try to load from assets
|
|
32
|
+
assets_dir = Path(__file__).parent / "assets"
|
|
33
|
+
icon_path = assets_dir / "icon.png"
|
|
34
|
+
if icon_path.exists():
|
|
35
|
+
return Image.open(icon_path)
|
|
36
|
+
|
|
37
|
+
# Fallback: create simple programmatic icon
|
|
38
|
+
size = 64
|
|
39
|
+
img = Image.new("RGBA", (size, size), (45, 212, 191, 255)) # Teal background
|
|
40
|
+
return img
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PaktTray:
|
|
44
|
+
"""System tray icon for Pakt."""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
web_url: str = "http://localhost:8080",
|
|
49
|
+
sync_callback: Callable[[], None] | None = None,
|
|
50
|
+
shutdown_callback: Callable[[], None] | None = None,
|
|
51
|
+
scheduler: "SyncScheduler | None" = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Initialize the system tray.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
web_url: URL of the web interface
|
|
57
|
+
sync_callback: Function to trigger sync
|
|
58
|
+
shutdown_callback: Function to shutdown the application
|
|
59
|
+
scheduler: Scheduler instance for status display
|
|
60
|
+
"""
|
|
61
|
+
if not TRAY_AVAILABLE:
|
|
62
|
+
raise ImportError("pystray and Pillow are required for system tray support")
|
|
63
|
+
|
|
64
|
+
self.web_url = web_url
|
|
65
|
+
self.sync_callback = sync_callback
|
|
66
|
+
self.shutdown_callback = shutdown_callback
|
|
67
|
+
self.scheduler = scheduler
|
|
68
|
+
self._icon: pystray.Icon | None = None
|
|
69
|
+
self._thread: threading.Thread | None = None
|
|
70
|
+
|
|
71
|
+
def _open_web_ui(self) -> None:
|
|
72
|
+
"""Open the web UI in the default browser."""
|
|
73
|
+
webbrowser.open(self.web_url)
|
|
74
|
+
|
|
75
|
+
def _trigger_sync(self) -> None:
|
|
76
|
+
"""Trigger a sync operation."""
|
|
77
|
+
if self.sync_callback:
|
|
78
|
+
self.sync_callback()
|
|
79
|
+
|
|
80
|
+
def _exit(self) -> None:
|
|
81
|
+
"""Exit the application."""
|
|
82
|
+
if self._icon:
|
|
83
|
+
self._icon.stop()
|
|
84
|
+
if self.shutdown_callback:
|
|
85
|
+
self.shutdown_callback()
|
|
86
|
+
|
|
87
|
+
def _get_menu(self) -> "pystray.Menu":
|
|
88
|
+
"""Create the context menu."""
|
|
89
|
+
items = [
|
|
90
|
+
pystray.MenuItem("Open Web UI", self._open_web_ui, default=True),
|
|
91
|
+
pystray.MenuItem("Sync Now", self._trigger_sync),
|
|
92
|
+
pystray.Menu.SEPARATOR,
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Add next run info if scheduler is available
|
|
96
|
+
if self.scheduler and self.scheduler.is_enabled:
|
|
97
|
+
next_run = self.scheduler.next_run
|
|
98
|
+
if next_run:
|
|
99
|
+
next_str = next_run.strftime("%H:%M")
|
|
100
|
+
items.append(
|
|
101
|
+
pystray.MenuItem(f"Next sync: {next_str}", None, enabled=False)
|
|
102
|
+
)
|
|
103
|
+
items.append(pystray.Menu.SEPARATOR)
|
|
104
|
+
|
|
105
|
+
items.append(pystray.MenuItem("Exit", self._exit))
|
|
106
|
+
return pystray.Menu(*items)
|
|
107
|
+
|
|
108
|
+
def start(self) -> None:
|
|
109
|
+
"""Start the system tray icon in a background thread."""
|
|
110
|
+
if not TRAY_AVAILABLE:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
icon_image = _get_icon_image()
|
|
114
|
+
icon = pystray.Icon(
|
|
115
|
+
name="Pakt",
|
|
116
|
+
icon=icon_image,
|
|
117
|
+
title="Pakt - Plex/Trakt Sync",
|
|
118
|
+
menu=self._get_menu(),
|
|
119
|
+
)
|
|
120
|
+
self._icon = icon
|
|
121
|
+
|
|
122
|
+
def run_icon():
|
|
123
|
+
icon.run()
|
|
124
|
+
|
|
125
|
+
self._thread = threading.Thread(target=run_icon, daemon=True)
|
|
126
|
+
self._thread.start()
|
|
127
|
+
|
|
128
|
+
def stop(self) -> None:
|
|
129
|
+
"""Stop the system tray icon."""
|
|
130
|
+
if self._icon:
|
|
131
|
+
self._icon.stop()
|
|
132
|
+
self._icon = None
|
|
133
|
+
|
|
134
|
+
def update_menu(self) -> None:
|
|
135
|
+
"""Update the menu (e.g., after scheduler status changes)."""
|
|
136
|
+
if self._icon:
|
|
137
|
+
self._icon.menu = self._get_menu()
|