180 lines
4.8 KiB
Dart
180 lines
4.8 KiB
Dart
|
|
import 'package:uuid/uuid.dart';
|
||
|
|
|
||
|
|
import '../../contracts/contracts.dart';
|
||
|
|
import '../errors.dart';
|
||
|
|
import '../ports/emby_gateway.dart';
|
||
|
|
import '../ports/server_repository.dart';
|
||
|
|
import '../ports/session_repository.dart';
|
||
|
|
|
||
|
|
const _uuid = Uuid();
|
||
|
|
|
||
|
|
class ListServersUseCase {
|
||
|
|
final ServerRepository serverRepository;
|
||
|
|
ListServersUseCase({required this.serverRepository});
|
||
|
|
|
||
|
|
Future<List<EmbyServer>> execute() => serverRepository.list();
|
||
|
|
}
|
||
|
|
|
||
|
|
class ReorderServersUseCase {
|
||
|
|
final ServerRepository serverRepository;
|
||
|
|
|
||
|
|
ReorderServersUseCase({required this.serverRepository});
|
||
|
|
|
||
|
|
Future<void> execute(List<EmbyServer> orderedServers) {
|
||
|
|
return serverRepository.replaceAll(orderedServers);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class ProbeServerUseCase {
|
||
|
|
final EmbyGateway embyGateway;
|
||
|
|
ProbeServerUseCase({required this.embyGateway});
|
||
|
|
|
||
|
|
Future<ProbePublicInfoRes> execute(EmbyServerProbeReq input) async {
|
||
|
|
final baseUrl = input.baseUrl.trim();
|
||
|
|
if (baseUrl.isEmpty) {
|
||
|
|
throw DomainError(ErrorCode.invalidParams, '服务器地址不能为空');
|
||
|
|
}
|
||
|
|
return embyGateway.probePublicInfo(baseUrl);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class SaveServerUseCase {
|
||
|
|
final ServerRepository serverRepository;
|
||
|
|
final DateTime Function() now;
|
||
|
|
final String Function() idGenerator;
|
||
|
|
|
||
|
|
SaveServerUseCase({
|
||
|
|
required this.serverRepository,
|
||
|
|
DateTime Function()? now,
|
||
|
|
String Function()? idGenerator,
|
||
|
|
}) : now = (now ?? DateTime.now),
|
||
|
|
idGenerator = (idGenerator ?? _defaultIdGenerator);
|
||
|
|
|
||
|
|
static String _defaultIdGenerator() => _uuid.v4();
|
||
|
|
|
||
|
|
Future<EmbyServer> execute(EmbyServerSaveReq input) async {
|
||
|
|
final name = input.name.trim();
|
||
|
|
if (name.isEmpty) {
|
||
|
|
throw DomainError(ErrorCode.invalidParams, '服务器名称不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
final baseUrl = _normalizeBaseUrl(input.baseUrl);
|
||
|
|
final nowIso = now().toUtc().toIso8601String();
|
||
|
|
|
||
|
|
final duplicated = await serverRepository.findByBaseUrl(baseUrl);
|
||
|
|
if (duplicated != null && duplicated.id != input.id) {
|
||
|
|
throw DomainError(
|
||
|
|
ErrorCode.storeConflict,
|
||
|
|
'服务器地址已存在',
|
||
|
|
details: {'baseUrl': baseUrl},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
final iconUrl = input.iconUrl.trim();
|
||
|
|
|
||
|
|
if (input.id != null) {
|
||
|
|
final current = await serverRepository.findById(input.id!);
|
||
|
|
if (current == null) {
|
||
|
|
throw DomainError(
|
||
|
|
ErrorCode.storeNotFound,
|
||
|
|
'服务器不存在',
|
||
|
|
details: {'id': input.id},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return serverRepository.update(
|
||
|
|
current.copyWith(
|
||
|
|
name: name,
|
||
|
|
baseUrl: baseUrl,
|
||
|
|
updatedAt: nowIso,
|
||
|
|
iconUrl: iconUrl,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return serverRepository.create(
|
||
|
|
EmbyServer(
|
||
|
|
id: idGenerator(),
|
||
|
|
name: name,
|
||
|
|
baseUrl: baseUrl,
|
||
|
|
createdAt: nowIso,
|
||
|
|
updatedAt: nowIso,
|
||
|
|
iconUrl: iconUrl,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
String _normalizeBaseUrl(String raw) {
|
||
|
|
final trimmed = raw.trim();
|
||
|
|
if (trimmed.isEmpty) {
|
||
|
|
throw DomainError(ErrorCode.invalidParams, '服务器地址不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
Uri parsed;
|
||
|
|
try {
|
||
|
|
parsed = Uri.parse(trimmed);
|
||
|
|
} catch (_) {
|
||
|
|
throw DomainError(
|
||
|
|
ErrorCode.invalidParams,
|
||
|
|
'服务器地址格式不正确',
|
||
|
|
details: {'baseUrl': raw},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (parsed.scheme != 'http' && parsed.scheme != 'https') {
|
||
|
|
throw DomainError(
|
||
|
|
ErrorCode.invalidParams,
|
||
|
|
'仅支持 HTTP/HTTPS 协议',
|
||
|
|
details: {'baseUrl': raw},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
final origin =
|
||
|
|
'${parsed.scheme}://${parsed.host}'
|
||
|
|
'${parsed.hasPort ? ':${parsed.port}' : ''}';
|
||
|
|
final cleanPath = parsed.path.replaceAll(RegExp(r'/+$'), '');
|
||
|
|
final normalizedPath = cleanPath.isEmpty ? '/' : cleanPath;
|
||
|
|
|
||
|
|
if (RegExp(r'/emby$', caseSensitive: false).hasMatch(normalizedPath)) {
|
||
|
|
return '$origin$normalizedPath';
|
||
|
|
}
|
||
|
|
if (normalizedPath == '/') {
|
||
|
|
return '$origin/emby';
|
||
|
|
}
|
||
|
|
return '$origin$normalizedPath/emby';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class TogglePauseServerUseCase {
|
||
|
|
final ServerRepository serverRepository;
|
||
|
|
|
||
|
|
TogglePauseServerUseCase({required this.serverRepository});
|
||
|
|
|
||
|
|
Future<EmbyServer> execute(String id) async {
|
||
|
|
final current = await serverRepository.findById(id);
|
||
|
|
if (current == null) {
|
||
|
|
throw DomainError(ErrorCode.storeNotFound, '服务器不存在', details: {'id': id});
|
||
|
|
}
|
||
|
|
return serverRepository.update(current.copyWith(paused: !current.paused));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class DeleteServerUseCase {
|
||
|
|
final ServerRepository serverRepository;
|
||
|
|
final SessionRepository sessionRepository;
|
||
|
|
|
||
|
|
DeleteServerUseCase({
|
||
|
|
required this.serverRepository,
|
||
|
|
required this.sessionRepository,
|
||
|
|
});
|
||
|
|
|
||
|
|
Future<void> execute(String id) async {
|
||
|
|
final exists = await serverRepository.findById(id);
|
||
|
|
if (exists == null) {
|
||
|
|
throw DomainError(ErrorCode.storeNotFound, '服务器不存在', details: {'id': id});
|
||
|
|
}
|
||
|
|
await serverRepository.deleteById(id);
|
||
|
|
await sessionRepository.clear(id);
|
||
|
|
}
|
||
|
|
}
|