Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,186 @@
import 'dart:convert';
import '../../contracts/contracts.dart';
import '../errors.dart';
import '../ports/danmaku_sources_store.dart';
import '../ports/script_widget_repository.dart';
import '../ports/server_repository.dart';
import '../ports/session_repository.dart';
const int kBackupSchemaVersion = 1;
class BackupExportResult {
final String json;
final int serverCount;
final int sessionCount;
final int danmakuSourceCount;
final int scriptWidgetCount;
final int scriptWidgetSubscriptionCount;
const BackupExportResult({
required this.json,
required this.serverCount,
required this.sessionCount,
required this.danmakuSourceCount,
required this.scriptWidgetCount,
required this.scriptWidgetSubscriptionCount,
});
}
class BackupImportResult {
final int serverCount;
final int sessionCount;
final int danmakuSourceCount;
final int scriptWidgetCount;
final int scriptWidgetSubscriptionCount;
const BackupImportResult({
required this.serverCount,
required this.sessionCount,
required this.danmakuSourceCount,
required this.scriptWidgetCount,
required this.scriptWidgetSubscriptionCount,
});
}
class ExportBackupUseCase {
final ServerRepository serverRepository;
final SessionRepository sessionRepository;
final DanmakuSourcesStore danmakuSourcesStore;
final ScriptWidgetRepository scriptWidgetRepository;
final DateTime Function() now;
ExportBackupUseCase({
required this.serverRepository,
required this.sessionRepository,
required this.danmakuSourcesStore,
required this.scriptWidgetRepository,
DateTime Function()? now,
}) : now = (now ?? DateTime.now);
Future<BackupExportResult> execute() async {
final servers = await serverRepository.list();
final sessions = await sessionRepository.listAll();
final danmakuSources = await danmakuSourcesStore.list();
final scriptWidgets = await scriptWidgetRepository.listWidgets();
final scriptWidgetSubscriptions =
await scriptWidgetRepository.listSubscriptions();
final bundle = BackupBundle(
version: kBackupSchemaVersion,
exportedAt: now().toUtc().toIso8601String(),
servers: servers,
sessions: sessions,
danmakuSources: danmakuSources,
scriptWidgets: scriptWidgets,
scriptWidgetSubscriptions: scriptWidgetSubscriptions,
);
final json = const JsonEncoder.withIndent(' ').convert(bundle.toJson());
return BackupExportResult(
json: json,
serverCount: servers.length,
sessionCount: sessions.length,
danmakuSourceCount: danmakuSources.length,
scriptWidgetCount: scriptWidgets.length,
scriptWidgetSubscriptionCount: scriptWidgetSubscriptions.length,
);
}
}
class ImportBackupUseCase {
final ServerRepository serverRepository;
final SessionRepository sessionRepository;
final DanmakuSourcesStore danmakuSourcesStore;
final ScriptWidgetRepository scriptWidgetRepository;
ImportBackupUseCase({
required this.serverRepository,
required this.sessionRepository,
required this.danmakuSourcesStore,
required this.scriptWidgetRepository,
});
Future<BackupImportResult> execute(String rawJson) async {
final BackupBundle bundle;
try {
final decoded = jsonDecode(rawJson);
if (decoded is! Map<String, dynamic>) {
throw DomainError(ErrorCode.invalidParams, '备份文件格式不正确');
}
bundle = BackupBundle.fromJson(decoded);
} on DomainError {
rethrow;
} catch (e) {
throw DomainError(
ErrorCode.invalidParams,
'备份文件解析失败',
details: {'reason': e.toString()},
);
}
if (bundle.version != kBackupSchemaVersion) {
throw DomainError(
ErrorCode.invalidParams,
'不支持的备份文件版本:${bundle.version}',
details: {'expected': kBackupSchemaVersion, 'got': bundle.version},
);
}
for (final server in bundle.servers) {
await serverRepository.update(server);
}
await sessionRepository.setMany(bundle.sessions);
final mergedSources = mergeDanmakuSourcesByUrl(
local: await danmakuSourcesStore.list(),
remote: bundle.danmakuSources,
);
await danmakuSourcesStore.replaceAll(mergedSources);
for (final scriptWidget in bundle.scriptWidgets) {
await scriptWidgetRepository.upsertWidget(scriptWidget);
}
for (final subscription in bundle.scriptWidgetSubscriptions) {
await scriptWidgetRepository.upsertSubscription(subscription);
}
return BackupImportResult(
serverCount: bundle.servers.length,
sessionCount: bundle.sessions.length,
danmakuSourceCount: mergedSources.length,
scriptWidgetCount: bundle.scriptWidgets.length,
scriptWidgetSubscriptionCount: bundle.scriptWidgetSubscriptions.length,
);
}
}
List<DanmakuSource> mergeDanmakuSourcesByUrl({
required List<DanmakuSource> local,
required List<DanmakuSource> remote,
}) {
final localByUrl = <String, DanmakuSource>{
for (final s in local) s.url.trim(): s,
};
final out = <DanmakuSource>[];
final seen = <String>{};
for (final r in remote) {
final url = r.url.trim();
if (url.isEmpty || seen.contains(url)) continue;
seen.add(url);
final hit = localByUrl[url];
if (hit != null) {
out.add(hit.copyWith(name: hit.name.trim().isEmpty ? r.name : hit.name));
} else {
out.add(
DanmakuSource(
id: r.id.trim().isEmpty
? 'src_${url.hashCode.toUnsigned(32).toRadixString(16)}'
: r.id,
name: r.name,
url: url,
),
);
}
}
return out;
}