import 'dart:io'; import '../core/app_trash.dart'; import '../core/file_ops.dart' show FileOps; import '../platform/windows/recycle_bin.dart'; /// 약속 (E5b): 지우기 · 되돌리기. 휴지통은 recycle_bin (Windows) · app_trash (Android) 를 부르기만 한다. /// - [deletePaths]: 입력 = 경로들 · 영구인지 · 앱 휴지통 (있으면 그리로), 출력 = [DeleteReport] (실제로 어떻게 됐는지 하나씩). /// 하나가 실패해도 나머지는 계속한다. 휴지통으로 못 보낸 것은 몰래 영구 삭제하지 않고 보고에 남긴다 /// (앱 휴지통 [DeleteReport.notTrashed] · 긴 경로 [DeleteReport.tooLong] - 영구 삭제는 부르는 쪽이 물은 뒤 [deleteForever]). /// - [deleteForever]: 영구 삭제, 출력 = 못 지운 것 (경로, 예외). /// - [undoTrash] · [undoRecycle]: 방금 휴지통으로 보낸 것을 원래 자리로, 출력 = 되살린 것 · 못 되살린 것 (이유). /// - 다시 쓰는 곳: 탐색기 · 다운로드 화면 지우기 · 보관함 · 정리. class DeleteReport { DeleteReport(this.since); /// 이 지우기를 시작한 때 (Windows 휴지통 되돌리기용 - 정보 파일 시각이 초 단위라 조금 앞) final DateTime since; /// Windows 휴지통으로 보낸 것 (되돌릴 수 있음) final sent = []; /// 앱 휴지통으로 보낸 것 · 그 경로 (되돌릴 수 있음) final trashed = []; final undoable = []; /// 앱 휴지통으로 옮기지 못한 것 (경로, 이유) - 다른 저장 장치 · 권한 등 final notTrashed = <(String, String)>[]; /// 경로가 너무 길어 휴지통이 받지 않은 것 final tooLong = []; /// 지우지 못한 것 (경로, 이유) final failed = <(String, String)>[]; /// 휴지통으로 보낸 수 · Windows 가 묻고 영구 삭제한 수 · 취소한 수 var recycled = 0, nuked = 0, kept = 0; } /// 사람이 읽을 실패 이유 (OS 메시지가 있으면 그것) String deleteFailReason(Object e) => e is FileSystemException ? (e.osError?.message ?? e.message) : '$e'; Future deletePaths(List paths, {required bool permanent, AppTrash? trash, DateTime? now}) async { final r = DeleteReport((now ?? DateTime.now()).subtract(const Duration(seconds: 2))); for (final x in paths) { if (!permanent && trash != null) { try { r.trashed.add(await trash.moveToTrash(x)); r.undoable.add(x); r.recycled++; } on TrashMoveException catch (e) { r.notTrashed.add((x, '${e.cause}')); } continue; } try { if (!permanent) { switch (recycle(x)) { case RecycleResult.recycled: r.recycled++; r.sent.add(x); case RecycleResult.deletedPermanently: r.nuked++; case RecycleResult.cancelled: r.kept++; } } else { await FileOps().delete([x]); } } on LongPathException { r.tooLong.add(x); } catch (e) { r.failed.add((x, deleteFailReason(e))); } } return r; } /// 영구 삭제 - 못 지운 것 (경로, 예외) Future> deleteForever(Iterable paths) async { final failed = <(String, Object)>[]; for (final x in paths) { try { await FileOps().delete([x]); } catch (e) { failed.add((x, e)); } } return failed; } /// 앱 휴지통에서 되살림 - 되살린 경로 (못 되살린 것은 빠짐) Future> undoTrash(AppTrash trash, List items) async { final back = []; for (final i in items) { try { back.add(await trash.restore(i)); } catch (_) {} } return back; } /// Windows 휴지통에서 되살림 - 되살린 수 · 못 되살린 것 (경로, 이유) ({int back, List<(String, String)> failed}) undoRecycle(List paths, DateTime since) { final failed = <(String, String)>[]; var back = 0; for (final x in paths) { try { restoreRecycled(x, since: since); back++; } catch (e) { failed.add((x, e is FileSystemException ? e.message : '$e')); } } return (back: back, failed: failed); }