automatyzacje-comit6

This commit is contained in:
Mateusz Gruszczyński
2026-05-07 11:55:29 +02:00
parent 85e1e6adcd
commit 8334aa97e2
5 changed files with 130 additions and 11 deletions

View File

@@ -66,6 +66,44 @@ def get_rule(rule_id: int, profile_id: int, user_id: int | None = None) -> dict[
return _rule_row(row)
def _portable_rule(rule: dict[str, Any]) -> dict[str, Any]:
return {
'name': str(rule.get('name') or 'Automation rule'),
'enabled': bool(rule.get('enabled', True)),
'cooldown_minutes': max(0, int(rule.get('cooldown_minutes') or 0)),
'conditions': list(rule.get('conditions') or []),
'effects': list(rule.get('effects') or []),
}
def export_rules(profile_id: int, user_id: int | None = None) -> dict[str, Any]:
# Note: Export contains only portable rule definitions, never DB ids or execution history.
rules = [_portable_rule(rule) for rule in list_rules(profile_id, user_id)]
return {'version': 1, 'app': 'pyTorrent', 'exported_at': utcnow(), 'rules': rules}
def import_rules(profile_id: int, payload: dict[str, Any] | list[Any], user_id: int | None = None, replace: bool = False) -> list[dict[str, Any]]:
user_id = user_id or default_user_id()
raw_rules = payload if isinstance(payload, list) else payload.get('rules', []) if isinstance(payload, dict) else []
if not isinstance(raw_rules, list) or not raw_rules:
raise ValueError('Import file does not contain automation rules')
if replace:
with connect() as conn:
# Note: Optional replace is profile-scoped; it does not touch other profiles or history tables.
conn.execute('DELETE FROM automation_rules WHERE user_id=? AND profile_id=?', (user_id, profile_id))
conn.execute('DELETE FROM automation_rule_state WHERE profile_id=?', (profile_id,))
imported = []
for raw in raw_rules:
if not isinstance(raw, dict):
continue
rule = _portable_rule(raw)
rule.pop('id', None)
imported.append(save_rule(profile_id, rule, user_id))
if not imported:
raise ValueError('No valid automation rules found')
return imported
def save_rule(profile_id: int, data: dict[str, Any], user_id: int | None = None) -> dict[str, Any]:
user_id = user_id or default_user_id()
name = str(data.get('name') or 'Automation rule').strip() or 'Automation rule'
@@ -111,6 +149,8 @@ def _condition_true(t: dict[str, Any], cond: dict[str, Any]) -> bool:
if typ == 'completed': return bool(int(t.get('complete') or 0))
if typ == 'no_seeds': return int(t.get('seeds') or 0) <= int(cond.get('seeds') or 0)
if typ == 'ratio_gte': return float(t.get('ratio') or 0) >= float(cond.get('ratio') or 0)
if typ == 'progress_gte': return float(t.get('progress') or 0) >= float(cond.get('progress') or 0)
if typ == 'progress_lte': return float(t.get('progress') or 0) <= float(cond.get('progress') or 0)
if typ == 'label_missing': return str(cond.get('label') or '').strip() not in _label_names(t.get('label'))
if typ == 'label_has': return str(cond.get('label') or '').strip() in _label_names(t.get('label'))
if typ == 'status': return str(t.get('status') or '').lower() == str(cond.get('status') or '').lower()