Skip to content

Commit

Permalink
Fix up playlist metadata a bit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Komodo5197 committed May 19, 2024
1 parent 13cc3f6 commit 7f75555
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 92 deletions.
24 changes: 6 additions & 18 deletions lib/components/AlbumScreen/download_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,17 @@ class DownloadButton extends ConsumerWidget {
required this.item,
this.children,
this.isLibrary = false,
this.infoOnly = false,
});

final DownloadStub item;
final int? children;
final bool isLibrary;
final bool infoOnly;

@override
Widget build(BuildContext context, WidgetRef ref) {
final downloadsService = GetIt.instance<DownloadsService>();
DownloadItemStatus? status;
if (infoOnly) {
status = (ref.watch(downloadsService.infoForAnchorProvider(item)).value ??
false)
? DownloadItemStatus.required
: DownloadItemStatus.notNeeded;
} else {
status =
ref.watch(downloadsService.statusProvider((item, children))).value;
}
DownloadItemStatus? status =
ref.watch(downloadsService.statusProvider((item, children))).value;
var isOffline = ref.watch(finampSettingsProvider
.select((value) => value.valueOrNull?.isOffline)) ??
true;
Expand Down Expand Up @@ -77,13 +67,12 @@ class DownloadButton extends ConsumerWidget {
AppLocalizations.of(context)!.addButtonLabel,
abortButtonText:
MaterialLocalizations.of(context).cancelButtonLabel,
onConfirmed: () => DownloadDialog.show(
context, item, viewId,
infoOnly: infoOnly),
onConfirmed: () =>
DownloadDialog.show(context, item, viewId),
onAborted: () {},
));
} else {
await DownloadDialog.show(context, item, viewId, infoOnly: infoOnly);
await DownloadDialog.show(context, item, viewId);
}
},
tooltip: parentTooltip,
Expand All @@ -106,8 +95,7 @@ class DownloadButton extends ConsumerWidget {
AppLocalizations.of(context)!.deleteDownloadsAbortButtonText,
onConfirmed: () async {
try {
await downloadsService.deleteDownload(
stub: item, asInfo: infoOnly);
await downloadsService.deleteDownload(stub: item);
GlobalSnackbar.message((scaffold) =>
AppLocalizations.of(scaffold)!.downloadsDeleted);
} catch (error) {
Expand Down
12 changes: 2 additions & 10 deletions lib/components/AlbumScreen/download_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,13 @@ class DownloadDialog extends StatefulWidget {
required this.downloadLocationId,
required this.needsTranscode,
required this.children,
required this.infoOnly,
});

final DownloadStub item;
final String viewId;
final String? downloadLocationId;
final bool needsTranscode;
final List<BaseItemDto>? children;
final bool infoOnly;

@override
State<DownloadDialog> createState() => _DownloadDialogState();
Expand Down Expand Up @@ -80,11 +78,7 @@ class DownloadDialog extends StatefulWidget {
(scaffold) => AppLocalizations.of(scaffold)!.confirmDownloadStarted,
isConfirmation: true);
unawaited(downloadsService
.addDownload(
stub: item,
viewId: viewId!,
transcodeProfile: profile,
asInfo: infoOnly)
.addDownload(stub: item, viewId: viewId!, transcodeProfile: profile)
// TODO only show the enqueued confirmation if the enqueuing took longer than ~10 seconds
.then((value) => GlobalSnackbar.message(
(scaffold) => AppLocalizations.of(scaffold)!.downloadsQueued)));
Expand All @@ -108,7 +102,6 @@ class DownloadDialog extends StatefulWidget {
downloadLocationId: downloadLocation,
needsTranscode: needTranscode,
children: children,
infoOnly: infoOnly,
),
);
}
Expand Down Expand Up @@ -229,8 +222,7 @@ class _DownloadDialogState extends State<DownloadDialog> {
.addDownload(
stub: widget.item,
viewId: widget.viewId,
transcodeProfile: profile,
asInfo: widget.infoOnly)
transcodeProfile: profile)
.onError(
(error, stackTrace) => GlobalSnackbar.error(error));

Expand Down
2 changes: 1 addition & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,7 @@
},
"description": "Tooltip for downloadbutton on incidental downloads, which show a lock icon. It says one of the requiring downloads."
},
"finampCollectionNames": "{itemType, select, favorites{Favorites} allPlaylists{All Playlists} fiveLatestAlbums{5 Latest Albums} other{{itemType}} }",
"finampCollectionNames": "{itemType, select, favorites{Favorites} allPlaylists{All Playlists} fiveLatestAlbums{5 Latest Albums} allPlaylistsMetadata{Info for All Playlists} other{{itemType}} }",
"@finampCollectionNames": {
"placeholders": {
"itemType": {
Expand Down
7 changes: 6 additions & 1 deletion lib/models/finamp_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,8 @@ enum FinampCollectionType {
favorites,
allPlaylists,
latest5Albums,
libraryImages;
libraryImages,
allPlaylistsMetadata;
}

@JsonSerializable(
Expand All @@ -1929,6 +1930,7 @@ class FinampCollection {
FinampCollectionType.latest5Albums => "5 Latest Albums",
FinampCollectionType.libraryImages =>
"Cache Library Images:${library!.id}",
FinampCollectionType.allPlaylistsMetadata => "All Playlists Metadata",
};

String getName(BuildContext context) => switch (type) {
Expand All @@ -1940,6 +1942,9 @@ class FinampCollection {
.finampCollectionNames("fiveLatestAlbums"),
FinampCollectionType.libraryImages => AppLocalizations.of(context)!
.cacheLibraryImagesName(library!.name ?? ""),
FinampCollectionType.allPlaylistsMetadata =>
AppLocalizations.of(context)!
.finampCollectionNames("allPlaylistsMetadata"),
};

factory FinampCollection.fromJson(Map<String, dynamic> json) =>
Expand Down
1 change: 1 addition & 0 deletions lib/models/finamp_models.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions lib/screens/downloads_settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ class DownloadsSettingsScreen extends StatelessWidget {
subtitle: Text(
AppLocalizations.of(context)!.allPlaylistsInfoSettingSubtitle),
trailing: DownloadButton(
infoOnly: true,
item: DownloadStub.fromFinampCollection(
FinampCollection(type: FinampCollectionType.allPlaylists))),
item: DownloadStub.fromFinampCollection(FinampCollection(
type: FinampCollectionType.allPlaylistsMetadata))),
),
// Do not limit enqueued downloads on IOS, it throttles them like crazy on its own.
if (!Platform.isIOS) const ConcurentDownloadsSelector(),
Expand Down Expand Up @@ -144,6 +143,10 @@ class SyncFavoritesSwitch extends StatelessWidget {
box.get("FinampSettings")!;
finampSettingsTemp.trackOfflineFavorites = value;
box.put("FinampSettings", finampSettingsTemp);
if (value) {
final isarDownloader = GetIt.instance<DownloadsService>();
isarDownloader.resyncAll();
}
},
);
},
Expand Down
116 changes: 70 additions & 46 deletions lib/services/downloads_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,6 @@ class DownloadsService {
required DownloadStub stub,
required String viewId,
required DownloadProfile transcodeProfile,
bool asInfo = false,
}) async {
// Comment https://github.com/jmshrv/finamp/issues/134#issuecomment-1563441355
// suggests this does not make a request and always returns failure
Expand All @@ -455,11 +454,7 @@ class DownloadsService {
var anchorItem = _anchor.asItem(null);
// This may be the first download ever, so the anchor might not be present
_isar.downloadItems.putSync(anchorItem);
if (asInfo) {
anchorItem.info.updateSync(link: [canonItem]);
} else {
anchorItem.requires.updateSync(link: [canonItem]);
}
anchorItem.requires.updateSync(link: [canonItem]);
// Update download location id/transcode profile for all our children
syncItemDownloadSettings(canonItem);
});
Expand All @@ -470,8 +465,7 @@ class DownloadsService {
/// Removes the anchor link to an item and sync deletes it. This will allow the
/// item to be deleted but may not result in deletion actually occurring as the
/// item may be required by other collections.
Future<void> deleteDownload(
{required DownloadStub stub, bool asInfo = false}) async {
Future<void> deleteDownload({required DownloadStub stub}) async {
DownloadItem? canonItem;
_isar.writeTxnSync(() {
var anchorItem = _anchor.asItem(null);
Expand All @@ -485,11 +479,7 @@ class DownloadsService {
_isar.downloadItems.putSync(anchorItem);
deleteBuffer.addAll([stub.isarId]);
// Actual item is not required for updating links
if (asInfo) {
anchorItem.info.updateSync(unlink: [canonItem!]);
} else {
anchorItem.requires.updateSync(unlink: [canonItem!]);
}
anchorItem.requires.updateSync(unlink: [canonItem!]);
canonItem!.userTranscodingProfile = null;
_isar.downloadItems.putSync(canonItem!);
});
Expand Down Expand Up @@ -914,9 +904,14 @@ class DownloadsService {
.distinctByState()
.stateProperty()
.findAllSync());
// add dependency on image in info links
childStates.addAll(
item.info.filter().distinctByState().stateProperty().findAllSync());
// playlist metadata info links collections which are not nessecarilly downloaded,
// and should still be considered downloaded.
if (item.finampCollection?.type !=
FinampCollectionType.allPlaylistsMetadata) {
// add dependency on image in info links
childStates.addAll(
item.info.filter().distinctByState().stateProperty().findAllSync());
}
}
if (childStates.contains(DownloadItemState.notDownloaded)) {
return updateItemState(item, DownloadItemState.notDownloaded);
Expand Down Expand Up @@ -1486,52 +1481,81 @@ class DownloadsService {
Future<int> getFileSize(DownloadStub item) async {
var canonItem = await _isar.downloadItems.get(item.isarId);
if (canonItem == null) return 0;
return _getFileSize(canonItem, {});
Set<DownloadItem> info = {};
Set<DownloadItem> required = {};
_getFileChildren(canonItem, required, info, true);
info = info.difference(required);
int size = 0;
for (var item in required) {
size += await _getFileSize(item, true);
}
for (var item in info) {
size += await _getFileSize(item, false);
}
return size;
}

/// Recursive subcomponent of [getFileSize].
Future<int> _getFileSize(
DownloadItem item, Set<DownloadStub> completed) async {
if (completed.contains(item)) {
return 0;
void _getFileChildren(DownloadItem item, Set<DownloadItem> required,
Set<DownloadItem> info, bool isRequired) {
if (required.contains(item) || (info.contains(item) && !isRequired)) {
return;
} else {
completed.add(item);
if (isRequired) {
required.add(item);
} else {
info.add(item);
}
}
int size = 0;
var children = item.requires.filter().findAllSync();
for (var child in children) {
size += await _getFileSize(child, completed);
if (isRequired || item.type == DownloadItemType.song) {
var children = item.requires.filter().findAllSync();
for (var child in children) {
_getFileChildren(child, required, info, isRequired);
}
}
if (isRequired || item.type != DownloadItemType.song) {
var children = item.info.filter().findAllSync();
for (var child in children) {
_getFileChildren(child, required, info, false);
}
}
if (item.type == DownloadItemType.song && item.state.isComplete) {
}

/// Recursive subcomponent of [getFileSize].
Future<int> _getFileSize(DownloadItem item, bool required) async {
if (item.type == DownloadItemType.song &&
item.state.isComplete &&
required) {
if (item.fileTranscodingProfile == null ||
item.fileTranscodingProfile!.codec !=
FinampTranscodingCodec.original ||
item.baseItem?.mediaSources == null) {
var statSize =
await item.file?.stat().then((value) => value.size).catchError((e) {
_downloadsLogger.fine(
"No file for song ${item.name} when calculating size.");
return 0;
}) ??
0;
size += statSize;
return await item.file
?.stat()
.then((value) => value.size)
.catchError((e) {
_downloadsLogger
.fine("No file for song ${item.name} when calculating size.");
return 0;
}) ??
0;
} else {
size += item.baseItem?.mediaSources?[0].size ?? 0;
return item.baseItem?.mediaSources?[0].size ?? 0;
}
}
if (item.type == DownloadItemType.image &&
item.state == DownloadItemState.complete) {
var statSize =
await item.file?.stat().then((value) => value.size).catchError((e) {
_downloadsLogger.fine(
"No file for image ${item.name} when calculating size.");
return 0;
}) ??
0;
size += statSize;
return await item.file
?.stat()
.then((value) => value.size)
.catchError((e) {
_downloadsLogger
.fine("No file for image ${item.name} when calculating size.");
return 0;
}) ??
0;
}

return size;
return 0;
}

/// Returns the download status of an item. This is whether the associated item
Expand Down
19 changes: 7 additions & 12 deletions lib/services/downloads_service_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1014,32 +1014,26 @@ class DownloadsSyncService {
.requiredBy((q) => q.isarIdEqualTo(parent.isarId))
.findAllSync();
requiredChildren.addAll(children);
var infoItems = _isar.downloadItems
.filter()
.infoFor((q) => q.isarIdEqualTo(parent.isarId))
.findAllSync();
// If trackOfflineFavorites is set and we have downloads, add an info link
// to the favorites collection, otherwise remove it.
var favorites = FinampCollection(type: FinampCollectionType.favorites);
infoItems.removeWhere((item) => item.id == favorites.id);
infoChildren.addAll(infoItems);
if (children.isNotEmpty &&
FinampSettingsHelper.finampSettings.trackOfflineFavorites) {
infoChildren.add(DownloadStub.fromFinampCollection(
FinampCollection(type: FinampCollectionType.favorites)));
}
infoChildren.addAll(infoItems);
updateRequiredChildren = false;
case DownloadItemType.finampCollection:
try {
if (asRequired) {
orderedChildItems = await _getFinampCollectionChildren(parent);
requiredChildren.addAll(orderedChildItems);
if (parent.finampCollection!.type ==
FinampCollectionType.allPlaylistsMetadata) {
infoChildren.addAll(orderedChildItems);
} else {
requiredChildren.addAll(orderedChildItems);
}
} else {
switch (parent.finampCollection!.type) {
case FinampCollectionType.allPlaylists:
orderedChildItems = await _getFinampCollectionChildren(parent);
infoChildren.addAll(orderedChildItems);
case FinampCollectionType.favorites:
orderedChildItems = await _getFinampCollectionChildren(parent);
case var type:
Expand Down Expand Up @@ -1373,6 +1367,7 @@ class DownloadsSyncService {
) ??
[]);
case FinampCollectionType.allPlaylists:
case FinampCollectionType.allPlaylistsMetadata:
outputItems = await _jellyfinApiData.getItems(
includeItemTypes: "Playlist",
) ??
Expand Down
2 changes: 1 addition & 1 deletion lib/services/favorite_provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7f75555

Please sign in to comment.