more conditions in automations
This commit is contained in:
@@ -137,8 +137,11 @@ def _apply_effects(c: Any, profile: dict[str, Any], torrent: dict[str, Any], eff
|
|||||||
for eff in effects:
|
for eff in effects:
|
||||||
typ = str(eff.get('type') or '')
|
typ = str(eff.get('type') or '')
|
||||||
if typ == 'move':
|
if typ == 'move':
|
||||||
|
# Note: Automation move-to-path now uses the same move implementation as the main app action.
|
||||||
path = str(eff.get('path') or '').strip() or rtorrent.default_download_path(profile)
|
path = str(eff.get('path') or '').strip() or rtorrent.default_download_path(profile)
|
||||||
if path: c.call('d.directory.set', h, path); applied.append({'type': 'move', 'path': path})
|
move_payload = {'path': path, 'move_data': bool(eff.get('move_data')), 'recheck': bool(eff.get('recheck', eff.get('move_data'))), 'keep_seeding': bool(eff.get('keep_seeding'))}
|
||||||
|
result = rtorrent.move_torrents(profile, [h], move_payload) if path else None
|
||||||
|
if path: applied.append({'type': 'move', 'path': path, 'move_data': bool(eff.get('move_data')), 'recheck': bool(move_payload['recheck']), 'keep_seeding': bool(eff.get('keep_seeding')), 'result': result})
|
||||||
elif typ == 'add_label':
|
elif typ == 'add_label':
|
||||||
label = str(eff.get('label') or '').strip()
|
label = str(eff.get('label') or '').strip()
|
||||||
if label and label not in labels: labels.append(label); c.call('d.custom1.set', h, _label_value(labels))
|
if label and label not in labels: labels.append(label); c.call('d.custom1.set', h, _label_value(labels))
|
||||||
|
|||||||
@@ -1274,6 +1274,73 @@ def start_or_resume_hash(c: ScgiRtorrentClient, torrent_hash: str) -> dict:
|
|||||||
result['ok'] = result.get('ok', True)
|
result['ok'] = result.get('ok', True)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def move_torrents(profile: dict, torrent_hashes: list[str], payload: dict | None = None) -> dict:
|
||||||
|
# Note: Shared move implementation keeps API move and automation move-to-path identical.
|
||||||
|
payload = payload or {}
|
||||||
|
c = client_for(profile)
|
||||||
|
path = _remote_clean_path(payload.get("path") or "")
|
||||||
|
move_data = bool(payload.get("move_data"))
|
||||||
|
recheck = bool(payload.get("recheck", move_data))
|
||||||
|
keep_seeding = bool(payload.get("keep_seeding"))
|
||||||
|
# Note: keep_seeding lets automation move completed data to another path and force the torrent back into seeding.
|
||||||
|
if not path:
|
||||||
|
raise ValueError("Missing path")
|
||||||
|
results = []
|
||||||
|
if move_data:
|
||||||
|
_rt_execute_allow_timeout(c, "execute.throw", "mkdir", "-p", path)
|
||||||
|
for h in torrent_hashes:
|
||||||
|
item = {"hash": h, "path": path, "move_data": move_data, "keep_seeding": keep_seeding}
|
||||||
|
try:
|
||||||
|
was_state = int(c.call("d.state", h) or 0)
|
||||||
|
except Exception:
|
||||||
|
was_state = 0
|
||||||
|
try:
|
||||||
|
was_active = int(c.call("d.is_active", h) or 0)
|
||||||
|
except Exception:
|
||||||
|
was_active = was_state
|
||||||
|
if move_data:
|
||||||
|
src = _remote_clean_path(_torrent_data_path(c, h))
|
||||||
|
if not src:
|
||||||
|
raise ValueError(f"Cannot determine source path for {h}")
|
||||||
|
dst = _remote_join(path, posixpath.basename(src.rstrip("/")))
|
||||||
|
if src != dst:
|
||||||
|
try:
|
||||||
|
c.call("d.stop", h)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
c.call("d.close", h)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_run_remote_move(c, src, dst)
|
||||||
|
item["moved_from"] = src
|
||||||
|
item["moved_to"] = dst
|
||||||
|
else:
|
||||||
|
item["skipped"] = "source and destination are the same"
|
||||||
|
c.call("d.directory.set", h, path)
|
||||||
|
if recheck:
|
||||||
|
try:
|
||||||
|
c.call("d.check_hash", h)
|
||||||
|
except Exception as exc:
|
||||||
|
item["recheck_error"] = str(exc)
|
||||||
|
if keep_seeding or was_state or was_active:
|
||||||
|
try:
|
||||||
|
c.call("d.start", h)
|
||||||
|
item["started_after_move"] = True
|
||||||
|
except Exception as exc:
|
||||||
|
item["start_error"] = str(exc)
|
||||||
|
else:
|
||||||
|
c.call("d.directory.set", h, path)
|
||||||
|
if keep_seeding:
|
||||||
|
try:
|
||||||
|
c.call("d.start", h)
|
||||||
|
item["started_after_path_change"] = True
|
||||||
|
except Exception as exc:
|
||||||
|
item["start_error"] = str(exc)
|
||||||
|
results.append(item)
|
||||||
|
return {"ok": True, "count": len(torrent_hashes), "move_data": move_data, "keep_seeding": keep_seeding, "results": results}
|
||||||
|
|
||||||
def action(profile: dict, torrent_hashes: list[str], name: str, payload: dict | None = None) -> dict:
|
def action(profile: dict, torrent_hashes: list[str], name: str, payload: dict | None = None) -> dict:
|
||||||
payload = payload or {}
|
payload = payload or {}
|
||||||
c = client_for(profile)
|
c = client_for(profile)
|
||||||
@@ -1294,58 +1361,8 @@ def action(profile: dict, torrent_hashes: list[str], name: str, payload: dict |
|
|||||||
c.call("d.custom.set", h, "py_ratio_group", group)
|
c.call("d.custom.set", h, "py_ratio_group", group)
|
||||||
return {"ok": True, "count": len(torrent_hashes), "ratio_group": group}
|
return {"ok": True, "count": len(torrent_hashes), "ratio_group": group}
|
||||||
if name == "move":
|
if name == "move":
|
||||||
path = _remote_clean_path(payload.get("path") or "")
|
# Note: Main move delegates to the shared helper used by automations.
|
||||||
move_data = bool(payload.get("move_data"))
|
return move_torrents(profile, torrent_hashes, payload)
|
||||||
recheck = bool(payload.get("recheck", move_data))
|
|
||||||
if not path:
|
|
||||||
raise ValueError("Missing path")
|
|
||||||
results = []
|
|
||||||
if move_data:
|
|
||||||
_rt_execute_allow_timeout(c, "execute.throw", "mkdir", "-p", path)
|
|
||||||
for h in torrent_hashes:
|
|
||||||
item = {"hash": h, "path": path, "move_data": move_data}
|
|
||||||
try:
|
|
||||||
was_state = int(c.call("d.state", h) or 0)
|
|
||||||
except Exception:
|
|
||||||
was_state = 0
|
|
||||||
try:
|
|
||||||
was_active = int(c.call("d.is_active", h) or 0)
|
|
||||||
except Exception:
|
|
||||||
was_active = was_state
|
|
||||||
if move_data:
|
|
||||||
src = _remote_clean_path(_torrent_data_path(c, h))
|
|
||||||
if not src:
|
|
||||||
raise ValueError(f"Cannot determine source path for {h}")
|
|
||||||
dst = _remote_join(path, posixpath.basename(src.rstrip("/")))
|
|
||||||
if src != dst:
|
|
||||||
try:
|
|
||||||
c.call("d.stop", h)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
c.call("d.close", h)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
_run_remote_move(c, src, dst)
|
|
||||||
item["moved_from"] = src
|
|
||||||
item["moved_to"] = dst
|
|
||||||
else:
|
|
||||||
item["skipped"] = "source and destination are the same"
|
|
||||||
c.call("d.directory.set", h, path)
|
|
||||||
if recheck:
|
|
||||||
try:
|
|
||||||
c.call("d.check_hash", h)
|
|
||||||
except Exception as exc:
|
|
||||||
item["recheck_error"] = str(exc)
|
|
||||||
if was_state or was_active:
|
|
||||||
try:
|
|
||||||
c.call("d.start", h)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
c.call("d.directory.set", h, path)
|
|
||||||
results.append(item)
|
|
||||||
return {"ok": True, "count": len(torrent_hashes), "move_data": move_data, "results": results}
|
|
||||||
if name == "pause":
|
if name == "pause":
|
||||||
# Note: The app pause action is now a pure d.pause so later resume works without stop/start.
|
# Note: The app pause action is now a pure d.pause so later resume works without stop/start.
|
||||||
results = [pause_hash(c, h) for h in torrent_hashes]
|
results = [pause_hash(c, h) for h in torrent_hashes]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1244,6 +1244,30 @@ body.mobile-mode .mobile-card {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-move-option {
|
||||||
|
gap: 0.45rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.automation-builder-list {
|
||||||
|
display: grid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.automation-chip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.4rem 0.55rem;
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
border-radius: 0.55rem;
|
||||||
|
background: var(--bs-secondary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.automation-row {
|
.automation-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user