보관함 넣기: 넣다가 잠가 멈추면 넣지 못한 원본은 지우지 않음
- 예전부터 있던 버그: [원본 지우기 (이동)] 로 넣는 도중에 잠그면 넣기는 멈추지만, 넣지 못한 원본이 bad 에 들어가지 않아 그 원본까지 지워졌다 (데이터 잃음) - putVaultAdd: 잠겨 멈출 때 남은 항목의 top 을 bad 에 넣는다 → 넣은 것만 원본을 지우고 나머지는 그대로 - 약속 시험 (실제 시험 폴더 · 실제 보관함): 원본 4개를 넣다가 3번째에서 잠그면 1 · 2 만 지워지고 3 · 4 는 바이트까지 그대로. 고치기 전 코드에서는 4.bin 이 지워져 실패함을 확인 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>split_vault
parent
88aadc7d3e
commit
43c8dab04c
|
|
@ -213,7 +213,7 @@ Future<({bool move, bool permanent})?> askVaultAdd(BuildContext context, List<St
|
||||||
|
|
||||||
/// 펼친 [plan] 을 [s] 의 보관함에 넣는다 (넣는 동안 잠그면 ([VaultUiState.vault] 가 null) 멈춤).
|
/// 펼친 [plan] 을 [s] 의 보관함에 넣는다 (넣는 동안 잠그면 ([VaultUiState.vault] 가 null) 멈춤).
|
||||||
/// - 원격은 앱 전용 폴더에 받아 넣고 바로 지운다. 넣은 원본의 썸네일 캐시 ([thumbTemp] 아래) 도 지운다
|
/// - 원격은 앱 전용 폴더에 받아 넣고 바로 지운다. 넣은 원본의 썸네일 캐시 ([thumbTemp] 아래) 도 지운다
|
||||||
/// - 출력: 넣은 수 [done] · 다 넣지 못한 고른 것 [bad] (plan.failed 포함). 오류는 [VaultUiState.snack] 으로 (던지지 않음)
|
/// - 출력: 넣은 수 [done] · 다 넣지 못한 고른 것 [bad] (plan.failed · 잠가서 멈춘 뒤 남은 것 포함). 오류는 [VaultUiState.snack] 으로 (던지지 않음)
|
||||||
Future<({int done, Set<String> bad})> putVaultAdd(VaultUiState s, List<String> paths, VaultAddPlan plan,
|
Future<({int done, Set<String> bad})> putVaultAdd(VaultUiState s, List<String> paths, VaultAddPlan plan,
|
||||||
{required Future<String> Function() thumbTemp}) async {
|
{required Future<String> Function() thumbTemp}) async {
|
||||||
s.setBusy(true);
|
s.setBusy(true);
|
||||||
|
|
@ -226,7 +226,13 @@ Future<({int done, Set<String> bad})> putVaultAdd(VaultUiState s, List<String> p
|
||||||
}
|
}
|
||||||
for (var i = 0; i < plan.files.length; i++) {
|
for (var i = 0; i < plan.files.length; i++) {
|
||||||
final x = plan.files[i];
|
final x = plan.files[i];
|
||||||
if (s.vault == null) break; // 넣는 동안 잠갔으면 멈춤
|
if (s.vault == null) {
|
||||||
|
// 넣는 동안 잠갔으면 멈춤 - 넣지 못한 남은 것은 bad 로 (그 원본은 [원본 지우기 (이동)] 로 지우지 않게)
|
||||||
|
for (final rest in plan.files.skip(i)) {
|
||||||
|
bad.add(rest.top);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
s.setProgress('${i + 1} / ${plan.files.length} · ${vBasename(x.src)}');
|
s.setProgress('${i + 1} / ${plan.files.length} · ${vBasename(x.src)}');
|
||||||
String? fetched;
|
String? fetched;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -158,4 +159,61 @@ void main() {
|
||||||
await t.tap(find.text('나'));
|
await t.tap(find.text('나'));
|
||||||
expect(got, hasLength(2), reason: '바쁜 동안은 누르기가 꺼짐');
|
expect(got, hasLength(2), reason: '바쁜 동안은 누르기가 꺼짐');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('vault_session (넣기): [원본 지우기 (이동)] 로 넣다가 중간에 잠그면 넣은 것만 원본을 지우고, 넣지 못한 원본은 바이트까지 그대로', (t) async {
|
||||||
|
final src = Directory(p.join(tmp.path, 'src'));
|
||||||
|
final names = ['1.bin', '2.bin', '3.bin', '4.bin'];
|
||||||
|
final bytes = {for (final n in names) n: List<int>.generate(4096, (i) => (i * 7 + n.codeUnitAt(0)) % 256)};
|
||||||
|
final vault = await t.runAsync(() async {
|
||||||
|
src.createSync();
|
||||||
|
for (final n in names) {
|
||||||
|
File(p.join(src.path, n)).writeAsBytesSync(bytes[n]!);
|
||||||
|
}
|
||||||
|
Directory(p.join(tmp.path, 'thumbs')).createSync();
|
||||||
|
return Vault.create(Directory(p.join(tmp.path, 'vault')), 'pw-for-test', iterations: 1000);
|
||||||
|
});
|
||||||
|
final paths = [for (final n in names) p.join(src.path, n)];
|
||||||
|
late VaultUiState s;
|
||||||
|
// 화면의 잠그기와 같게: 열쇠를 버린다 (vault = null). "3 / 4" 를 보이는 때 = 1 · 2 를 넣은 뒤
|
||||||
|
s = VaultUiState(
|
||||||
|
changed: () {
|
||||||
|
if (s.progress?.startsWith('3 / ') ?? false) s.vault = null;
|
||||||
|
},
|
||||||
|
snack: (_) {})
|
||||||
|
..vault = vault;
|
||||||
|
final deleted = <String>[];
|
||||||
|
late BuildContext ctx;
|
||||||
|
await t.pumpWidget(MaterialApp(home: Builder(builder: (c) {
|
||||||
|
ctx = c;
|
||||||
|
return const Scaffold();
|
||||||
|
})));
|
||||||
|
var finished = false;
|
||||||
|
unawaited(vaultAddFlow(ctx, s, paths, thumbTemp: () async => p.join(tmp.path, 'thumbs'), deleteOriginals: (ok, {required permanent}) async {
|
||||||
|
deleted.addAll(ok);
|
||||||
|
for (final x in ok) {
|
||||||
|
File(x).deleteSync(); // 실제로 지운다 (탐색기의 지우기 대신)
|
||||||
|
}
|
||||||
|
}).whenComplete(() => finished = true));
|
||||||
|
Future<void> until(bool Function() ok) async {
|
||||||
|
for (var i = 0; i < 300 && !ok(); i++) {
|
||||||
|
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
||||||
|
await t.pump(const Duration(milliseconds: 20));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await until(() => find.text('원본 지우기 (이동)').evaluate().isNotEmpty);
|
||||||
|
await t.tap(find.text('원본 지우기 (이동)'));
|
||||||
|
await t.pump();
|
||||||
|
await t.tap(find.widgetWithText(FilledButton, '넣기'));
|
||||||
|
await until(() => finished);
|
||||||
|
expect(finished, isTrue);
|
||||||
|
expect(s.vault, isNull, reason: '중간에 잠금');
|
||||||
|
expect([for (final e in vault!.entries) e.name], unorderedEquals(['1.bin', '2.bin']), reason: '잠그기 전 둘만 들어감');
|
||||||
|
expect(deleted, unorderedEquals([paths[0], paths[1]]), reason: '넣은 것만 원본 지우기로');
|
||||||
|
for (final n in ['3.bin', '4.bin']) {
|
||||||
|
final f = File(p.join(src.path, n));
|
||||||
|
expect(f.existsSync(), isTrue, reason: '넣지 못한 원본 $n 은 남음');
|
||||||
|
expect(f.readAsBytesSync(), bytes[n], reason: '$n 바이트 그대로');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue