237 lines
8.8 KiB
Dart
237 lines
8.8 KiB
Dart
|
|
|
|
|
|
// 약속 (설정 A): 설정 안의 기록 - 이동 버튼 · 즐겨찾기 · 실시간 동기화 쌍 · 복사 작업. 저마다 toJson · fromJson
|
|
|
|
/// MKV 세부 정보의 이동 버튼 하나: 표시 이름 · 옮길 폴더
|
|
class MoveTarget {
|
|
final String name;
|
|
final String dir;
|
|
const MoveTarget(this.name, this.dir);
|
|
|
|
MoveTarget copyWith({String? name, String? dir}) => MoveTarget(name ?? this.name, dir ?? this.dir);
|
|
|
|
Map<String, Object?> toJson() => {'name': name, 'dir': dir};
|
|
|
|
factory MoveTarget.fromJson(Map<Object?, Object?> j) =>
|
|
MoveTarget((j['name'] as String?)?.trim().isNotEmpty == true ? j['name'] as String : '이동', j['dir'] as String);
|
|
|
|
@override
|
|
bool operator ==(Object other) => other is MoveTarget && other.name == name && other.dir == dir;
|
|
|
|
@override
|
|
int get hashCode => Object.hash(name, dir);
|
|
}
|
|
|
|
/// 73: 파일 탐색기 즐겨찾기 (폴더). [right] 가 있으면 Rsync 화면의 "왼쪽 + 오른쪽" 짝
|
|
class FolderBookmark {
|
|
final String name;
|
|
final String path;
|
|
final String? right;
|
|
const FolderBookmark({required this.name, required this.path, this.right});
|
|
|
|
bool get isPair => right != null;
|
|
|
|
FolderBookmark copyWith({String? name}) => FolderBookmark(name: name ?? this.name, path: path, right: right);
|
|
|
|
Map<String, Object?> toJson() => {'name': name, 'path': path, if (right != null) 'right': right};
|
|
|
|
static FolderBookmark? fromJson(Object? j) => j is Map && j['path'] is String
|
|
? FolderBookmark(
|
|
name: j['name'] is String && (j['name'] as String).trim().isNotEmpty ? j['name'] as String : j['path'] as String,
|
|
path: j['path'] as String,
|
|
right: j['right'] is String ? j['right'] as String : null)
|
|
: null;
|
|
}
|
|
|
|
/// 실시간 동기화 한 쌍 (lsyncd 처럼: 원본 폴더를 지켜보다가 바뀌면 대상 폴더에 맞춘다)
|
|
class LiveSyncPair {
|
|
final String source;
|
|
final String target;
|
|
|
|
/// 'builtin' · 'rsync' · 'robocopy' (CopyMethod 이름)
|
|
final String method;
|
|
|
|
/// 원본에 없는 것을 대상에서 지우기 (rsync --delete, robocopy /PURGE)
|
|
final bool delete;
|
|
final bool enabled;
|
|
|
|
/// 동작 시간 (cron 줄들, core/cron_window.dart). 비어 있으면 계속 (앱이 켜져 있는 동안 늘)
|
|
final List<String> schedule;
|
|
|
|
/// "지우기" 를 사용자가 지울 목록을 보고 확인했는지. 확인 전에는 지울 것이 있으면 지우지 않고 맞추며 확인을 기다린다 (42).
|
|
/// 이 값이 생기기 전부터 돌던 쌍은 확인한 것으로 본다.
|
|
final bool deleteConfirmed;
|
|
|
|
/// 지우기를 확인할 때의 rsync 옵션 (rsync 쌍만, 그 밖은 ''). 확인한 뒤 옵션이 바뀌어 지울 것이 늘면 다시 묻는다
|
|
/// (관리자 3a 판단 10/20 - --delete-excluded · --exclude · --filter 등). null = 이 값이 생기기 전에 확인한 쌍
|
|
final String? confirmedOptions;
|
|
const LiveSyncPair(this.source, this.target,
|
|
{this.method = 'builtin',
|
|
this.delete = false,
|
|
this.enabled = true,
|
|
this.schedule = const [],
|
|
this.deleteConfirmed = false,
|
|
this.confirmedOptions});
|
|
|
|
/// [delete] 를 새로 켜면 확인은 다시 받는다
|
|
LiveSyncPair copyWith(
|
|
{String? method, bool? delete, bool? enabled, List<String>? schedule, bool? deleteConfirmed, String? confirmedOptions}) =>
|
|
LiveSyncPair(source, target,
|
|
method: method ?? this.method,
|
|
delete: delete ?? this.delete,
|
|
enabled: enabled ?? this.enabled,
|
|
schedule: schedule ?? this.schedule,
|
|
deleteConfirmed: deleteConfirmed ?? (delete == true && !this.delete ? false : this.deleteConfirmed),
|
|
confirmedOptions: confirmedOptions ?? this.confirmedOptions);
|
|
|
|
Map<String, Object?> toJson() => {
|
|
'source': source,
|
|
'target': target,
|
|
'method': method,
|
|
'delete': delete,
|
|
'enabled': enabled,
|
|
'schedule': schedule,
|
|
'deleteConfirmed': deleteConfirmed,
|
|
if (confirmedOptions != null) 'confirmedOptions': confirmedOptions,
|
|
};
|
|
|
|
factory LiveSyncPair.fromJson(Map<Object?, Object?> j) => LiveSyncPair(
|
|
j['source'] as String,
|
|
j['target'] as String,
|
|
method: const ['rsync', 'robocopy'].contains(j['method']) ? j['method'] as String : 'builtin',
|
|
delete: j['delete'] == true,
|
|
enabled: j['enabled'] != false,
|
|
schedule: [for (final x in (j['schedule'] as List?) ?? const []) '$x'],
|
|
// 이 값이 없는 예전 쌍은 이미 지우기 포함으로 돌던 것 → 확인한 것으로
|
|
deleteConfirmed: j['deleteConfirmed'] as bool? ?? true,
|
|
confirmedOptions: j['confirmedOptions'] is String ? j['confirmedOptions'] as String : null,
|
|
);
|
|
}
|
|
|
|
/// 복사 · 이동 기억 (모니터링 > 복사 · rsync): 같은 원본 → 대상을 다시 복사하면 이 옵션을 쓴다. 언제든 [실행] 으로 다시.
|
|
class CopyTask {
|
|
final String id;
|
|
final List<String> sources;
|
|
final String dest;
|
|
final bool move;
|
|
|
|
/// 폴더 "안의 것" 을 대상에 맞추기 (lsync 에서 옮겨 온 것: 원본/ → 대상/)
|
|
final bool contents;
|
|
|
|
/// CopyMethod 이름 · 그 방법의 옵션 · 한 번에 · 속도 제한 (KB/s)
|
|
final String method;
|
|
final String options;
|
|
final bool once;
|
|
final int bandwidthKBps;
|
|
|
|
/// 마지막 실행: 시각 (ISO) · 결과 ('done' · 'failed' · 'cancelled' · '') · 글 · 파일 수
|
|
final String lastRun;
|
|
final String lastResult;
|
|
final String lastMessage;
|
|
final int lastFiles;
|
|
|
|
/// 235: 이동 뒤 원본에 남은 파일 수 (건너뜀 - 216). 카드에 "n개는 원본에 남음" 으로
|
|
final int lastLeft;
|
|
|
|
/// 237: 실패 원문 (예외 이름 · 명령 줄 포함) - 카드의 [자세히] 에서 보고 복사한다. 화면 글은 [lastMessage]
|
|
final String lastDetail;
|
|
|
|
/// 이동 ([move], rsync --remove-source-files) 뒤 원본 정리: '' 그대로 · 'keep' 빈 폴더를 지우고 원본 폴더는 남김 ·
|
|
/// 'all' 원본 폴더까지 (비었으면) 지움 (find 원본/ -type d -empty -delete)
|
|
final String prune;
|
|
|
|
const CopyTask({
|
|
required this.id,
|
|
required this.sources,
|
|
required this.dest,
|
|
this.move = false,
|
|
this.contents = false,
|
|
this.method = 'builtin',
|
|
this.options = '',
|
|
this.once = false,
|
|
this.bandwidthKBps = 0,
|
|
this.lastRun = '',
|
|
this.lastResult = '',
|
|
this.lastMessage = '',
|
|
this.lastFiles = 0,
|
|
this.lastLeft = 0,
|
|
this.lastDetail = '',
|
|
this.prune = '',
|
|
});
|
|
|
|
/// 같은 복사인지 (원본들 · 대상 · 이동)
|
|
String get key => '${move ? 'M' : contents ? 'S' : 'C'}|${[...sources]..sort()}|$dest';
|
|
|
|
CopyTask copyWith({
|
|
String? method,
|
|
String? options,
|
|
bool? once,
|
|
int? bandwidthKBps,
|
|
bool? move,
|
|
String? lastRun,
|
|
String? lastResult,
|
|
String? lastMessage,
|
|
int? lastFiles,
|
|
int? lastLeft,
|
|
String? lastDetail,
|
|
String? prune,
|
|
}) =>
|
|
CopyTask(
|
|
id: id,
|
|
sources: sources,
|
|
dest: dest,
|
|
move: move ?? this.move,
|
|
contents: contents,
|
|
method: method ?? this.method,
|
|
options: options ?? this.options,
|
|
once: once ?? this.once,
|
|
bandwidthKBps: bandwidthKBps ?? this.bandwidthKBps,
|
|
lastRun: lastRun ?? this.lastRun,
|
|
lastResult: lastResult ?? this.lastResult,
|
|
lastMessage: lastMessage ?? this.lastMessage,
|
|
lastFiles: lastFiles ?? this.lastFiles,
|
|
lastLeft: lastLeft ?? this.lastLeft,
|
|
lastDetail: lastDetail ?? this.lastDetail,
|
|
prune: prune ?? this.prune,
|
|
);
|
|
|
|
Map<String, Object?> toJson() => {
|
|
'id': id,
|
|
'sources': sources,
|
|
'dest': dest,
|
|
'move': move,
|
|
'contents': contents,
|
|
'method': method,
|
|
'options': options,
|
|
'once': once,
|
|
'bandwidthKBps': bandwidthKBps,
|
|
'lastRun': lastRun,
|
|
'lastResult': lastResult,
|
|
'lastMessage': lastMessage,
|
|
'lastFiles': lastFiles,
|
|
'lastLeft': lastLeft,
|
|
'lastDetail': lastDetail,
|
|
'prune': prune,
|
|
};
|
|
|
|
factory CopyTask.fromJson(Map<Object?, Object?> j) => CopyTask(
|
|
id: '${j['id']}',
|
|
sources: [for (final x in (j['sources'] as List?) ?? const []) '$x'],
|
|
dest: '${j['dest']}',
|
|
move: j['move'] == true,
|
|
contents: j['contents'] == true,
|
|
method: const ['rsync', 'robocopy'].contains(j['method']) ? j['method'] as String : 'builtin',
|
|
options: j['options'] as String? ?? '',
|
|
once: j['once'] == true,
|
|
bandwidthKBps: (j['bandwidthKBps'] as num?)?.toInt() ?? 0,
|
|
lastRun: j['lastRun'] as String? ?? '',
|
|
lastResult: j['lastResult'] as String? ?? '',
|
|
lastMessage: j['lastMessage'] as String? ?? '',
|
|
lastFiles: (j['lastFiles'] as num?)?.toInt() ?? 0,
|
|
lastLeft: (j['lastLeft'] as num?)?.toInt() ?? 0,
|
|
lastDetail: j['lastDetail'] as String? ?? '',
|
|
prune: const ['keep', 'all'].contains(j['prune']) ? j['prune'] as String : '',
|
|
);
|
|
}
|