Skip to content

Commit ca1a075

Browse files
committed
fix: fixed AwClient initialization (respect --testing), misc cleanup
1 parent de461a9 commit ca1a075

File tree

1 file changed

+54
-36
lines changed

1 file changed

+54
-36
lines changed

aw_notify/main.py

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
and send notifications to the user on predefined conditions.
44
"""
55
import logging
6-
import platform
6+
import sys
77
import threading
88
from collections import defaultdict
99
from datetime import datetime, timedelta, timezone
@@ -23,20 +23,38 @@
2323
from desktop_notifier import DesktopNotifier
2424
from typing_extensions import TypeAlias
2525

26-
# TODO: Add thresholds for total time today (incl percentage of productive time)
27-
2826
logger = logging.getLogger(__name__)
2927

28+
# Types
29+
AwClient = aw_client.ActivityWatchClient
30+
CacheKey: TypeAlias = tuple
31+
32+
# Constants
33+
# TODO: Add thresholds for total time today (incl percentage of productive time)
3034
# TODO: read from server settings
3135
TIME_OFFSET = timedelta(hours=4)
3236

33-
aw = aw_client.ActivityWatchClient("aw-notify", testing=False)
37+
td15min = timedelta(minutes=15)
38+
td30min = timedelta(minutes=30)
39+
td1h = timedelta(hours=1)
40+
td2h = timedelta(hours=2)
41+
td6h = timedelta(hours=6)
42+
td4h = timedelta(hours=4)
43+
td8h = timedelta(hours=8)
44+
45+
# global objects
46+
# will init in entrypoints
47+
aw: Optional[AwClient] = None
48+
notifier: Optional[DesktopNotifier] = None
49+
50+
# executable path
51+
script_dir = Path(__file__).parent.absolute()
52+
icon_path = script_dir / ".." / "media" / "logo" / "logo.png"
3453

3554

3655
def cache_ttl(ttl: Union[timedelta, int]):
3756
"""Decorator that caches the result of a function in-memory, with a given time-to-live."""
3857
T = TypeVar("T")
39-
CacheKey: TypeAlias = tuple
4058

4159
_ttl: timedelta = ttl if isinstance(ttl, timedelta) else timedelta(seconds=ttl)
4260

@@ -66,6 +84,7 @@ def get_time(date=None, top_level_only=True) -> dict[str, timedelta]:
6684
"""
6785
Returns a dict with the time spent today (or for `date`) for each category.
6886
"""
87+
assert aw
6988

7089
if date is None:
7190
date = datetime.now(timezone.utc)
@@ -120,16 +139,8 @@ def to_hms(duration: timedelta) -> str:
120139
return s.strip()
121140

122141

123-
notifier: DesktopNotifier = None
124-
125-
# executable path
126-
127-
script_dir = Path(__file__).parent.absolute()
128-
icon_path = script_dir / ".." / "media" / "logo" / "logo.png"
129-
130-
131142
def notify(title: str, msg: str):
132-
# send a notification to the user
143+
"""send a notification to the user"""
133144

134145
global notifier
135146
if notifier is None:
@@ -143,15 +154,6 @@ def notify(title: str, msg: str):
143154
notifier.send_sync(title=title, message=msg)
144155

145156

146-
td15min = timedelta(minutes=15)
147-
td30min = timedelta(minutes=30)
148-
td1h = timedelta(hours=1)
149-
td2h = timedelta(hours=2)
150-
td6h = timedelta(hours=6)
151-
td4h = timedelta(hours=4)
152-
td8h = timedelta(hours=8)
153-
154-
155157
class CategoryAlert:
156158
"""
157159
Alerts for a category.
@@ -242,6 +244,19 @@ def test_category_alert():
242244
catalert.check()
243245

244246

247+
def init_macos():
248+
# set NSBundle on macOS, needed for desktop-notifier
249+
from rubicon.objc import ObjCClass
250+
251+
logger.info("Setting NSBundle for macOS")
252+
253+
# needs to be the same as the one in the PyInstaller config (later Info.plist)
254+
# could also be done by just monkey-patching the helper function is_signed_bundle in desktop-notifier
255+
# see: https://github.com/samschott/desktop-notifier/issues/115
256+
NSBundle = ObjCClass("NSBundle")
257+
NSBundle.mainBundle.bundleIdentifier = "net.activitywatch.ActivityWatch"
258+
259+
245260
@click.group(invoke_without_command=True)
246261
@click.pass_context
247262
@click.option("-v", "--verbose", is_flag=True, help="Verbose logging.")
@@ -251,23 +266,20 @@ def main(ctx, verbose: bool, testing: bool):
251266
logging.getLogger("urllib3").setLevel(logging.WARNING)
252267
logger.info("Starting...")
253268

254-
# set NSBundle on macOS, needed for desktop-notifier
255-
if platform.system() == "Darwin":
256-
from rubicon.objc import ObjCClass
257-
258-
# needs to be the same as the one in the PyInstaller config (later Info.plist)
259-
# could also be done by just monkey-patching the helper function is_signed_bundle in desktop-notifier
260-
# see: https://github.com/samschott/desktop-notifier/issues/115
261-
NSBundle = ObjCClass("NSBundle")
262-
NSBundle.mainBundle.bundleIdentifier = "net.activitywatch.ActivityWatch"
269+
if sys.platform == "darwin":
270+
init_macos()
263271

264272
if ctx.invoked_subcommand is None:
265-
ctx.invoke(start)
273+
ctx.invoke(start, testing=testing)
266274

267275

268276
@main.command()
269-
def start():
277+
@click.option("--testing", is_flag=True, help="Enables testing mode.")
278+
def start(testing=False):
270279
"""Start the notification service."""
280+
global aw
281+
aw = aw_client.ActivityWatchClient("aw-notify", testing=testing)
282+
271283
send_checkin()
272284
send_checkin_yesterday()
273285
start_hourly()
@@ -301,14 +313,19 @@ def threshold_alerts():
301313
status = alert.status()
302314
if status != getattr(alert, "last_status", None):
303315
logger.debug(f"New status: {status}")
304-
alert.last_status = status
316+
setattr(alert, "last_status", status)
305317

318+
# TODO: make configurable, perhaps increase default to save resources
306319
sleep(10)
307320

308321

309322
@main.command()
310-
def checkin():
323+
@click.option("--testing", is_flag=True, help="Enables testing mode.")
324+
def checkin(testing=False):
311325
"""Send a summary notification."""
326+
global aw
327+
aw = aw_client.ActivityWatchClient("aw-notify-checkin", testing=testing)
328+
312329
send_checkin()
313330

314331

@@ -347,6 +364,7 @@ def get_active_status() -> Union[bool, None]:
347364
Returns True if user is active/not-afk, False if not.
348365
On error, like out-of-date event, returns None.
349366
"""
367+
assert aw
350368

351369
hostname = aw.get_info().get("hostname", "unknown")
352370
events = aw.get_events(f"aw-watcher-afk_{hostname}", limit=1)

0 commit comments

Comments
 (0)