forked from IsmaeelAkram/icloud-linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsync.py
More file actions
128 lines (106 loc) · 4.19 KB
/
Copy pathsync.py
File metadata and controls
128 lines (106 loc) · 4.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
"""
sync.py — trigger a one-shot on-demand iCloud sync in the running driver.
Sends SIGUSR1 to the running icloud.service process, then waits for the
completion marker written by the driver when the crawl finishes. Exits
when the sync is done or if it times out.
Usage:
./icloudctl sync [--timeout SECONDS] [--quiet]
.venv/bin/python sync.py [--timeout SECONDS] [--quiet]
Exit codes:
0 Sync completed successfully
1 Sync timed out or driver did not respond
2 Could not locate running driver process
"""
import argparse
import os
import subprocess
import sys
import time
STATE_DIR = os.path.expanduser("~/.local/state/icloud-linux")
MARKER_FILE = os.path.join(STATE_DIR, "sync_done")
DEFAULT_TIMEOUT = 900 # seconds (15 min — iCloud crawl of 19k+ entries takes several minutes)
def get_driver_pid():
"""Return PID of the running icloud.service process, or None."""
try:
result = subprocess.run(
["systemctl", "--user", "show", "icloud.service", "--property=MainPID", "--value"],
capture_output=True,
text=True,
check=True,
)
pid_str = result.stdout.strip()
pid = int(pid_str)
return pid if pid > 0 else None
except (subprocess.CalledProcessError, ValueError):
return None
def send_sigusr1(pid):
"""Send SIGUSR1 to the driver process."""
import signal
os.kill(pid, signal.SIGUSR1)
def main():
parser = argparse.ArgumentParser(
description="Trigger an on-demand iCloud sync in the running driver."
)
parser.add_argument(
"--timeout",
type=int,
default=DEFAULT_TIMEOUT,
help=f"Seconds to wait for sync completion (default: {DEFAULT_TIMEOUT})",
)
parser.add_argument(
"--quiet",
action="store_true",
help="Suppress progress output.",
)
args = parser.parse_args()
def log(msg):
if not args.quiet:
print(msg, flush=True)
# ── find driver ──────────────────────────────────────────────────────────
pid = get_driver_pid()
if pid is None:
print("ERROR: icloud.service is not running. Start it with: icloudctl start", file=sys.stderr)
sys.exit(2)
log(f"icloud.service PID: {pid}")
# ── remove stale marker so we can detect a fresh completion ─────────────
try:
os.remove(MARKER_FILE)
except FileNotFoundError:
pass
# ── send signal ──────────────────────────────────────────────────────────
log("Sending sync signal to driver...")
try:
send_sigusr1(pid)
except ProcessLookupError:
print("ERROR: Driver process not found (PID may be stale).", file=sys.stderr)
sys.exit(2)
except PermissionError:
print("ERROR: Permission denied sending signal to driver.", file=sys.stderr)
sys.exit(2)
# ── wait for completion marker ───────────────────────────────────────────
log(f"Waiting for sync to complete (timeout: {args.timeout}s)...")
deadline = time.time() + args.timeout
poll_interval = 2
while time.time() < deadline:
if os.path.exists(MARKER_FILE):
# Read the timestamp inside to confirm it's a fresh marker
try:
with open(MARKER_FILE) as fh:
ts = float(fh.read().strip())
# Allow 5s clock skew
if ts >= time.time() - (args.timeout + 5):
elapsed = int(time.time() - (deadline - args.timeout))
log(f"Sync complete in ~{elapsed}s.")
sys.exit(0)
except (ValueError, OSError):
pass
time.sleep(poll_interval)
print(
f"ERROR: Sync did not complete within {args.timeout}s. "
"Check logs with: icloudctl logs",
file=sys.stderr,
)
sys.exit(1)
if __name__ == "__main__":
main()