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:
@@ -56,6 +56,12 @@ jobs:
|
|||||||
- name: Pub get
|
- name: Pub get
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Codegen (drift, etc.)
|
||||||
|
# build_runner generates *.g.dart files (drift schemas, etc.)
|
||||||
|
# which are gitignored. Must run before analyze + test so the
|
||||||
|
# generated symbols exist.
|
||||||
|
run: dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Analyze
|
- name: Analyze
|
||||||
run: flutter analyze --fatal-infos
|
run: flutter analyze --fatal-infos
|
||||||
|
|
||||||
|
|||||||
@@ -43,3 +43,7 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
# drift codegen output (regenerated by build_runner; CI runs build_runner)
|
||||||
|
*.g.dart
|
||||||
|
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -24,12 +24,18 @@ dependencies:
|
|||||||
pub_semver: ^2.1.4
|
pub_semver: ^2.1.4
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
|
drift: ^2.18.0
|
||||||
|
drift_flutter: ^0.2.0
|
||||||
|
sqlite3_flutter_libs: ^0.5.24
|
||||||
|
connectivity_plus: ^6.0.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
mocktail: ^1.0.4
|
mocktail: ^1.0.4
|
||||||
|
drift_dev: ^2.18.0
|
||||||
|
build_runner: ^2.4.13
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
Reference in New Issue
Block a user