WIP 압축 펼치기: 펼친 뒤 압축 파일이 바뀌면 (크기 · 바뀐 때) 펼치거나 새로 고칠 때 목록을 다시 읽음 (검토 대기)

관리자 지시 (10/19, 사용자 "오늘 못했던 것 진행" 1번 - 압축 펼치기의 알려진 한계):
읽어 둔 압축 목록을 계속 써서, 다른 프로그램이 압축을 바꿔도 접었다 펼치거나 새로 고쳐도 예전 목록이 보였다.
- 압축을 읽을 때의 (크기, 바뀐 때) 를 기억하고, 다시 펼칠 때 · 새로 고침 (F5 · [새로 고침] · 앱으로 돌아옴) 때 비교해 다르면
  그 압축의 목록과 모든 창의 그 안 폴더 목록을 잊고 다시 읽는다.
- 압축이 없어졌으면 목록을 잊고 그 안은 빈 목록으로 (예전 것을 보이지 않게).

시험: explorer_test (새 1: 펼친 뒤 압축을 바꾸고 F5 → 새 항목 · 예전 항목 없음) 62 통과, 임시 폴더 흔적 0 (tool/test_temp_check.ps1), analyze 문제 없음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
next2
RyuJongJin 2026-10-19 20:47:58 +09:00
parent a74e7be59b
commit 53f5c432bb
2 changed files with 65 additions and 6 deletions

View File

@ -908,11 +908,19 @@ class _ExplorerPageState extends State<ExplorerPage> {
Future<void> _load(_Pane pane, String dir, {bool force = false}) async {
if (!force && pane.cache.containsKey(dir)) return;
// X-plore 비교 f: 압축 안 (읽어 둔 목록에서)
if (_archiveOf(dir) != null) {
pane.cache[dir] = _sorted(_archiveList(dir), dir);
pane.changed();
return;
// X-plore 비교 f: 압축 안 (읽어 둔 목록에서). 새로 고침이면 압축 파일이 바뀌었는지 보고 바뀌었으면 다시 읽는다
final arc = _archiveOf(dir);
if (arc != null) {
if (force && !await _readArchive(arc)) {
pane.cache[dir] = const [];
pane.changed();
return;
}
if (_archives.containsKey(arc)) {
pane.cache[dir] = _sorted(_archiveList(dir), dir);
pane.changed();
return;
}
}
// 80: 다시 읽는 폴더는 안이 바뀌었을 수 있다 - 그 폴더와 위 폴더들의 크기를 잊는다
if (force) _sizes.forget(dir);
@ -2173,6 +2181,28 @@ class _ExplorerPageState extends State<ExplorerPage> {
/// 펼쳐 본 압축의 목록 (압축 경로 → 항목)
final _archives = <String, List<ArchiveEntry>>{};
/// 압축을 읽은 때의 (크기, 바뀐 때) - 바뀌었으면 다음에 펼치거나 새로 고칠 때 다시 읽는다 (관리자 지시 10/19)
final _archiveStamps = <String, (int, DateTime)>{};
static Future<(int, DateTime)?> _stampOf(String path) async {
try {
final st = await File(path).stat();
if (st.type == FileSystemEntityType.notFound) return null;
return (st.size, st.modified);
} catch (_) {
return null;
}
}
/// [arc] 의 읽어 둔 목록과 창들의 그 안 목록을 잊는다 (다시 읽게)
void _forgetArchive(String arc) {
_archives.remove(arc);
_archiveStamps.remove(arc);
for (final q in _panes) {
q.cache.removeWhere((k, _) => samePath(k, arc) || isSameOrInside(k, arc));
}
}
/// 트리에서 펼칠 수 있는 압축 (이 기기의 파일 · 탐색기 화면)
bool _canBrowseArchive(FileEntry e) => !widget.rsync && !e.isDir && !isDav(e.path) && archiveKindOf(e.path) != null;
@ -2196,7 +2226,13 @@ class _ExplorerPageState extends State<ExplorerPage> {
/// 압축의 목록을 읽어 둔다 (비밀번호가 걸린 7z 등은 [압축 풀기] 로 안내). 읽었으면 true
Future<bool> _readArchive(String path) async {
if (_archives.containsKey(path)) return true;
final stamp = await _stampOf(path);
if (_archives.containsKey(path)) {
// 펼친 뒤 압축 파일이 바뀌었으면 (크기 · 바뀐 때) 다시 읽는다. 없어졌으면 잊는다
if (stamp != null && stamp == _archiveStamps[path]) return true;
_forgetArchive(path);
if (stamp == null) return false;
}
final kind = archiveKindOf(path)!;
try {
if (kind == ArchiveKind.zip) {
@ -2209,6 +2245,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
}
_archives[path] = await list7z(exe, path);
}
if (stamp != null) _archiveStamps[path] = stamp;
return true;
} on ArchivePasswordException {
_snack(tr('비밀번호가 걸린 압축은 안을 미리 볼 수 없습니다 - [압축 풀기] 로 푸세요'));

View File

@ -358,6 +358,28 @@ void main() {
await calm(tester);
});
testWidgets('X-plore f 보충: 펼친 뒤 압축 파일이 바뀌면 새로 고칠 때 목록을 다시 읽는다 (관리자 지시 10/19)', (tester) async {
final zip = p.join(left, 'pack.zip');
File(zip).writeAsBytesSync(ZipEncoder().encode(Archive()..addFile(ArchiveFile.string('old.txt', 'old'))));
await open(tester);
await settle(tester, () => find.text('pack.zip').evaluate().isNotEmpty);
await act(tester, () => tester.tap(chevronOf('pack.zip').first));
await settle(tester, () => find.text('old.txt').evaluate().isNotEmpty);
expect(find.text('old.txt'), findsOneWidget);
// 다른 프로그램이 압축을 바꿈 (크기 · 바뀐 때가 달라짐)
await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 1100));
File(zip).writeAsBytesSync(ZipEncoder().encode(Archive()
..addFile(ArchiveFile.string('new.txt', 'new content'))
..addFile(ArchiveFile.string('more.txt', 'more'))));
});
await act(tester, () => tester.sendKeyEvent(LogicalKeyboardKey.f5));
await settle(tester, () => find.text('new.txt').evaluate().isNotEmpty);
expect(find.text('new.txt'), findsOneWidget);
expect(find.text('more.txt'), findsOneWidget);
expect(find.text('old.txt'), findsNothing);
});
testWidgets('X-plore g · h: 폴더 메뉴 [격자로 보기] (길게 = 그 자리로) · 파일 메뉴 [글 뷰어로 보기]', (tester) async {
File(p.join(left, 'sub', 'pic.png')).writeAsBytesSync([0x89, 0x50]);
await open(tester);