feat: Sync offline/online playback when able (#431)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-08-03 13:35:56 +02:00 committed by GitHub
parent 15ac3566e2
commit 092836328f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 1002 additions and 497 deletions

View file

@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:fladder/models/item_base_model.dart';
@ -17,11 +19,13 @@ import 'package:fladder/providers/user_provider.dart';
part 'database_item.g.dart';
const _databseName = 'syncedDatabase';
@TableIndex(name: 'database_id', columns: {#userId, #id})
class DatabaseItems extends Table {
TextColumn get userId => text().withLength(min: 1)();
TextColumn get id => text().withLength(min: 1)();
BoolColumn get syncing => boolean()();
BoolColumn get syncing => boolean().withDefault(const Constant(false))();
TextColumn get sortName => text().nullable()();
TextColumn get parentId => text().nullable()();
TextColumn get path => text().nullable()();
@ -32,6 +36,7 @@ class DatabaseItems extends Table {
TextColumn get images => text().nullable()();
TextColumn get chapters => text().nullable()();
TextColumn get subtitles => text().nullable()();
BoolColumn get unSyncedData => boolean().withDefault(const Constant(false))();
TextColumn get userData => text().nullable()();
@override
@ -47,14 +52,15 @@ class AppDatabase extends _$AppDatabase {
String get userId => ref.read(userProvider.select((value) => value?.id ?? ""));
@override
int get schemaVersion => 2;
int get schemaVersion => 1;
Future<void> clearDatabase() {
return transaction(() async {
for (final table in allTables) {
await delete(table).go();
}
});
Future<void> clearDatabase() async {
final dbPath = await getApplicationSupportDirectory();
final dbFile = File(p.join(dbPath.path, '$_databseName.sqlite'));
if (await dbFile.exists()) {
await dbFile.delete(recursive: true);
}
}
Selectable<SyncedItem> getItem(String id) =>
@ -69,6 +75,10 @@ class AppDatabase extends _$AppDatabase {
..orderBy([(t) => OrderingTerm(expression: t.sortName)]))
.map(databaseConverter);
Selectable<SyncedItem> get getAllItems => ((select(databaseItems)..where((tbl) => tbl.userId.equals(userId)))
..orderBy([(t) => OrderingTerm(expression: t.sortName)]))
.map(databaseConverter);
Selectable<SyncedItem> getChildren(String parentId) =>
((select(databaseItems)..where((tbl) => (tbl.parentId.equals(parentId) & tbl.userId.equals(userId))))
..orderBy([(t) => OrderingTerm(expression: t.sortName)]))
@ -90,11 +100,9 @@ class AppDatabase extends _$AppDatabase {
if (itemType == null) return [];
final int maxDepth = switch (itemType) {
FladderItemType.episode => 0,
FladderItemType.movie => 0,
FladderItemType.season => 1,
FladderItemType.series => 2,
_ => 1,
_ => 0,
};
final all = <SyncedItem>[];
@ -151,6 +159,7 @@ class AppDatabase extends _$AppDatabase {
chapters: Value(jsonEncode(item.fChapters.map((e) => e.toJson()).toList())),
subtitles: Value(jsonEncode(item.subtitles.map((e) => e.toJson()).toList())),
userData: Value(item.userData != null ? jsonEncode(item.userData?.toJson()) : null),
unSyncedData: Value(item.unSyncedData),
);
}
@ -176,6 +185,7 @@ class AppDatabase extends _$AppDatabase {
? (jsonDecode(dataItem.subtitles!) as List).map((e) => SubStreamModel.fromJson(e)).toList()
: [],
userData: dataItem.userData != null ? UserData.fromJson(jsonDecode(dataItem.userData!)) : null,
unSyncedData: dataItem.unSyncedData,
);
return syncedItem.copyWith(
@ -185,34 +195,11 @@ class AppDatabase extends _$AppDatabase {
static QueryExecutor _openConnection() {
return driftDatabase(
name: 'syncedDatabase',
name: _databseName,
native: const DriftNativeOptions(
databaseDirectory: getApplicationSupportDirectory,
),
// If you need web support, see https://drift.simonbinder.eu/platforms/web/
);
}
@override
MigrationStrategy get migration {
return MigrationStrategy(
onCreate: (Migrator m) {
return m.createAll();
},
onUpgrade: (Migrator m, int from, int to) async {
if (from == 1) {
final allItems = await select(databaseItems).get();
m.deleteTable(databaseItems.actualTableName);
m.createAll();
await batch((batch) {
batch.insertAll(
databaseItems,
allItems,
mode: InsertMode.insertOrReplace,
);
});
}
},
);
}
}