二次开发源码提交

This commit is contained in:
2026-05-15 08:52:48 +08:00
parent 4131f7321a
commit eb35a1d067
191 changed files with 34566 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
import 'api_service.dart';
import '../core/utils/app_logger.dart';
import '../core/utils/file_utils.dart';
import '../core/utils/time_flow_decoder.dart';
/// 缩略图缓存条目
class _ThumbCacheEntry {
final String imageUrl;
final DateTime expiresAt;
_ThumbCacheEntry({required this.imageUrl, required this.expiresAt});
bool get isExpired => DateTime.now().isAfter(expiresAt);
}
/// 缩略图服务 — 获取、解码、缓存缩略图 URL
class ThumbnailService {
static ThumbnailService? _instance;
static ThumbnailService get instance {
_instance ??= ThumbnailService._();
return _instance!;
}
ThumbnailService._();
// 内存缓存: fileUri -> cache entry
final Map<String, _ThumbCacheEntry> _urlCache = {};
// 请求去重: fileUri -> in-flight Future
final Map<String, Future<String?>> _inFlightRequests = {};
/// 获取缩略图图片 URL
/// [fileUri] 文件的 relativePath(如 /path/to/file.jpg
/// [contextHint] 可选,加速服务端 DB 查询
Future<String?> getThumbnailUrl({
required String fileUri,
String? contextHint,
}) async {
final cacheKey = fileUri;
// 1. 检查内存缓存
final cached = _urlCache[cacheKey];
if (cached != null && !cached.isExpired) {
return cached.imageUrl;
}
if (cached != null) {
_urlCache.remove(cacheKey);
}
// 2. 检查请求去重
final inFlight = _inFlightRequests[cacheKey];
if (inFlight != null) {
return inFlight;
}
// 3. 发起请求
final future = _fetchThumbnailUrl(fileUri, contextHint);
_inFlightRequests[cacheKey] = future;
try {
return await future;
} finally {
_inFlightRequests.remove(cacheKey);
}
}
Future<String?> _fetchThumbnailUrl(String fileUri, String? contextHint) async {
try {
final uri = FileUtils.toCloudreveUri(fileUri);
final headers = contextHint != null
? <String, dynamic>{'X-Cr-Context-Hint': contextHint}
: null;
// _parseResponse 已提取 ApiResponse.dataresponse 即 {url, obfuscated, expires}
final response = await ApiService.instance.get<Map<String, dynamic>>(
'/file/thumb',
queryParameters: {'uri': uri},
headers: headers,
);
AppLogger.d('ThumbnailService: response for $fileUri = $response');
var url = response['url'] as String? ?? '';
final obfuscated = response['obfuscated'] as bool? ?? false;
final expiresStr = response['expires'] as String?;
// 解码混淆 URL
if (obfuscated && url.isNotEmpty) {
final decoded = TimeFlowDecoder.decodeTimeFlowString(url);
if (decoded == null) {
AppLogger.w('Failed to decode obfuscated thumbnail URL for $fileUri');
return null;
}
url = decoded;
}
if (url.isEmpty) return null;
AppLogger.d('ThumbnailService: resolved URL for $fileUri = $url');
// 解析过期时间,缓存提前 30 秒过期
DateTime expiresAt;
if (expiresStr != null) {
try {
expiresAt = DateTime.parse(expiresStr).subtract(const Duration(seconds: 30));
} catch (_) {
expiresAt = DateTime.now().add(const Duration(minutes: 5));
}
} else {
expiresAt = DateTime.now().add(const Duration(minutes: 5));
}
// 存入内存缓存
_urlCache[fileUri] = _ThumbCacheEntry(
imageUrl: url,
expiresAt: expiresAt,
);
return url;
} catch (e) {
AppLogger.d('ThumbnailService: failed to get thumbnail URL for $fileUri: $e');
return null;
}
}
/// 移除指定文件的缓存 URL
void evictUrl(String fileUri) {
_urlCache.remove(fileUri);
}
/// 清空所有缓存(目录切换时调用)
void clearAll() {
_urlCache.clear();
_inFlightRequests.clear();
}
}