WIP HWP 고침 (관리자 지시 10/20, 28 지적): 진행 창 (여는 중 · PDF 로 바꾸는 중) 에 [취소] - ← · [취소] 로 먼저 닫혔으면 끝난 뒤 pop 하지 않음 (예전에는 다른 화면을 닫을 수 있었음, Windows 문서 미리보기도) · 그만두면 결과를 보이지 않고 연 문서를 닫음 · 앱이 켜질 때 남은 HWP 작업 폴더 (jj_hwp_) 정리 · 시험 progress_dialog_test · 정리 시험 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>hwp2
parent
34efa34193
commit
c43637b9cc
|
|
@ -2452,6 +2452,7 @@
|
|||
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "The last run did not end normally (last check {0}:{1}). It has been noted in the job log.",
|
||||
"지난 업데이트가 남긴 임시 파일을 지움: {0}": "Removed temporary files left by the last update: {0}",
|
||||
"지난 작업 기록": "Old job logs",
|
||||
"지난번에 남은 문서 보기 임시 폴더를 지움: {0}개": "Removed {0} leftover document-view temp folders",
|
||||
"지문 확인하고 연결": "Check fingerprint and connect",
|
||||
"지우고 맞추기": "Delete and sync",
|
||||
"지우고 초기화": "Delete and reset",
|
||||
|
|
|
|||
|
|
@ -2452,6 +2452,7 @@
|
|||
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "前回の実行が正常に終了しませんでした(最終確認 {0}:{1})。作業記録に残しました。",
|
||||
"지난 업데이트가 남긴 임시 파일을 지움: {0}": "前回のアップデートが残した一時ファイルを削除: {0}",
|
||||
"지난 작업 기록": "過去の作業記録",
|
||||
"지난번에 남은 문서 보기 임시 폴더를 지움: {0}개": "前回残った文書表示の一時フォルダーを削除: {0} 個",
|
||||
"지문 확인하고 연결": "フィンガープリントを確認して接続",
|
||||
"지우고 맞추기": "削除して同期",
|
||||
"지우고 초기화": "削除して初期化",
|
||||
|
|
|
|||
|
|
@ -2452,6 +2452,7 @@
|
|||
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "上次运行未正常结束(最后检查 {0}:{1})。已记录在任务记录中。",
|
||||
"지난 업데이트가 남긴 임시 파일을 지움: {0}": "已删除上次更新留下的临时文件: {0}",
|
||||
"지난 작업 기록": "旧任务记录",
|
||||
"지난번에 남은 문서 보기 임시 폴더를 지움: {0}개": "已删除上次留下的文档查看临时文件夹: {0} 个",
|
||||
"지문 확인하고 연결": "确认指纹并连接",
|
||||
"지우고 맞추기": "删除并同步",
|
||||
"지우고 초기화": "删除并重置",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import '../services/platform_services.dart';
|
|||
import '../services/system_usage.dart';
|
||||
import 'settings.dart';
|
||||
import 'cleanup.dart' show removeUpdateLeftovers;
|
||||
import '../core/hwp_source.dart' show removeStaleHwpWork;
|
||||
import '../l10n/tr.dart';
|
||||
import '../core/remote_idle.dart';
|
||||
|
||||
|
|
@ -213,6 +214,11 @@ class AppController extends ChangeNotifier {
|
|||
final gone = removeUpdateLeftovers(Directory.systemTemp.path);
|
||||
if (gone.isNotEmpty) _log(trf('지난 업데이트가 남긴 임시 파일을 지움: {0}', [gone.join(', ')]));
|
||||
}
|
||||
// 관리자 지시 (10/20): 강제로 끝나 남은 HWP 보기 작업 폴더 (바꾼 PDF 들)
|
||||
try {
|
||||
final hwp = removeStaleHwpWork(await services.storage.tempDirectory());
|
||||
if (hwp.isNotEmpty) _log(trf('지난번에 남은 문서 보기 임시 폴더를 지움: {0}개', [hwp.length]));
|
||||
} catch (_) {}
|
||||
ffmpegVersion = await _tool.version();
|
||||
if (ffmpegVersion == null) {
|
||||
_log(tr('⚠ FFmpeg 를 찾을 수 없습니다. MKV 만들기를 사용할 수 없습니다.'));
|
||||
|
|
|
|||
|
|
@ -12,6 +12,25 @@ import '../l10n/tr.dart';
|
|||
/// HWP · HWPX 확장자 (앱 안에서 PDF 로 바꿔 보기 - Android, rhwp)
|
||||
const hwpExtensions = ['hwp', 'hwpx'];
|
||||
|
||||
/// 관리자 지시 (10/20): 앱이 강제로 끝나 남은 HWP 작업 폴더 (앱 임시 폴더의 jj_hwp_…, 바꾼 PDF 들) 를 앱이 켜질 때 지운다.
|
||||
/// 막 생긴 것 ([minAge] 안) 은 남긴다. 지운 이름을 돌려준다
|
||||
List<String> removeStaleHwpWork(String tempDir, {Duration minAge = const Duration(minutes: 1), DateTime? now}) {
|
||||
final before = (now ?? DateTime.now()).subtract(minAge);
|
||||
final removed = <String>[];
|
||||
try {
|
||||
for (final e in Directory(tempDir).listSync(followLinks: false)) {
|
||||
final name = p.basename(e.path);
|
||||
if (e is! Directory || !name.startsWith('jj_hwp_')) continue;
|
||||
try {
|
||||
if (!e.statSync().modified.isBefore(before)) continue;
|
||||
e.deleteSync(recursive: true);
|
||||
removed.add(name);
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// HWP · HWPX → PDF 엔진 (Android: APK 의 libjj_rhwp.so - lib/platform/android/hwp_pdf_ffi.dart, 시험: 가짜)
|
||||
abstract class HwpPdfEngine {
|
||||
/// 문서 열기 (암호가 있으면 [password]). 실패하면 [HwpPdfException]
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import '../platform/windows/recycle_bin.dart';
|
|||
import '../core/rsync_filter.dart';
|
||||
import '../core/sync_preview.dart';
|
||||
import 'path_label.dart';
|
||||
import 'progress_dialog.dart';
|
||||
import 'sync_preview_view.dart';
|
||||
import 'sftp_settings.dart';
|
||||
import 'ftp_settings.dart';
|
||||
|
|
@ -1768,17 +1769,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
Future<void> _viewDoc(String path) async {
|
||||
final temp = await c.services.storage.tempDirectory();
|
||||
if (!mounted) return;
|
||||
final dialog = showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => AlertDialog(
|
||||
content: Row(children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: Text(trf('PDF 로 바꾸는 중: {0}', [vBasename(path)]))),
|
||||
]),
|
||||
),
|
||||
);
|
||||
final dialog = showProgressDialog(context, trf('PDF 로 바꾸는 중: {0}', [vBasename(path)]));
|
||||
String? pdf;
|
||||
Object? error;
|
||||
try {
|
||||
|
|
@ -1787,8 +1778,10 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
await dialog;
|
||||
// 사용자가 [취소] · ← 로 그만두었으면 결과를 보이지 않는다 (바꾸기는 끝까지 돌았음)
|
||||
if (dialog.cancelled()) return;
|
||||
dialog.close();
|
||||
await dialog.done;
|
||||
if (pdf == null) {
|
||||
_snack(trf('미리 볼 수 없습니다: {0}', [error]));
|
||||
return;
|
||||
|
|
@ -1808,17 +1801,7 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
Future<bool> _viewHwp(String path) async {
|
||||
final temp = await c.services.storage.tempDirectory();
|
||||
if (!mounted) return true;
|
||||
final dialog = showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => AlertDialog(
|
||||
content: Row(children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: Text(trf('여는 중: {0}', [vBasename(path)]))),
|
||||
]),
|
||||
),
|
||||
);
|
||||
final dialog = showProgressDialog(context, trf('여는 중: {0}', [vBasename(path)]));
|
||||
HwpSource? src;
|
||||
Object? error;
|
||||
try {
|
||||
|
|
@ -1826,8 +1809,13 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
await dialog;
|
||||
// 사용자가 [취소] · ← 로 그만두었으면 연 문서를 닫고 끝 (다른 앱으로도 열지 않음)
|
||||
if (dialog.cancelled()) {
|
||||
await src?.dispose();
|
||||
return true;
|
||||
}
|
||||
dialog.close();
|
||||
await dialog.done;
|
||||
if (src == null) {
|
||||
_snack(trf('앱 안에서 열지 못해 다른 앱으로 엽니다: {0}', [error]));
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../l10n/tr.dart';
|
||||
|
||||
/// 바꾸는 · 여는 동안의 진행 창 ([취소] 있음). 관리자 지시 (10/20, 28 지적): Android ← · [취소] 로 창이 먼저 닫힐 수 있어,
|
||||
/// 일이 끝난 뒤에는 그 창이 아직 열려 있을 때만 닫는다 (예전에는 끝난 뒤의 pop() 이 다른 화면을 닫을 수 있었다).
|
||||
/// - [ProgressDialog.cancelled]: 사용자가 [취소] · ← 로 그만두었는지 (그러면 결과를 보이지 않는다)
|
||||
/// - [ProgressDialog.close]: 일이 끝나 창을 닫는다 (이미 닫혔으면 아무것도 하지 않는다)
|
||||
class ProgressDialog {
|
||||
final Future<void> done;
|
||||
final bool Function() cancelled;
|
||||
final void Function() close;
|
||||
const ProgressDialog._(this.done, this.cancelled, this.close);
|
||||
}
|
||||
|
||||
ProgressDialog showProgressDialog(BuildContext context, String text) {
|
||||
BuildContext? dctx;
|
||||
var open = true;
|
||||
var byUser = true;
|
||||
final done = showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
dctx = ctx;
|
||||
return AlertDialog(
|
||||
content: Row(children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(child: Text(text)),
|
||||
]),
|
||||
actions: [TextButton(onPressed: () => Navigator.of(ctx).pop(), child: Text(tr('취소')))],
|
||||
);
|
||||
},
|
||||
).whenComplete(() => open = false);
|
||||
return ProgressDialog._(done, () => !open && byUser, () {
|
||||
if (!open) return;
|
||||
byUser = false;
|
||||
final x = dctx;
|
||||
if (x != null && x.mounted) Navigator.of(x).pop();
|
||||
});
|
||||
}
|
||||
|
|
@ -166,4 +166,19 @@ void main() {
|
|||
expect(r, isA<StateError>(), reason: '닫힌 문서');
|
||||
expect(o.opened, isEmpty, reason: '닫은 뒤에는 PDF 를 열지 않음');
|
||||
});
|
||||
|
||||
test('앱이 켜질 때 남은 HWP 작업 폴더 (jj_hwp_) 만 지운다 - 막 생긴 것 · 다른 이름은 남김', () {
|
||||
final old = Directory(p.join(dir.path, 'jj_hwp_1'))..createSync();
|
||||
File(p.join(old.path, 'p0.pdf')).writeAsStringSync('x');
|
||||
final fresh = Directory(p.join(dir.path, 'jj_hwp_2'))..createSync();
|
||||
final other = Directory(p.join(dir.path, 'jj_hwpx_keep'))..createSync();
|
||||
final file = File(p.join(dir.path, 'jj_hwp_file'))..writeAsStringSync('x');
|
||||
// 5분 뒤로 보면 old · fresh 둘 다 1분이 지났다 - fresh 는 지금 기준으로는 남는다
|
||||
expect(removeStaleHwpWork(dir.path), isEmpty, reason: '모두 막 생김');
|
||||
final gone = removeStaleHwpWork(dir.path, now: DateTime.now().add(const Duration(minutes: 5)));
|
||||
expect(gone..sort(), ['jj_hwp_1', 'jj_hwp_2']);
|
||||
expect(old.existsSync() || fresh.existsSync(), isFalse);
|
||||
expect(other.existsSync(), isTrue, reason: '이름이 jj_hwp_ 로 시작하지 않음 (jj_hwpx_…)');
|
||||
expect(file.existsSync(), isTrue, reason: '폴더만');
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/ui/progress_dialog.dart';
|
||||
|
||||
/// 관리자 지시 (10/20, 28 지적): 진행 창이 ← · [취소] 로 먼저 닫혀도, 일이 끝난 뒤 close() 가 아래 화면을 닫지 않는다
|
||||
void main() {
|
||||
Future<BuildContext> pushPage(WidgetTester tester) async {
|
||||
late BuildContext page;
|
||||
await tester.pumpWidget(MaterialApp(home: Builder(builder: (ctx) {
|
||||
return Scaffold(
|
||||
body: TextButton(
|
||||
onPressed: () => Navigator.of(ctx).push(MaterialPageRoute<void>(builder: (c) {
|
||||
page = c;
|
||||
return const Scaffold(body: Text('아래 화면'));
|
||||
})),
|
||||
child: const Text('열기'),
|
||||
),
|
||||
);
|
||||
})));
|
||||
await tester.tap(find.text('열기'));
|
||||
await tester.pumpAndSettle();
|
||||
return page;
|
||||
}
|
||||
|
||||
testWidgets('← 로 닫으면 그만둔 것 - 끝난 뒤 close() 는 아래 화면을 닫지 않는다', (tester) async {
|
||||
final page = await pushPage(tester);
|
||||
final d = showProgressDialog(page, '여는 중: a.hwp');
|
||||
await tester.pump();
|
||||
expect(find.text('여는 중: a.hwp'), findsOneWidget);
|
||||
expect(d.cancelled(), isFalse);
|
||||
// Android ← (창을 닫음)
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('여는 중: a.hwp'), findsNothing);
|
||||
expect(d.cancelled(), isTrue);
|
||||
d.close(); // 일이 끝남 - 이미 닫혔으니 아무것도 닫지 않는다
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('아래 화면'), findsOneWidget, reason: '예전에는 이 pop() 이 아래 화면을 닫았다');
|
||||
});
|
||||
|
||||
testWidgets('[취소] 도 그만둔 것, 일이 끝나 close() 하면 창만 닫히고 그만둔 것이 아니다', (tester) async {
|
||||
final page = await pushPage(tester);
|
||||
final a = showProgressDialog(page, '바꾸는 중');
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('취소'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(a.cancelled(), isTrue);
|
||||
expect(find.text('아래 화면'), findsOneWidget);
|
||||
|
||||
final b = showProgressDialog(page, '바꾸는 중');
|
||||
await tester.pump();
|
||||
b.close();
|
||||
await tester.pumpAndSettle();
|
||||
await b.done;
|
||||
expect(b.cancelled(), isFalse);
|
||||
expect(find.text('바꾸는 중'), findsNothing);
|
||||
expect(find.text('아래 화면'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue