mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-09 15:38:13 -07:00
feature: Re-implemented syncing
This commit is contained in:
parent
c5c7f71b84
commit
86ff355e21
51 changed files with 3067 additions and 1147 deletions
|
|
@ -45,6 +45,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
|||
final String? seriesName;
|
||||
final int season;
|
||||
final int episode;
|
||||
final int? episodeEnd;
|
||||
final List<Chapter> chapters;
|
||||
final ItemLocation? location;
|
||||
final DateTime? dateAired;
|
||||
|
|
@ -52,6 +53,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
|||
required this.seriesName,
|
||||
required this.season,
|
||||
required this.episode,
|
||||
required this.episodeEnd,
|
||||
this.chapters = const [],
|
||||
this.location,
|
||||
this.dateAired,
|
||||
|
|
@ -134,12 +136,26 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
|||
String seasonAnnotation(BuildContext context) => context.localized.season(1)[0];
|
||||
String episodeAnnotation(BuildContext context) => context.localized.episode(1)[0];
|
||||
|
||||
int get episodeCount {
|
||||
if (episodeEnd != null && episodeEnd! > episode) {
|
||||
return episodeEnd! - episode + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
String get episodeRange {
|
||||
if (episodeEnd != null && episodeEnd! > episode) {
|
||||
return "$episode-${episodeEnd!}";
|
||||
}
|
||||
return episode.toString();
|
||||
}
|
||||
|
||||
String seasonEpisodeLabel(BuildContext context) {
|
||||
return "${seasonAnnotation(context)}$season - ${episodeAnnotation(context)}$episode";
|
||||
return "${seasonAnnotation(context)}$season - ${episodeAnnotation(context)}$episodeRange";
|
||||
}
|
||||
|
||||
String seasonEpisodeLabelFull(BuildContext context) {
|
||||
return "${context.localized.season(1)} $season - ${context.localized.episode(1)} $episode";
|
||||
return "${context.localized.season(1)} $season - ${context.localized.episode(episodeCount)} $episodeRange";
|
||||
}
|
||||
|
||||
String episodeLabel(BuildContext context) {
|
||||
|
|
@ -147,7 +163,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
|||
}
|
||||
|
||||
String get fullName {
|
||||
return "$episode. $subText";
|
||||
return "$episodeRange. $subText";
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -169,6 +185,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
|||
primaryRatio: item.primaryImageAspectRatio,
|
||||
season: item.parentIndexNumber ?? 0,
|
||||
episode: item.indexNumber ?? 0,
|
||||
episodeEnd: item.indexNumberEnd,
|
||||
location: ItemLocation.fromDto(item.locationType),
|
||||
parentImages: ImagesData.fromBaseItemParent(item, ref),
|
||||
canDelete: item.canDelete,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
|||
static int _$episode(EpisodeModel v) => v.episode;
|
||||
static const Field<EpisodeModel, int> _f$episode =
|
||||
Field('episode', _$episode);
|
||||
static int? _$episodeEnd(EpisodeModel v) => v.episodeEnd;
|
||||
static const Field<EpisodeModel, int> _f$episodeEnd =
|
||||
Field('episodeEnd', _$episodeEnd);
|
||||
static List<Chapter> _$chapters(EpisodeModel v) => v.chapters;
|
||||
static const Field<EpisodeModel, List<Chapter>> _f$chapters =
|
||||
Field('chapters', _$chapters, opt: true, def: const []);
|
||||
|
|
@ -86,6 +89,7 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
|||
#seriesName: _f$seriesName,
|
||||
#season: _f$season,
|
||||
#episode: _f$episode,
|
||||
#episodeEnd: _f$episodeEnd,
|
||||
#chapters: _f$chapters,
|
||||
#location: _f$location,
|
||||
#dateAired: _f$dateAired,
|
||||
|
|
@ -120,6 +124,7 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
|||
seriesName: data.dec(_f$seriesName),
|
||||
season: data.dec(_f$season),
|
||||
episode: data.dec(_f$episode),
|
||||
episodeEnd: data.dec(_f$episodeEnd),
|
||||
chapters: data.dec(_f$chapters),
|
||||
location: data.dec(_f$location),
|
||||
dateAired: data.dec(_f$dateAired),
|
||||
|
|
@ -166,6 +171,7 @@ abstract class EpisodeModelCopyWith<$R, $In extends EpisodeModel, $Out>
|
|||
{String? seriesName,
|
||||
int? season,
|
||||
int? episode,
|
||||
int? episodeEnd,
|
||||
List<Chapter>? chapters,
|
||||
ItemLocation? location,
|
||||
DateTime? dateAired,
|
||||
|
|
@ -209,6 +215,7 @@ class _EpisodeModelCopyWithImpl<$R, $Out>
|
|||
{Object? seriesName = $none,
|
||||
int? season,
|
||||
int? episode,
|
||||
Object? episodeEnd = $none,
|
||||
List<Chapter>? chapters,
|
||||
Object? location = $none,
|
||||
Object? dateAired = $none,
|
||||
|
|
@ -230,6 +237,7 @@ class _EpisodeModelCopyWithImpl<$R, $Out>
|
|||
if (seriesName != $none) #seriesName: seriesName,
|
||||
if (season != null) #season: season,
|
||||
if (episode != null) #episode: episode,
|
||||
if (episodeEnd != $none) #episodeEnd: episodeEnd,
|
||||
if (chapters != null) #chapters: chapters,
|
||||
if (location != $none) #location: location,
|
||||
if (dateAired != $none) #dateAired: dateAired,
|
||||
|
|
@ -253,6 +261,7 @@ class _EpisodeModelCopyWithImpl<$R, $Out>
|
|||
seriesName: data.get(#seriesName, or: $value.seriesName),
|
||||
season: data.get(#season, or: $value.season),
|
||||
episode: data.get(#episode, or: $value.episode),
|
||||
episodeEnd: data.get(#episodeEnd, or: $value.episodeEnd),
|
||||
chapters: data.get(#chapters, or: $value.chapters),
|
||||
location: data.get(#location, or: $value.location),
|
||||
dateAired: data.get(#dateAired, or: $value.dateAired),
|
||||
|
|
|
|||
|
|
@ -50,6 +50,21 @@ class UserData with UserDataMappable {
|
|||
|
||||
Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
|
||||
|
||||
static UserData? determineLastUserData(List<UserData?> data) {
|
||||
return data.where((data) => data != null).reduce((a, b) {
|
||||
final aDate = a?.lastPlayed;
|
||||
final bDate = b?.lastPlayed;
|
||||
|
||||
if (aDate != null && bDate != null) {
|
||||
return aDate.isAfter(bDate) ? a : b;
|
||||
} else if (aDate != null) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
factory UserData.fromMap(Map<String, dynamic> map) => UserDataMapper.fromMap(map);
|
||||
factory UserData.fromJson(String json) => UserDataMapper.fromJson(json);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class SeasonModel extends ItemBaseModel with SeasonModelMappable {
|
|||
}
|
||||
|
||||
@override
|
||||
bool get syncAble => true;
|
||||
bool get syncAble => episodes.isNotEmpty && episodes.any((element) => element.syncAble);
|
||||
|
||||
@override
|
||||
ImagesData? get getPosters => images ?? parentImages;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:developer';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:chopper/chopper.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -26,7 +27,6 @@ import 'package:fladder/profiles/default_profile.dart';
|
|||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/providers/service_provider.dart';
|
||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
|
|
@ -129,7 +129,7 @@ class PlaybackModelHelper {
|
|||
await _createOfflinePlaybackModel(
|
||||
newItem,
|
||||
null,
|
||||
ref.read(syncProvider.notifier).getSyncedItem(newItem),
|
||||
await ref.read(syncProvider.notifier).getSyncedItem(newItem),
|
||||
oldModel: currentModel,
|
||||
);
|
||||
if (newModel == null) return null;
|
||||
|
|
@ -143,10 +143,10 @@ class PlaybackModelHelper {
|
|||
SyncedItem? syncedItem, {
|
||||
PlaybackModel? oldModel,
|
||||
}) async {
|
||||
final ItemBaseModel? syncedItemModel = ref.read(syncProvider.notifier).getItem(syncedItem);
|
||||
final ItemBaseModel? syncedItemModel = syncedItem?.itemModel;
|
||||
if (syncedItemModel == null || syncedItem == null || !syncedItem.dataFile.existsSync()) return null;
|
||||
|
||||
final children = ref.read(syncChildrenProvider(syncedItem));
|
||||
final children = await ref.read(syncProvider.notifier).getChildren(syncedItem);
|
||||
final syncedItems = children.where((element) => element.videoFile.existsSync()).toList();
|
||||
final itemQueue = syncedItems.map((e) => e.createItemModel(ref));
|
||||
|
||||
|
|
@ -174,67 +174,81 @@ class PlaybackModelHelper {
|
|||
final userId = ref.read(userProvider)?.id;
|
||||
if (userId?.isEmpty == true) return null;
|
||||
|
||||
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
|
||||
try {
|
||||
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
|
||||
|
||||
final firstItemToPlay = switch (item) {
|
||||
SeriesModel _ || SeasonModel _ => (queue.whereType<EpisodeModel>().toList().nextUp),
|
||||
_ => item,
|
||||
};
|
||||
|
||||
if (firstItemToPlay == null) return null;
|
||||
|
||||
final fullItem = (await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id)).body;
|
||||
|
||||
if (fullItem == null) return null;
|
||||
|
||||
SyncedItem? syncedItem = ref.read(syncProvider.notifier).getSyncedItem(fullItem);
|
||||
|
||||
final firstItemIsSynced = syncedItem != null && syncedItem.status == SyncStatus.complete;
|
||||
|
||||
final options = {
|
||||
PlaybackType.directStream,
|
||||
PlaybackType.transcode,
|
||||
if (firstItemIsSynced) PlaybackType.offline,
|
||||
};
|
||||
|
||||
if ((showPlaybackOptions || firstItemIsSynced) && context != null) {
|
||||
final playbackType = await showPlaybackTypeSelection(
|
||||
context: context,
|
||||
options: options,
|
||||
);
|
||||
|
||||
if (!context.mounted) return null;
|
||||
|
||||
return switch (playbackType) {
|
||||
PlaybackType.directStream || PlaybackType.transcode => await _createServerPlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
playbackType,
|
||||
oldModel: oldModel,
|
||||
libraryQueue: queue,
|
||||
startPosition: startPosition,
|
||||
),
|
||||
PlaybackType.offline => await _createOfflinePlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
syncedItem,
|
||||
),
|
||||
null => null
|
||||
final firstItemToPlay = switch (item) {
|
||||
SeriesModel _ || SeasonModel _ => (queue.whereType<EpisodeModel>().toList().nextUp),
|
||||
_ => item,
|
||||
};
|
||||
} else {
|
||||
return (await _createServerPlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
PlaybackType.directStream,
|
||||
startPosition: startPosition,
|
||||
oldModel: oldModel,
|
||||
libraryQueue: queue,
|
||||
)) ??
|
||||
await _createOfflinePlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
syncedItem,
|
||||
);
|
||||
|
||||
if (firstItemToPlay == null) return null;
|
||||
|
||||
final fullItem = (await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id)).body;
|
||||
|
||||
if (fullItem == null) return null;
|
||||
|
||||
SyncedItem? syncedItem = await ref.read(syncProvider.notifier).getSyncedItem(fullItem);
|
||||
|
||||
final firstItemIsSynced = syncedItem != null && syncedItem.status == TaskStatus.complete;
|
||||
|
||||
final options = {
|
||||
PlaybackType.directStream,
|
||||
PlaybackType.transcode,
|
||||
if (firstItemIsSynced) PlaybackType.offline,
|
||||
};
|
||||
|
||||
if ((showPlaybackOptions || firstItemIsSynced) && context != null) {
|
||||
final playbackType = await showPlaybackTypeSelection(
|
||||
context: context,
|
||||
options: options,
|
||||
);
|
||||
|
||||
if (!context.mounted) return null;
|
||||
|
||||
return switch (playbackType) {
|
||||
PlaybackType.directStream || PlaybackType.transcode => await _createServerPlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
playbackType,
|
||||
oldModel: oldModel,
|
||||
libraryQueue: queue,
|
||||
startPosition: startPosition,
|
||||
),
|
||||
PlaybackType.offline => await _createOfflinePlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
syncedItem,
|
||||
),
|
||||
null => null
|
||||
};
|
||||
} else {
|
||||
return (await _createServerPlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
PlaybackType.directStream,
|
||||
startPosition: startPosition,
|
||||
oldModel: oldModel,
|
||||
libraryQueue: queue,
|
||||
)) ??
|
||||
await _createOfflinePlaybackModel(
|
||||
fullItem,
|
||||
item.streamModel,
|
||||
syncedItem,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
SyncedItem? syncedItem = await ref.read(syncProvider.notifier).getSyncedItem(item);
|
||||
if (syncedItem != null) {
|
||||
return await _createOfflinePlaybackModel(
|
||||
item,
|
||||
item.streamModel,
|
||||
syncedItem,
|
||||
oldModel: oldModel,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
193
lib/models/syncing/database_item.dart
Normal file
193
lib/models/syncing/database_item.dart
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/chapters_model.dart';
|
||||
import 'package:fladder/models/items/images_models.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/media_segments_model.dart';
|
||||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/models/syncing/sync_item.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
|
||||
part 'database_item.g.dart';
|
||||
|
||||
@TableIndex(name: 'database_id', columns: {#id})
|
||||
class DatabaseItems extends Table {
|
||||
TextColumn get userId => text()();
|
||||
TextColumn get id => text().withLength(min: 1)();
|
||||
BoolColumn get syncing => boolean()();
|
||||
TextColumn get sortName => text().nullable()();
|
||||
TextColumn get parentId => text().nullable()();
|
||||
TextColumn get path => text().nullable()();
|
||||
IntColumn get fileSize => integer().nullable()();
|
||||
TextColumn get videoFileName => text().nullable()();
|
||||
TextColumn get trickPlayModel => text().nullable()();
|
||||
TextColumn get mediaSegments => text().nullable()();
|
||||
TextColumn get images => text().nullable()();
|
||||
TextColumn get chapters => text().nullable()();
|
||||
TextColumn get subtitles => text().nullable()();
|
||||
TextColumn get userData => text().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column<Object>> get primaryKey => {id};
|
||||
}
|
||||
|
||||
@DriftDatabase(tables: [DatabaseItems])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase(this.ref, [QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||
|
||||
final Ref ref;
|
||||
|
||||
String get userId => ref.read(userProvider.select((value) => value?.id ?? ""));
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
|
||||
Future<void> clearDatabase() {
|
||||
return transaction(() async {
|
||||
for (final table in allTables) {
|
||||
await delete(table).go();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Selectable<SyncedItem> getItem(String id) =>
|
||||
(select(databaseItems)..where((tbl) => tbl.id.equals(id) & tbl.userId.equals(userId))).map(databaseConverter);
|
||||
|
||||
Selectable<SyncedItem> getParent(String id) =>
|
||||
(select(databaseItems)..where((tbl) => tbl.parentId.equals(id) & tbl.userId.equals(userId)))
|
||||
.map(databaseConverter);
|
||||
|
||||
Selectable<SyncedItem> get getParentItems =>
|
||||
((select(databaseItems)..where((tbl) => (tbl.parentId.isNull() & 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)]))
|
||||
.map(databaseConverter);
|
||||
|
||||
Future<int> insertItem(SyncedItem item) async {
|
||||
final itemExists = await getItem(item.id).getSingleOrNull();
|
||||
if (itemExists != null) {
|
||||
return (update(databaseItems)..where((tbl) => tbl.id.equals(item.id) & tbl.userId.equals(userId)))
|
||||
.write(toDataBaseItem(item));
|
||||
} else {
|
||||
return into(databaseItems).insert(toDataBaseItem(item));
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SyncedItem>> getNestedChildren(SyncedItem root) async {
|
||||
final itemType = root.createItemModel(ref)?.type;
|
||||
|
||||
if (itemType == null) return [];
|
||||
|
||||
final int maxDepth = switch (itemType) {
|
||||
FladderItemType.episode => 0,
|
||||
FladderItemType.movie => 0,
|
||||
FladderItemType.season => 1,
|
||||
FladderItemType.series => 2,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
final all = <SyncedItem>[];
|
||||
List<SyncedItem> toProcess = [root];
|
||||
|
||||
if (maxDepth == 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (var i = 0; i < maxDepth; i++) {
|
||||
final futures = toProcess.map((item) => getChildren(item.id).get());
|
||||
final resultsList = await Future.wait(futures);
|
||||
|
||||
final children = resultsList.expand((r) => r).toList();
|
||||
|
||||
if (children.isEmpty) break;
|
||||
|
||||
all.addAll(children);
|
||||
toProcess = children;
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
Future<void> insertMultipleEntries(List<SyncedItem> items) async {
|
||||
await batch((batch) {
|
||||
batch.insertAll(
|
||||
databaseItems,
|
||||
items.map(toDataBaseItem),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteAllItems(List<SyncedItem> items) async => await batch((batch) {
|
||||
batch.deleteWhere(databaseItems, (tbl) => tbl.id.isIn(items.map((e) => e.id)));
|
||||
});
|
||||
|
||||
DatabaseItemsCompanion toDataBaseItem(SyncedItem item) {
|
||||
return DatabaseItemsCompanion(
|
||||
id: Value(item.id),
|
||||
parentId: Value(item.parentId),
|
||||
syncing: Value(item.syncing),
|
||||
userId: Value(userId),
|
||||
path: Value(item.path),
|
||||
fileSize: Value(item.fileSize),
|
||||
sortName: Value(item.sortName),
|
||||
videoFileName: Value(item.videoFileName),
|
||||
trickPlayModel: Value(item.fTrickPlayModel != null ? jsonEncode(item.fTrickPlayModel?.toJson()) : null),
|
||||
mediaSegments: Value(item.mediaSegments != null ? jsonEncode(item.mediaSegments?.toJson()) : null),
|
||||
images: Value(item.fImages != null ? jsonEncode(item.fImages?.toJson()) : null),
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
SyncedItem databaseConverter(DatabaseItem dataItem) {
|
||||
final syncedItem = SyncedItem(
|
||||
id: dataItem.id,
|
||||
userId: dataItem.userId,
|
||||
parentId: dataItem.parentId,
|
||||
sortName: dataItem.sortName,
|
||||
syncing: dataItem.syncing,
|
||||
path: dataItem.path,
|
||||
fileSize: dataItem.fileSize,
|
||||
videoFileName: dataItem.videoFileName,
|
||||
fTrickPlayModel:
|
||||
dataItem.trickPlayModel != null ? TrickPlayModel.fromJson(jsonDecode(dataItem.trickPlayModel!)) : null,
|
||||
mediaSegments:
|
||||
dataItem.mediaSegments != null ? MediaSegmentsModel.fromJson(jsonDecode(dataItem.mediaSegments!)) : null,
|
||||
fImages: dataItem.images != null ? ImagesData.fromJson(jsonDecode(dataItem.images!)) : null,
|
||||
fChapters: (dataItem.chapters != null && dataItem.chapters!.isNotEmpty)
|
||||
? (jsonDecode(dataItem.chapters!) as List).map((e) => Chapter.fromJson(e)).toList()
|
||||
: [],
|
||||
subtitles: (dataItem.subtitles != null && dataItem.subtitles!.isNotEmpty)
|
||||
? (jsonDecode(dataItem.subtitles!) as List).map((e) => SubStreamModel.fromJson(e)).toList()
|
||||
: [],
|
||||
userData: dataItem.userData != null ? UserData.fromJson(jsonDecode(dataItem.userData!)) : null,
|
||||
);
|
||||
|
||||
return syncedItem.copyWith(
|
||||
itemModel: syncedItem.createItemModel(ref),
|
||||
);
|
||||
}
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
name: 'syncedDatabase',
|
||||
native: const DriftNativeOptions(
|
||||
databaseDirectory: getApplicationSupportDirectory,
|
||||
),
|
||||
// If you need web support, see https://drift.simonbinder.eu/platforms/web/
|
||||
);
|
||||
}
|
||||
}
|
||||
1035
lib/models/syncing/database_item.g.dart
Normal file
1035
lib/models/syncing/database_item.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,24 +6,6 @@ import 'package:fladder/models/syncing/sync_item.dart';
|
|||
|
||||
part 'i_synced_item.g.dart';
|
||||
|
||||
// extension IsarExtensions on String? {
|
||||
// int get fastHash {
|
||||
// if (this == null) return 0;
|
||||
// var hash = 0xcbf29ce484222325;
|
||||
|
||||
// var i = 0;
|
||||
// while (i < this!.length) {
|
||||
// final codeUnit = this!.codeUnitAt(i++);
|
||||
// hash ^= codeUnit >> 8;
|
||||
// hash *= 0x100000001b3;
|
||||
// hash ^= codeUnit & 0xFF;
|
||||
// hash *= 0x100000001b3;
|
||||
// }
|
||||
|
||||
// return hash;
|
||||
// }
|
||||
// }
|
||||
|
||||
@collection
|
||||
class ISyncedItem {
|
||||
String? userId;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import 'package:fladder/models/items/media_segments_model.dart';
|
|||
import 'package:fladder/models/items/media_streams_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
|
|
@ -44,6 +43,8 @@ class SyncedItem with _$SyncedItem {
|
|||
@Default([]) List<Chapter> fChapters,
|
||||
@Default([]) List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData,
|
||||
// ignore: invalid_annotation_target
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) ItemBaseModel? itemModel,
|
||||
}) = _SyncItem;
|
||||
|
||||
static String trickPlayPath = "TrickPlay";
|
||||
|
|
@ -70,9 +71,9 @@ class SyncedItem with _$SyncedItem {
|
|||
File get videoFile => File(joinAll(["$path", "$videoFileName"]));
|
||||
Directory get directory => Directory(path ?? "");
|
||||
|
||||
SyncStatus get status => switch (videoFile.existsSync()) {
|
||||
true => SyncStatus.complete,
|
||||
_ => SyncStatus.partially,
|
||||
TaskStatus get status => switch (videoFile.existsSync()) {
|
||||
true => TaskStatus.complete,
|
||||
_ => TaskStatus.notFound,
|
||||
};
|
||||
|
||||
String? get taskId => task?.taskId;
|
||||
|
|
@ -103,10 +104,9 @@ class SyncedItem with _$SyncedItem {
|
|||
return true;
|
||||
}
|
||||
|
||||
List<SyncedItem> nestedChildren(WidgetRef ref) => ref.watch(syncChildrenProvider(this));
|
||||
|
||||
List<SyncedItem> getChildren(Ref ref) => ref.read(syncProvider.notifier).getChildren(this);
|
||||
List<SyncedItem> getNestedChildren(Ref ref) => ref.read(syncProvider.notifier).getNestedChildren(this);
|
||||
Future<List<SyncedItem>> getChildren(Ref ref) async => await ref.read(syncProvider.notifier).getChildren(this);
|
||||
Future<List<SyncedItem>> getNestedChildren(Ref ref) async =>
|
||||
await ref.read(syncProvider.notifier).getNestedChildren(this);
|
||||
|
||||
Future<int> get getDirSize async {
|
||||
var files = await directory.list(recursive: true).toList();
|
||||
|
|
@ -158,44 +158,45 @@ class SyncedItem with _$SyncedItem {
|
|||
}
|
||||
}
|
||||
|
||||
enum SyncStatus {
|
||||
complete(
|
||||
Color.fromARGB(255, 141, 214, 58),
|
||||
IconsaxPlusLinear.tick_circle,
|
||||
),
|
||||
partially(
|
||||
Color.fromARGB(255, 221, 135, 23),
|
||||
IconsaxPlusLinear.more_circle,
|
||||
),
|
||||
;
|
||||
|
||||
const SyncStatus(this.color, this.icon);
|
||||
|
||||
final Color color;
|
||||
String label(BuildContext context) {
|
||||
return switch (this) {
|
||||
SyncStatus.partially => context.localized.syncStatusPartially,
|
||||
SyncStatus.complete => context.localized.syncStatusSynced,
|
||||
};
|
||||
}
|
||||
|
||||
final IconData icon;
|
||||
}
|
||||
|
||||
extension StatusExtension on TaskStatus {
|
||||
Color color(BuildContext context) => switch (this) {
|
||||
TaskStatus.enqueued => Colors.blueAccent,
|
||||
TaskStatus.running => Colors.limeAccent,
|
||||
TaskStatus.complete => Colors.limeAccent,
|
||||
TaskStatus.canceled || TaskStatus.notFound || TaskStatus.failed => Theme.of(context).colorScheme.error,
|
||||
TaskStatus.waitingToRetry => Colors.yellowAccent,
|
||||
TaskStatus.paused => Colors.orangeAccent,
|
||||
IconData get icon => switch (this) {
|
||||
TaskStatus.enqueued => IconsaxPlusLinear.calendar_circle,
|
||||
TaskStatus.running => IconsaxPlusLinear.arrow_down_1,
|
||||
TaskStatus.complete => IconsaxPlusLinear.tick_circle,
|
||||
TaskStatus.notFound => IconsaxPlusLinear.warning_2,
|
||||
TaskStatus.failed => IconsaxPlusLinear.tag_cross,
|
||||
TaskStatus.canceled => IconsaxPlusLinear.tag_cross,
|
||||
TaskStatus.waitingToRetry => IconsaxPlusLinear.clock,
|
||||
TaskStatus.paused => IconsaxPlusLinear.pause_circle,
|
||||
};
|
||||
|
||||
Color color(BuildContext context) {
|
||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
return isDarkMode
|
||||
? switch (this) {
|
||||
TaskStatus.enqueued => Colors.blueAccent,
|
||||
TaskStatus.running => Colors.greenAccent,
|
||||
TaskStatus.complete => Colors.limeAccent,
|
||||
TaskStatus.notFound => const Color.fromARGB(255, 221, 135, 23),
|
||||
TaskStatus.canceled || TaskStatus.failed => Theme.of(context).colorScheme.error,
|
||||
TaskStatus.waitingToRetry => Colors.yellowAccent,
|
||||
TaskStatus.paused => Colors.tealAccent,
|
||||
}
|
||||
: switch (this) {
|
||||
TaskStatus.enqueued => Colors.blue,
|
||||
TaskStatus.running => Colors.green,
|
||||
TaskStatus.complete => Colors.lime,
|
||||
TaskStatus.notFound => const Color.fromARGB(255, 221, 135, 23),
|
||||
TaskStatus.canceled || TaskStatus.failed => Theme.of(context).colorScheme.error,
|
||||
TaskStatus.waitingToRetry => Colors.yellow,
|
||||
TaskStatus.paused => Colors.teal,
|
||||
};
|
||||
}
|
||||
|
||||
String name(BuildContext context) => switch (this) {
|
||||
TaskStatus.enqueued => context.localized.syncStatusEnqueued,
|
||||
TaskStatus.running => context.localized.syncStatusRunning,
|
||||
TaskStatus.complete => context.localized.syncStatusComplete,
|
||||
TaskStatus.complete => context.localized.syncStatusSynced,
|
||||
TaskStatus.notFound => context.localized.syncStatusNotFound,
|
||||
TaskStatus.failed => context.localized.syncStatusFailed,
|
||||
TaskStatus.canceled => context.localized.syncStatusCanceled,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ mixin _$SyncedItem {
|
|||
List<Chapter> get fChapters => throw _privateConstructorUsedError;
|
||||
List<SubStreamModel> get subtitles => throw _privateConstructorUsedError;
|
||||
@UserDataJsonSerializer()
|
||||
UserData? get userData => throw _privateConstructorUsedError;
|
||||
UserData? get userData =>
|
||||
throw _privateConstructorUsedError; // ignore: invalid_annotation_target
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ItemBaseModel? get itemModel => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of SyncedItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
|
@ -61,7 +64,9 @@ abstract class $SyncedItemCopyWith<$Res> {
|
|||
ImagesData? fImages,
|
||||
List<Chapter> fChapters,
|
||||
List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData});
|
||||
@UserDataJsonSerializer() UserData? userData,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ItemBaseModel? itemModel});
|
||||
|
||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||
}
|
||||
|
|
@ -96,6 +101,7 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
|
|||
Object? fChapters = null,
|
||||
Object? subtitles = null,
|
||||
Object? userData = freezed,
|
||||
Object? itemModel = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
id: null == id
|
||||
|
|
@ -158,6 +164,10 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
|
|||
? _value.userData
|
||||
: userData // ignore: cast_nullable_to_non_nullable
|
||||
as UserData?,
|
||||
itemModel: freezed == itemModel
|
||||
? _value.itemModel
|
||||
: itemModel // ignore: cast_nullable_to_non_nullable
|
||||
as ItemBaseModel?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +209,9 @@ abstract class _$$SyncItemImplCopyWith<$Res>
|
|||
ImagesData? fImages,
|
||||
List<Chapter> fChapters,
|
||||
List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() UserData? userData});
|
||||
@UserDataJsonSerializer() UserData? userData,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ItemBaseModel? itemModel});
|
||||
|
||||
@override
|
||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||
|
|
@ -233,6 +245,7 @@ class __$$SyncItemImplCopyWithImpl<$Res>
|
|||
Object? fChapters = null,
|
||||
Object? subtitles = null,
|
||||
Object? userData = freezed,
|
||||
Object? itemModel = freezed,
|
||||
}) {
|
||||
return _then(_$SyncItemImpl(
|
||||
id: null == id
|
||||
|
|
@ -295,6 +308,10 @@ class __$$SyncItemImplCopyWithImpl<$Res>
|
|||
? _value.userData
|
||||
: userData // ignore: cast_nullable_to_non_nullable
|
||||
as UserData?,
|
||||
itemModel: freezed == itemModel
|
||||
? _value.itemModel
|
||||
: itemModel // ignore: cast_nullable_to_non_nullable
|
||||
as ItemBaseModel?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -317,7 +334,8 @@ class _$SyncItemImpl extends _SyncItem {
|
|||
this.fImages,
|
||||
final List<Chapter> fChapters = const [],
|
||||
final List<SubStreamModel> subtitles = const [],
|
||||
@UserDataJsonSerializer() this.userData})
|
||||
@UserDataJsonSerializer() this.userData,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false) this.itemModel})
|
||||
: _fChapters = fChapters,
|
||||
_subtitles = subtitles,
|
||||
super._();
|
||||
|
|
@ -369,10 +387,14 @@ class _$SyncItemImpl extends _SyncItem {
|
|||
@override
|
||||
@UserDataJsonSerializer()
|
||||
final UserData? userData;
|
||||
// ignore: invalid_annotation_target
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final ItemBaseModel? itemModel;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SyncedItem(id: $id, syncing: $syncing, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortName: $sortName, fileSize: $fileSize, videoFileName: $videoFileName, mediaSegments: $mediaSegments, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData)';
|
||||
return 'SyncedItem(id: $id, syncing: $syncing, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortName: $sortName, fileSize: $fileSize, videoFileName: $videoFileName, mediaSegments: $mediaSegments, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData, itemModel: $itemModel)';
|
||||
}
|
||||
|
||||
/// Create a copy of SyncedItem
|
||||
|
|
@ -400,7 +422,9 @@ abstract class _SyncItem extends SyncedItem {
|
|||
final ImagesData? fImages,
|
||||
final List<Chapter> fChapters,
|
||||
final List<SubStreamModel> subtitles,
|
||||
@UserDataJsonSerializer() final UserData? userData}) = _$SyncItemImpl;
|
||||
@UserDataJsonSerializer() final UserData? userData,
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
final ItemBaseModel? itemModel}) = _$SyncItemImpl;
|
||||
_SyncItem._() : super._();
|
||||
|
||||
@override
|
||||
|
|
@ -433,7 +457,10 @@ abstract class _SyncItem extends SyncedItem {
|
|||
List<SubStreamModel> get subtitles;
|
||||
@override
|
||||
@UserDataJsonSerializer()
|
||||
UserData? get userData;
|
||||
UserData? get userData; // ignore: invalid_annotation_target
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
ItemBaseModel? get itemModel;
|
||||
|
||||
/// Create a copy of SyncedItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue