185 lines
7.4 KiB
Dart
185 lines
7.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/core/hwp_source.dart';
|
|
import 'package:jj_mkvmaker/core/reader_sources.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// 쪽 범위를 글로 적은 파일을 "PDF" 로 만드는 가짜 엔진 (rhwp 대신) - 부른 범위를 적어 둔다
|
|
class _Engine implements HwpPdfEngine {
|
|
final int pages;
|
|
final calls = <(int, int)>[];
|
|
Completer<void>? hold;
|
|
var closed = false;
|
|
_Engine(this.pages);
|
|
|
|
@override
|
|
Future<HwpPdfDoc> open(String path, {String? password}) async => _Doc(this);
|
|
}
|
|
|
|
class _Doc implements HwpPdfDoc {
|
|
final _Engine e;
|
|
_Doc(this.e);
|
|
@override
|
|
int get pageCount => e.pages;
|
|
|
|
@override
|
|
Future<void> exportPdf(int from, int to, String out, {List<String> fontDirs = const []}) async {
|
|
e.calls.add((from, to));
|
|
final h = e.hold;
|
|
if (h != null) await h.future;
|
|
File(out).writeAsStringSync('$from-$to');
|
|
}
|
|
|
|
@override
|
|
Future<void> close() async => e.closed = true;
|
|
}
|
|
|
|
/// 파일을 읽어 쪽 수를 아는 가짜 묶음 (PDFium 없이) - 연 것 · 닫은 것을 적어 둔다
|
|
class _Opener {
|
|
final opened = <String>[];
|
|
final disposed = <String>[];
|
|
Completer<void>? renderHold;
|
|
|
|
Future<HwpChunk> call(String path) async {
|
|
opened.add(p.basename(path));
|
|
final parts = File(path).readAsStringSync().split('-').map(int.parse).toList();
|
|
return _Chunk(this, p.basename(path), parts[1] - parts[0]);
|
|
}
|
|
}
|
|
|
|
class _Chunk implements HwpChunk {
|
|
final _Opener o;
|
|
final String name;
|
|
@override
|
|
final int pageCount;
|
|
_Chunk(this.o, this.name, this.pageCount);
|
|
|
|
@override
|
|
Future<ReaderImage> render(int index, {required int maxWidth}) async {
|
|
expect(index, lessThan(pageCount));
|
|
final h = o.renderHold;
|
|
if (h != null) await h.future;
|
|
return const ReaderImage();
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async => o.disposed.add(name);
|
|
}
|
|
|
|
/// 관리자 지시 (10/20, HWP 2단계): 보기 원본 HwpSource 의 흐름 - 묶음 나누기 · 미리 만들기 · 열어 두는 묶음 상한 (메모리) ·
|
|
/// 그리는 묶음은 닫지 않음 · 닫은 묶음은 다시 바꾸지 않고 파일만 · 시간 제한 → 이유 · 다시 시도 · 닫기
|
|
void main() {
|
|
late Directory dir;
|
|
late File doc;
|
|
setUp(() {
|
|
dir = Directory.systemTemp.createTempSync('jj_hwpunit_');
|
|
doc = File(p.join(dir.path, 'a.hwp'))..writeAsStringSync('x');
|
|
});
|
|
tearDown(() {
|
|
if (dir.existsSync()) dir.deleteSync(recursive: true);
|
|
});
|
|
|
|
Future<void> settle() => Future<void>.delayed(const Duration(milliseconds: 50));
|
|
|
|
test('보는 쪽의 묶음 + 다음 묶음만 · 열어 둔 묶음은 3개까지 (오래된 것부터 닫음) · 다시 보면 파일만 연다', () async {
|
|
final e = _Engine(40), o = _Opener();
|
|
final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, maxOpenChunks: 3, opener: o.call);
|
|
expect(s.length, 40);
|
|
await s.load(0, maxWidth: 100);
|
|
await settle();
|
|
expect(e.calls, [(0, 5), (5, 10)]);
|
|
expect(s.openChunks, [0, 1]);
|
|
await s.load(12, maxWidth: 100); // 묶음 2 + 미리 3 → 4개 → 0 을 닫음
|
|
await settle();
|
|
expect(e.calls, [(0, 5), (5, 10), (10, 15), (15, 20)]);
|
|
expect(s.openChunks, [1, 2, 3]);
|
|
expect(o.disposed, ['p0.pdf']);
|
|
// 닫은 묶음 0 을 다시: 바꾸지 않고 (calls 그대로) 파일만 연다
|
|
await s.load(1, maxWidth: 100);
|
|
await settle();
|
|
expect(e.calls.where((c) => c == (0, 5)), hasLength(1));
|
|
expect(o.opened.where((n) => n == 'p0.pdf'), hasLength(2));
|
|
expect(s.openChunks.length, lessThanOrEqualTo(3));
|
|
expect(s.madeChunks, containsAll([0, 1, 2, 3]));
|
|
// 끝 묶음은 남은 쪽만
|
|
await s.load(39, maxWidth: 100);
|
|
expect(e.calls.last, (35, 40));
|
|
await s.dispose();
|
|
expect(e.closed, isTrue);
|
|
expect(s.work.existsSync(), isFalse, reason: '닫으면 만든 PDF 들을 지움');
|
|
});
|
|
|
|
test('그리고 있는 묶음은 상한을 넘어도 닫지 않고, 다 그린 뒤에 닫는다', () async {
|
|
final e = _Engine(40), o = _Opener();
|
|
final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, maxOpenChunks: 2, opener: o.call);
|
|
final hold = Completer<void>();
|
|
o.renderHold = hold;
|
|
final drawing = s.load(2, maxWidth: 100); // 묶음 0 을 그리는 중
|
|
await settle();
|
|
o.renderHold = null;
|
|
await s.load(20, maxWidth: 100); // 묶음 4 (+ 5)
|
|
await s.load(30, maxWidth: 100); // 묶음 6 (+ 7)
|
|
await settle();
|
|
expect(o.disposed, isNot(contains('p0.pdf')), reason: '그리는 중');
|
|
expect(s.openChunks, contains(0));
|
|
hold.complete();
|
|
await drawing;
|
|
await s.load(35, maxWidth: 100);
|
|
await settle();
|
|
expect(o.disposed, contains('p0.pdf'), reason: '다 그린 뒤에는 상한에 맞춰 닫힘');
|
|
expect(s.openChunks.length, lessThanOrEqualTo(2));
|
|
await s.dispose();
|
|
});
|
|
|
|
test('시간 제한을 넘기면 쪽 범위 · 초가 든 이유, 다시 부르면 다시 만든다', () async {
|
|
final e = _Engine(8)..hold = Completer<void>();
|
|
final o = _Opener();
|
|
final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, timeout: const Duration(milliseconds: 200),
|
|
opener: o.call);
|
|
Object? err;
|
|
try {
|
|
await s.load(0, maxWidth: 100);
|
|
} catch (x) {
|
|
err = x;
|
|
}
|
|
expect(err, isA<HwpPdfException>());
|
|
expect('$err', allOf(contains('1~5'), contains('0초')));
|
|
expect(s.openChunks, isNot(contains(0)), reason: '실패한 묶음은 기억하지 않음 - [다시 시도] 가 다시 만든다');
|
|
e.hold!.complete();
|
|
e.hold = null;
|
|
await s.load(0, maxWidth: 100);
|
|
expect(e.calls.where((c) => c == (0, 5)).length, greaterThanOrEqualTo(2));
|
|
await s.dispose();
|
|
});
|
|
|
|
test('닫으면 만드는 중인 묶음은 끝난 뒤 스스로 닫히고 더 만들지 않는다', () async {
|
|
final e = _Engine(10)..hold = Completer<void>();
|
|
final o = _Opener();
|
|
final s = await HwpSource.open(doc.path, engine: e, tempDir: dir.path, chunk: 5, opener: o.call);
|
|
final pending = s.load(0, maxWidth: 100).then<Object?>((_) => null, onError: (Object x) => x);
|
|
await settle();
|
|
await s.dispose(); // 기다리지 않고 바로 끝난다
|
|
e.hold!.complete();
|
|
final r = await pending;
|
|
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: '폴더만');
|
|
});
|
|
}
|