move to anther profile

This commit is contained in:
Mateusz Gruszczyński
2026-06-20 17:01:48 +02:00
parent e6733d6a27
commit fc03b7755b
12 changed files with 201 additions and 14 deletions
+4 -1
View File
@@ -408,7 +408,8 @@ def _automation_profile_transfer_payload(profile: dict[str, Any], eff: dict[str,
if not target_profile:
raise ValueError('Automation target profile does not exist')
default_path = _safe_remote_path(rtorrent.default_download_path(target_profile))
target_path = _safe_remote_path(str(eff.get('target_path') or eff.get('path') or default_path))
requested_target_path = _safe_remote_path(str(eff.get('target_path') or eff.get('path') or ''))
target_path = requested_target_path or default_path
roots = [default_path]
try:
prefs = get_disk_monitor_preferences(target_id, user_id=user_id)
@@ -423,6 +424,8 @@ def _automation_profile_transfer_payload(profile: dict[str, Any], eff: dict[str,
pass
target_roots = [r for r in roots if r]
if not any(_path_inside_root(target_path, root) for root in target_roots):
if requested_target_path:
raise ValueError('Automation target path is outside the target profile download roots')
target_path = default_path
requested_move_data = bool(eff.get('move_data'))
move_data = False
+74
View File
@@ -577,3 +577,77 @@ def save_preferences(data: dict, user_id: int | None = None, profile_id: int | N
if disk_payload is not None:
save_disk_monitor_preferences(profile_id, disk_payload, user_id)
return get_preferences(user_id, profile_id)
def _row_int(row: dict, key: str) -> int:
try:
return int(float(row.get(key) or 0))
except (TypeError, ValueError):
return 0
def profile_runtime_stats_from_rows(profile: dict, rows: list[dict], user_id: int | None = None) -> dict:
# Note: Stored profile stats are intentionally approximate and updated only when the user switches to that profile.
user_id = user_id or auth.current_user_id() or default_user_id()
total_size = completed = downloaded = uploaded = active = seeding = downloading = stopped = 0
for row in rows or []:
size = _row_int(row, 'size')
total_size += size
completed += min(size, _row_int(row, 'completed_bytes')) if size else _row_int(row, 'completed_bytes')
downloaded += _row_int(row, 'down_total')
uploaded += _row_int(row, 'up_total')
status = str(row.get('status') or '').strip().lower()
state = bool(row.get('state'))
complete = bool(row.get('complete'))
if state:
active += 1
if complete and state:
seeding += 1
if not complete and state and status != 'queued':
downloading += 1
if not state:
stopped += 1
return {
'profile_id': int(profile.get('id') or 0),
'user_id': int(user_id),
'torrent_count': len(rows or []),
'total_size_bytes': total_size,
'completed_bytes': completed,
'downloaded_bytes': downloaded,
'uploaded_bytes': uploaded,
'active_count': active,
'seeding_count': seeding,
'downloading_count': downloading,
'stopped_count': stopped,
'updated_at': utcnow(),
}
def save_profile_runtime_stats(profile: dict, rows: list[dict], user_id: int | None = None) -> dict:
stats = profile_runtime_stats_from_rows(profile, rows, user_id=user_id)
with connect() as conn:
conn.execute(
"""
INSERT INTO profile_runtime_stats(
profile_id,user_id,torrent_count,total_size_bytes,completed_bytes,downloaded_bytes,uploaded_bytes,
active_count,seeding_count,downloading_count,stopped_count,updated_at
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(profile_id) DO UPDATE SET
user_id=excluded.user_id, torrent_count=excluded.torrent_count, total_size_bytes=excluded.total_size_bytes,
completed_bytes=excluded.completed_bytes, downloaded_bytes=excluded.downloaded_bytes, uploaded_bytes=excluded.uploaded_bytes,
active_count=excluded.active_count, seeding_count=excluded.seeding_count, downloading_count=excluded.downloading_count,
stopped_count=excluded.stopped_count, updated_at=excluded.updated_at
""",
(
stats['profile_id'], stats['user_id'], stats['torrent_count'], stats['total_size_bytes'], stats['completed_bytes'],
stats['downloaded_bytes'], stats['uploaded_bytes'], stats['active_count'], stats['seeding_count'],
stats['downloading_count'], stats['stopped_count'], stats['updated_at'],
),
)
return stats
def get_profile_runtime_stats(profile_id: int) -> dict | None:
with connect() as conn:
row = conn.execute("SELECT * FROM profile_runtime_stats WHERE profile_id=?", (int(profile_id),)).fetchone()
return dict(row) if row else None
+6 -5
View File
@@ -289,6 +289,12 @@ def _emit_disk_refresh_requested(profile_id: int, action_name: str, payload: dic
_schedule_profile_disk_refresh(int(profile_id), len((payload or {}).get("hashes") or []))
def _execute(profile: dict, action_name: str, payload: dict, user_id: int | None = None):
def checkpoint(next_state: dict, current: int, total: int):
# Note: Checkpoint is defined before every action branch so profile-transfer jobs can resume safely.
job_id = payload.get("__job_id")
if job_id:
_checkpoint_job(str(job_id), next_state, current, total)
if action_name == "smart_queue_check":
from . import smart_queue
return smart_queue.check(profile, user_id=user_id or default_user_id(), force=True)
@@ -315,11 +321,6 @@ def _execute(profile: dict, action_name: str, payload: dict, user_id: int | None
disk_guard.assert_can_start_download(profile)
state = payload.get("__resume_state") or {}
def checkpoint(next_state: dict, current: int, total: int):
job_id = payload.get("__job_id")
if job_id:
_checkpoint_job(str(job_id), next_state, current, total)
return rtorrent.action(profile, hashes, action_name, payload, checkpoint=checkpoint, resume_state=state)