feat(flutter/cache): drift schema + new deps + CI codegen step (#357 plan B)
Adds the offline-mode foundation: - pubspec deps: drift, drift_flutter, sqlite3_flutter_libs, connectivity_plus + drift_dev/build_runner (dev) - lib/cache/db.dart with 8 tables: CachedArtists/Albums/Tracks/Likes/ Playlists/PlaylistTracks + AudioCacheIndex + SyncMetadata - *.g.dart added to .gitignore (build_runner regenerates per build) - CI workflow gains a 'Codegen (drift)' step between pub get and analyze so the generated symbols exist when analyze runs CacheSource enum drives tiered LRU eviction (manual/autoLiked/ autoPlaylist/autoPrefetch/incidental). Subsequent commits add the sync controller, audio cache manager, prefetcher, settings UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
// Drift database for Minstrel's offline cache (#357 plan B).
|
||||
//
|
||||
// Two cache layers in one database:
|
||||
// - Metadata cache (CachedArtists, CachedAlbums, CachedTracks,
|
||||
// CachedLikes, CachedPlaylists, CachedPlaylistTracks) — populated
|
||||
// by SyncController via /api/library/sync
|
||||
// - Audio cache index (AudioCacheIndex) — owned by AudioCacheManager
|
||||
//
|
||||
// Plus SyncMetadata holding the latest sync cursor.
|
||||
//
|
||||
// All entity ids are TEXT (server-side UUIDs serialized as strings).
|
||||
// build_runner generates db.g.dart from this file; it's gitignored and
|
||||
// regenerated in CI.
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
|
||||
part 'db.g.dart';
|
||||
|
||||
class CachedArtists extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get sortName => text()();
|
||||
TextColumn get mbid => text().nullable()();
|
||||
TextColumn get artistThumbPath => text().nullable()();
|
||||
TextColumn get artistFanartPath => text().nullable()();
|
||||
DateTimeColumn get fetchedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
class CachedAlbums extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get artistId => text()();
|
||||
TextColumn get title => text()();
|
||||
TextColumn get sortTitle => text()();
|
||||
TextColumn get releaseDate => text().nullable()();
|
||||
TextColumn get coverPath => text().nullable()();
|
||||
TextColumn get mbid => text().nullable()();
|
||||
DateTimeColumn get fetchedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
class CachedTracks extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get albumId => text()();
|
||||
TextColumn get artistId => text()();
|
||||
TextColumn get title => text()();
|
||||
IntColumn get durationMs => integer().withDefault(const Constant(0))();
|
||||
IntColumn get trackNumber => integer().nullable()();
|
||||
IntColumn get discNumber => integer().nullable()();
|
||||
TextColumn get filePath => text().nullable()();
|
||||
TextColumn get fileFormat => text().nullable()();
|
||||
TextColumn get genre => text().nullable()();
|
||||
DateTimeColumn get fetchedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
class CachedLikes extends Table {
|
||||
TextColumn get userId => text()();
|
||||
TextColumn get entityType => text()(); // 'track' | 'album' | 'artist'
|
||||
TextColumn get entityId => text()();
|
||||
DateTimeColumn get likedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
@override
|
||||
Set<Column> get primaryKey => {userId, entityType, entityId};
|
||||
}
|
||||
|
||||
class CachedPlaylists extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get userId => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get description => text().withDefault(const Constant(''))();
|
||||
BoolColumn get isPublic => boolean().withDefault(const Constant(false))();
|
||||
TextColumn get coverPath => text().nullable()();
|
||||
IntColumn get trackCount => integer().withDefault(const Constant(0))();
|
||||
IntColumn get durationSec => integer().withDefault(const Constant(0))();
|
||||
DateTimeColumn get fetchedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
class CachedPlaylistTracks extends Table {
|
||||
TextColumn get playlistId => text()();
|
||||
TextColumn get trackId => text()();
|
||||
IntColumn get position => integer().withDefault(const Constant(0))();
|
||||
@override
|
||||
Set<Column> get primaryKey => {playlistId, trackId};
|
||||
}
|
||||
|
||||
/// One row per fully-downloaded audio file. `source` drives tiered LRU
|
||||
/// eviction; `incidental` evicts first, `manual` last.
|
||||
class AudioCacheIndex extends Table {
|
||||
TextColumn get trackId => text()();
|
||||
TextColumn get path => text()();
|
||||
IntColumn get sizeBytes => integer()();
|
||||
DateTimeColumn get cachedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
TextColumn get source => textEnum<CacheSource>()();
|
||||
@override
|
||||
Set<Column> get primaryKey => {trackId};
|
||||
}
|
||||
|
||||
/// Single-row table holding the latest sync cursor.
|
||||
class SyncMetadata extends Table {
|
||||
IntColumn get id => integer().withDefault(const Constant(1))();
|
||||
IntColumn get cursor => integer().withDefault(const Constant(0))();
|
||||
DateTimeColumn get lastSyncAt => dateTime().nullable()();
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
enum CacheSource { manual, autoLiked, autoPlaylist, autoPrefetch, incidental }
|
||||
|
||||
@DriftDatabase(tables: [
|
||||
CachedArtists,
|
||||
CachedAlbums,
|
||||
CachedTracks,
|
||||
CachedLikes,
|
||||
CachedPlaylists,
|
||||
CachedPlaylistTracks,
|
||||
AudioCacheIndex,
|
||||
SyncMetadata,
|
||||
])
|
||||
class AppDb extends _$AppDb {
|
||||
AppDb([QueryExecutor? e]) : super(e ?? driftDatabase(name: 'minstrel_cache'));
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
}
|
||||
Reference in New Issue
Block a user