snapshot of https://github.com/ActivityWatch/aw-client.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 2m4s
Examples / Test examples (ubuntu-latest, 3.10) (push) Successful in 2m16s
Lint / lint (push) Successful in 1m25s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:20:39 +05:00
commit 96755ecd21
36 changed files with 5338 additions and 0 deletions

206
aw_client/config.py Normal file
View File

@@ -0,0 +1,206 @@
import logging
import os
from typing import List, Optional, Tuple, Union
import platformdirs
import tomlkit
from aw_core.config import load_config_toml
from .profile import DEFAULT_PROFILE, TESTING_PROFILE, profile_from_env
logger = logging.getLogger(__name__)
_DEFAULT_APPNAME = "activitywatch"
_TESTING_APPNAME = "activitywatch-testing"
# Identical to aw-core#152 / aw-server-rust#652 so python and rust agree on
# the same on-disk state (ActivityWatch/activitywatch#1399). Keep this list
# specific: a false positive would pin a fresh install to legacy forever.
_LEGACY_TESTING_FILENAME_MARKERS = (
"peewee-sqlite-testing",
"sqlite-testing",
"settings-testing",
"config-testing",
"-testing.db",
"-testing.toml",
"-testing.json",
"_testing_",
)
default_config = """
[server]
hostname = "127.0.0.1"
port = "5600"
[client]
commit_interval = 10
[server-testing]
hostname = "127.0.0.1"
port = "5666"
[client-testing]
commit_interval = 5
""".strip()
def load_config():
return load_config_toml("aw-client", default_config)
def _user_data_dir(appname: str) -> str:
return platformdirs.user_data_dir(appname)
def _user_config_dir(appname: str) -> str:
return platformdirs.user_config_dir(appname)
def _user_cache_dir(appname: str) -> str:
return platformdirs.user_cache_dir(appname)
def _is_legacy_testing_filename(name: str) -> bool:
lower = name.lower()
return any(marker in lower for marker in _LEGACY_TESTING_FILENAME_MARKERS)
def _new_testing_root_exists() -> bool:
"""True if any platform dir for ``activitywatch-testing`` already exists.
Must not create directories: existence is the signal that a previous run
already adopted the isolated testing root.
"""
for getter in (_user_data_dir, _user_config_dir, _user_cache_dir):
if os.path.isdir(getter(_TESTING_APPNAME)):
return True
return False
def _legacy_testing_artifacts_exist() -> bool:
"""True if testing data still lives under the shared ``activitywatch`` root."""
roots = (
_user_data_dir(_DEFAULT_APPNAME),
_user_config_dir(_DEFAULT_APPNAME),
_user_cache_dir(_DEFAULT_APPNAME),
)
for root in roots:
if not os.path.isdir(root):
continue
for dirpath, dirnames, filenames in os.walk(root):
for filename in filenames:
if _is_legacy_testing_filename(filename):
return True
# Descend one level (activitywatch/aw-server-rust/...) not further.
if os.path.relpath(dirpath, root) != ".":
dirnames.clear()
return False
def using_legacy_testing_root() -> bool:
"""Whether ``profile=testing`` should stay on the shared ``activitywatch`` root.
Resolution rule (ActivityWatch/activitywatch#1399), identical on python
and rust:
1. If ``activitywatch-testing/`` already exists: use it (new layout).
2. Else if legacy testing artifacts exist in the bare ``activitywatch/``
root: stay in legacy mode (old paths, old filenames).
3. Else (fresh setup): create and use ``activitywatch-testing/``.
"""
if _new_testing_root_exists():
return False
return _legacy_testing_artifacts_exist()
def rust_server_config_candidates(profile: str) -> List[Tuple[str, int]]:
"""Ordered rust config files to read for this profile.
Isolated profile roots (including new-style ``activitywatch-testing/``)
use bare ``config.toml`` — the directory already isolates. Suffixed
``config-testing.toml`` remains only in the legacy shared-root layout.
Lookup does **not** create directories (``get_config_dir`` would, and
creating ``activitywatch-testing/`` would flip the fallback). Testing
tries the other layout second so a python client that already created
the new root still finds a rust server that wrote the key on the
shared root — the regression Erik named on #1399.
"""
testing_new = (
os.path.join(
_user_config_dir(_TESTING_APPNAME), "aw-server-rust", "config.toml"
),
5666,
)
testing_legacy = (
os.path.join(
_user_config_dir(_DEFAULT_APPNAME),
"aw-server-rust",
"config-testing.toml",
),
5666,
)
if profile == TESTING_PROFILE:
if using_legacy_testing_root():
return [testing_legacy, testing_new]
return [testing_new, testing_legacy]
if profile == DEFAULT_PROFILE or not profile:
return [
(
os.path.join(
_user_config_dir(_DEFAULT_APPNAME),
"aw-server-rust",
"config.toml",
),
5600,
)
]
return [
(
os.path.join(
_user_config_dir(f"{_DEFAULT_APPNAME}-{profile}"),
"aw-server-rust",
"config.toml",
),
5600,
)
]
def load_local_server_api_key(
host: str,
port: Union[int, str],
profile: Optional[str] = None,
) -> Optional[str]:
if host not in {"127.0.0.1", "localhost", "::1"}:
return None
try:
requested_port = int(str(port))
except (TypeError, ValueError):
return None
if profile is None:
profile = profile_from_env(False)
for config_path, default_port in rust_server_config_candidates(profile):
if not os.path.isfile(config_path):
continue
try:
with open(config_path, encoding="utf-8") as f:
config = tomlkit.parse(f.read())
configured_port = int(str(config.get("port", default_port)))
if configured_port != requested_port:
continue
auth_config = config.get("auth", {})
api_key = auth_config.get("api_key")
if api_key:
return str(api_key)
except Exception as e:
logger.warning(
"Failed to read aw-server-rust config %s: %s", config_path, e
)
return None