引入部分缓存机制
引入封面图信息 正在进入测试阶段
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
// lib/metadata/metadata_service.dart
|
||||
// ⭐ 移除未使用的 import 'dart:io';
|
||||
import 'package:flutter/foundation.dart'; // ⭐ 添加
|
||||
import 'dart:io'; // ⭐ 添加这一行
|
||||
import 'dart:typed_data'; // ⭐ 如果已经有更好
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../database/song_database.dart';
|
||||
import 'metadata_model.dart';
|
||||
import 'metadata_reader.dart';
|
||||
import 'metadata_normalizer.dart';
|
||||
import 'metadata_cache.dart';
|
||||
import 'file_provider.dart';
|
||||
import '../utils/artwork_helper.dart';
|
||||
|
||||
class MetadataService {
|
||||
static final MetadataService _instance = MetadataService._internal();
|
||||
@@ -14,7 +17,13 @@ class MetadataService {
|
||||
|
||||
final MetadataReader _reader = MetadataReader();
|
||||
final MetadataNormalizer _normalizer = MetadataNormalizer();
|
||||
final MetadataCache _cache = MetadataCache();
|
||||
final MetadataCache _memoryCache = MetadataCache();
|
||||
final SongDatabase _db = SongDatabase();
|
||||
|
||||
String _generateSongKey(String url, int fileSize, int modifiedTime) {
|
||||
final raw = '$url|$fileSize|$modifiedTime';
|
||||
return raw.hashCode.toString();
|
||||
}
|
||||
|
||||
FileProvider _getProvider(String url) {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
@@ -29,11 +38,11 @@ class MetadataService {
|
||||
required String fileId,
|
||||
bool forceRefresh = false,
|
||||
}) async {
|
||||
// 1. 检查缓存
|
||||
// 1. 内存缓存
|
||||
if (!forceRefresh) {
|
||||
final cached = await _cache.get(fileId);
|
||||
final cached = await _memoryCache.get(fileId);
|
||||
if (cached != null && cached.isNotEmpty) {
|
||||
debugPrint('📦 [MetadataService] cache hit: ${cached.title}');
|
||||
debugPrint('📦 [MetadataService] memory hit: ${cached.title}');
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
@@ -43,33 +52,96 @@ class MetadataService {
|
||||
final file = await provider.getFile(url);
|
||||
|
||||
if (file == null || !await file.exists()) {
|
||||
debugPrint(
|
||||
'⚠️ [MetadataService] file not available, using filename fallback');
|
||||
debugPrint('⚠️ [MetadataService] file not available, using fallback');
|
||||
return _fallbackFromFileName(fileName);
|
||||
}
|
||||
|
||||
// 3. 读取 metadata
|
||||
final stat = await file.stat();
|
||||
final songKey =
|
||||
_generateSongKey(url, stat.size, stat.modified.millisecondsSinceEpoch);
|
||||
|
||||
// 3. SQLite 查询(高置信度直接使用)
|
||||
if (!forceRefresh) {
|
||||
final dbSong = await _db.getSong(songKey);
|
||||
if (dbSong != null) {
|
||||
final confidence = (dbSong['confidence'] as num?)?.toDouble() ?? 0.0;
|
||||
if (confidence >= 0.6) {
|
||||
debugPrint(
|
||||
'📦 [MetadataService] SQLite hit: ${dbSong['title']} - ${dbSong['artist']}');
|
||||
|
||||
// 读取封面图
|
||||
final artworkPath = dbSong['artwork_path'] as String?;
|
||||
Uint8List? artwork;
|
||||
if (artworkPath != null && artworkPath.isNotEmpty) {
|
||||
try {
|
||||
final artFile = File(artworkPath);
|
||||
if (await artFile.exists()) {
|
||||
artwork = await artFile.readAsBytes();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
final metadata = FinalMetadata(
|
||||
title: dbSong['title'] as String? ?? '',
|
||||
artist: dbSong['artist'] as String? ?? '',
|
||||
album: dbSong['album'] as String? ?? '',
|
||||
genre: dbSong['genre'] as String? ?? '',
|
||||
artwork: artwork,
|
||||
source: 'database',
|
||||
);
|
||||
await _memoryCache.put(fileId, metadata);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 读取原始 metadata
|
||||
final raw = await _reader.readRawMetadata(file);
|
||||
|
||||
// 4. 标准化
|
||||
// 5. 标准化
|
||||
final normalized = _normalizer.normalize(
|
||||
raw,
|
||||
fileName: fileName,
|
||||
filePath: url,
|
||||
);
|
||||
|
||||
// 5. 评估(第一阶段:直接接受)
|
||||
final history = await _cache.getHistory(normalized.artist, limit: 10);
|
||||
// 6. 评估
|
||||
final history = await _db.getSongsByArtist(normalized.artist, limit: 10);
|
||||
final candidate = _normalizer.evaluate(normalized, history);
|
||||
final finalMetadata = _normalizer.decide(candidate);
|
||||
|
||||
// 6. 缓存
|
||||
if (finalMetadata.isNotEmpty) {
|
||||
await _cache.put(fileId, finalMetadata);
|
||||
debugPrint(
|
||||
'✅ [MetadataService] metadata saved: ${finalMetadata.title} - ${finalMetadata.artist}');
|
||||
// 7. 保存封面图到本地
|
||||
String? artworkPath;
|
||||
if (finalMetadata.artwork != null && finalMetadata.artwork!.isNotEmpty) {
|
||||
artworkPath =
|
||||
await ArtworkHelper.saveArtwork(finalMetadata.artwork!, songKey);
|
||||
}
|
||||
|
||||
// 8. 保存到 SQLite
|
||||
await _db.insertSong({
|
||||
'song_key': songKey,
|
||||
'remote_path': url,
|
||||
'file_size': stat.size,
|
||||
'modified_time': stat.modified.millisecondsSinceEpoch,
|
||||
'title': finalMetadata.title,
|
||||
'artist': finalMetadata.artist,
|
||||
'album': finalMetadata.album,
|
||||
'genre': finalMetadata.genre,
|
||||
'artwork_path': artworkPath,
|
||||
'confidence': candidate.confidence,
|
||||
'validation_count': 1,
|
||||
'first_scan': DateTime.now().millisecondsSinceEpoch,
|
||||
'last_scan': DateTime.now().millisecondsSinceEpoch,
|
||||
'metadata_status': candidate.confidence >= 0.6 ? 'verified' : 'pending',
|
||||
});
|
||||
|
||||
// 9. 内存缓存
|
||||
await _memoryCache.put(fileId, finalMetadata);
|
||||
|
||||
debugPrint(
|
||||
'✅ [MetadataService] saved: ${finalMetadata.title} - ${finalMetadata.artist} (confidence: ${candidate.confidence})');
|
||||
return finalMetadata;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user