Единое окно: полное пользовательское удаление загрузки (delete)
Вторая половина «единого окна»: команда «Удалить» снимает наши библиотечные хардлинки (гард последней копии осознанно выключен, в отличие от Undo) и сносит раздачу с файлами из qBittorrent (deleteFiles=true) → терминальный deleted. Доступна из done/orphaned/target_missing, идемпотентна к отсутствующей стороне; инициатор различается через error_code=user_delete. Подтверждение обязательно: веб — danger-секция внизу страницы (hx-confirm + details), Telegram — двухшаговый inline-confirm. qbt.Delete + layout.Remove (unlink без ErrLastCopy, только свои ссылки под movies/series). Граф переходов не менялся — рёбра уже были. OpenSpec: state-reconciliation +1 требование; синк workflow.md; беклог закрыт. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -434,6 +434,34 @@ func (l *Layouter) Undo(ctx context.Context, links []Link) (int, error) {
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
// Remove снимает целевые ссылки БЕЗ гарда последней копии — в отличие от Undo,
|
||||
// который отказывается стирать единственную копию данных. Служит осознанному
|
||||
// удалению загрузки («освободить место»): снимает последнюю библиотечную ссылку,
|
||||
// даже если источник уже пропал. Как и Undo, трогает только пути строго под
|
||||
// библиотеками (источник недосягаем) и идемпотентен к отсутствующей цели.
|
||||
// Возвращает число удалённых ссылок.
|
||||
func (l *Layouter) Remove(ctx context.Context, links []Link) (int, error) {
|
||||
log := logctx.FromOr(ctx, l.log)
|
||||
removed := 0
|
||||
for _, ln := range links {
|
||||
root, err := undoRoot(l, ln.Dst)
|
||||
if err != nil {
|
||||
return removed, err
|
||||
}
|
||||
if err := os.Remove(ln.Dst); err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
continue // цели уже нет — снимать нечего (идемпотентно)
|
||||
}
|
||||
log.Error("layout remove failed", "dst", ln.Dst, "error", err)
|
||||
return removed, fmt.Errorf("layout: remove %q: %w", ln.Dst, err)
|
||||
}
|
||||
removed++
|
||||
log.Debug("layout link removed (delete)", "dst", ln.Dst)
|
||||
pruneEmptyDirs(filepath.Dir(ln.Dst), root)
|
||||
}
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
// undoRoot возвращает корень библиотеки, под которым лежит dst, либо ошибку,
|
||||
// если путь не под movies/series (откат трогает только библиотеку).
|
||||
func undoRoot(l *Layouter, dst string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user