finamp icon indicating copy to clipboard operation
finamp copied to clipboard

[Redesign] Downloads Storage Rewrite

Open Komodo5197 opened this issue 1 year ago • 95 comments

An attempt to implement as much of the planned download rewrite as I can. It's not done yet, but I wanted to get this out there.

Highlights:

  • Stores download metadata via linked items in Isar database.
  • Uses background_downloader for downloads.
  • Supports downloading songs, albums, artists, or genres.
  • Can migrate metadata and files from existing downloads while offline.
  • Can sync to download new songs if playlist/artist/genre updates.
  • Requires Dart 3.0 due to Isar and requires android sdk 24/iOS 13 due to backgound_downloader.

The Design:

All download metadata is stored in a single collection of DownloadItems. Each item has a type, an optional BaseItemDto, a download state, and can require other items of arbitrary type. The primary download action is a sync, which takes a node and recalculates required children according to the node's type, fetching information from the server if needed. Then recently unconnected nodes are considered for deletion if no other nodes require them, and currently connected nodes are recursively synced. Downloads can be tracked via a DownloadStub, a simplified DownloadItem with no state used to key into it's matching DownloadItem.

The DownloadItem types:

  • song - requires its image and artist/album/genre collectionInfo nodes. Can have associated file.
  • image - does not require anything. Can have associated file.
  • anchor - requires user-selected downloads, will never be deleted.
  • collectionDownload - A playlist/album/artist/genre to be downloaded. Requires all elements of the collection, its own collectionInfo, and its image.
  • collectionInfo - Metadata for a playlist/album/artist/genre. Does NOT require all elements of the collection. Songs require this to allow showing album/etc. pages without having it downloaded. Requires its image.

TODO:

  • Implement external storage directories
  • More testing. I don't really have a way to test in anything other than android emulator, so hopefully iOS works.
  • Reimplement the downloads error screen
  • Various UI improvements
  • Improve threading and verify all transactions are fully safe.
  • Investigate more advanced background_downloader features like notifications or pausing
  • Improve user feedback during migration - maybe delaying until after app startup
  • Decide whether to show partially downloaded albums while offline. Should this be a setting?
  • Decide whether to show incomplete/failed song downloads while offline.
  • Code cleanup and documentation.
  • Make most/all download retrieval functions asynchronous.

Komodo5197 avatar Dec 21 '23 06:12 Komodo5197

Oh man, this is amazing. I'm sure @jmshrv will love this too!

Is this ready to take a look at already? Do you need any help or designs for this?
Also, why did you decided to target main with this PR? This is a major (and breaking) change, and with the redesign beta coming up, I'm not sure if this will ever be released before the beta starts. And once we have a beta, I'd like all new features to go into redesign, and only do fixes and backports for stable, to make sure the branches don't diverge too much.

Regarding your TODOs, here are some thoughts:

  • Regarding more advanced downloading features, I think a highly requested one is downloading only over WiFi
  • I think we should show partial collections, there could be a way to filter this to only complete ones. But I wouldn't implement this as a global setting
  • I see no reason to show failed downloads in offline mode, at least not within a collection. They could still be shown on the downloads screen though

Lastly, I'm wondering if the collectionInfo type does include metadata for all items within a collection, even if they're not downloaded? For example, if I have an album with a few downloaded tracks and some that are not downloaded, the missing ones could be grayed-out but still shown. Maybe this only makes sense for certain collections (albums and playlists, maybe artists) but not others (genres, albums/artists/genres tab)?
I think it would be nice to have the full context for i.e. albums in offline mode, and if Isar can handle it, the size of the metadata shouldn't be an issue even for large libraries.
This could be a setting though, in case someone really doesn't have too much storage space available.
What do you think?

Chaphasilor avatar Dec 21 '23 15:12 Chaphasilor

This is absolutely amazing, thanks for making what I couldn't in 3 years :)

I'll install this on my phone now and see how well it works - by the sounds of it, it should be way more reliable than Flutter downloader lol

jmshrv avatar Dec 21 '23 17:12 jmshrv

The actual download/delete/playback functionality seems mostly stable on my end, if you want to try it out. In regards to design help, there's a couple UI questions I haven't made a decision on if you want to weigh in, and of course I'm always open to ideas.

  • With the re-sync and repair downloads buttons on the main download page, the retry failed downloads button seems pointless, especially as the resync currently retries failed downloads without additional prompting. Is there anything else I should be putting there?
  • Expanding a child on the download screen shows songs for album/playlist but album for genre/artist, and you can't delete them because the parent would just re-download them on the next sync. Should I always show songs and never albums? Should I show images? Should I do anything if a song is individually downloaded?
  • How should download button state be handled? My current thoughts are we want a button that is delete if the item is required by the anchor, download otherwise. And then maybe a sync button that shows up if we are required directly or indirectly, but is hidden otherwise, in line with #534. Is just showing download in cases where we are actually downloaded, but would be deleted if a parent collection gets removed confusing, or fine?

I didn't really put much thought into what branch to target. If you want this targeting the redesign, I can do that. I noticed you just merged main into the redesign, so it shouldn't be to bad to merge the redesign into this.

I believe requiring Wifi is just a flag with background_downloder, so I'll throw that in. Regarding partial collection filtering, if there was an option where should I put it?

Regarding showing greyed out album tracks - currently, that info is not downloaded. I could add a songInfo type and pretty easily grab everything for albums. Playlists will never have parent metadata unless they have been downloaded, so all the songs must be there although some may have failed. Persumebly they should still show, but greyed out, if this functionality is added? The metadata size probably doesn't matter, it's tiny compared to the songs even if we double it with a bunch of songInfo nodes, and it seems unlikely for the songs to have many unique images. Grabbing children for artists is messier because it introduces information for empty albums that would need to be filtered out. Also, a note - I'm adding separate copies of metadata in info types because some sort of required flag seems annoying to track and adding some sort of 'info' link types would lead to loops, which I'd rather avoid.

Komodo5197 avatar Dec 22 '23 03:12 Komodo5197

Okay. I did merge main into redesign, but it was a difference of ~500 commits, so I'll test this in the next few days. I'll take a look at this PR afterwards. But yes, please target redesign with this PR, starting next year the stable version will probably be put into maintenance mode so we can focus on the beta.

Regarding your questions:

  • The repair button is nice, but if you remove the "failed downloads" button it would be good to show which items/collections failed to download somewhere, at least for debugging purposes (mainly user debugging, not dev debugging). You could use a long-press handler for now
  • Maybe it would be best and easiest to maintain if you used multiple tabs for the different types, like in the main music screen. Given that all downloaded parent items will always try to download their children, I see no reason why we would need to show child items like songs in an album. This should also handle cases where I downloaded an artist and also a specific album of that artist, and then delete the artist. The album should be on the albums tab on the downloads screen, the artist on the artist tab, and when I delete the artist all the albums are deleted as well until just the one that was manually downloaded is left.
  • I think there should be three or four different download states, where the last one is optional depending on how hard it is to implement:
    1. Not downloaded
    2. Directly downloaded
    3. Downloaded as (transitive) dependency
    4. Downloaded, but out-of-sync (maybe split this into two states for direct and dependency downloads?)
      For now, just implement the functionality and put the following buttons: Download (for state 1), Delete (for state 2), "Persist" (for state 3), Sync (for state 4). I'll think about the design and how to best integrate it into the new UI. Maybe the delete button could also be shown for state 3, and show a dialog explaining why it can't be deleted, and have an option that takes you to the parent.
  • Grayed-out tracks for ordered lists (albums, playlists) would be nice, no need for artists or genres.
  • You could add the partial collections toggle next to the favorites only toggle for now. It's only temporary. Some indicator for partial collections (especially on the downloads screen) would be nice too

Also:

  • If you're feeling adventurous, I'd appreciate a "download all" button, especially with the transcoded downloads in the pipeline. For that it would be nice to have a confirmation prompt that shows an approximate total size. If you don't want to look into it, try to still keep it in mind (might require changes to the "anchor" type?)
  • Be sure to handle failures due to low disk space, that might require some other handling as just retrying
  • I encountered a but where trying to play an item instead deletes the download. This happend when trying to shuffle all songs and playing albums, although one album managed to play. Playlists seem to work fine. Not sure what's going on here? This is still a draft PR though, so no worries. Let me know if you need a repro or logs :)

Chaphasilor avatar Dec 22 '23 10:12 Chaphasilor

@Komodo5197 I asked the dev of the background_downloader package about how downloading over WiFi only should be handled in our case, you can find their reply here: https://github.com/jmshrv/finamp/issues/213#issuecomment-1869734094

Basically, you should add retries: 10 when creating a DownloadTask, so tasks can be restarted on Android after reconnecting to WiFi.
And it might make sense to somehow update waiting download tasks whenever the "download over WiFi only" setting is changed, so that a user could start a collection download, then change the setting, and not burn through their mobile data. This probably requires making use of MemoryTaskQueue.waiting and MemoryTaskQueue.remove(Task task), and then re-creating the DownloadTasks using Task.copyWith({ requiresWiFi: <settingValue> }).
It doesn't have to be perfect, but I'd like to keep users happy :)

Let me know if I should help with implementing the suggested changes!

Chaphasilor avatar Dec 27 '23 15:12 Chaphasilor

I can add retries, although I'll probably keep it smaller like 3-4 instead of 10 because 10 seems a bit excessive, especially when everything we download is so small. Trying to update active downloads with the current setting seems like more effort than the feature is worth.

About downloading libraries, I believe that should be pretty trivial to add by downloading the libraries view BaseItemDto with the same treatment as a genre/artist. For estimating the library size though, I can't think of a particularly good approach. We could request all songs, which is still a decent sized server request, and then just assume each is ~4MB or so, maybe multiply with runtimeTicks, which could be pretty inaccurate. But being more accurate would seem to involve requesting playback info for every song individually. Do you have any better ideas?

Regarding downloads being deleted on play, presumably that's download verification failing, likely due to an incorrect path somewhere. I've already fixed at least one problem in that area, if it happens again with a newer build the logs would be nice. If these were migrated files in a non-internal location, I already know those don't work yet.

Komodo5197 avatar Dec 28 '23 03:12 Komodo5197

I wanted to increase retries in case of a spotty connection, but you're right, since each task is a single download, it should be fine with less retries.
About the updating, I'll see what I can do. If we don't update, we should at least put a disclaimer in the settings saying that toggling it will only apply to the next download.

I didn't find a way to get the item size for a group of items either, which is a bummer. So I guess we'll have to wait for server-side support. We could show the size for transcoded downloads though (once those are working), since there we have a static (maximum) bitrate and can just multiply by runtime ticks.

I'll send you some logs if the issue happens again. I think I do have the logs from last time on my old phone as well. The issue was after a clean install.

Thanks for all your efforts! I'll do more in-depth testing in a few days :)

Chaphasilor avatar Dec 28 '23 10:12 Chaphasilor

@Komodo5197 I'm the author of background_downloader. I'm not familiar with Isar, but since you are using that as the backend storage for your downloads, I suggest to also implement PersistentStorage using the Isar backend (see here). PersistentStorage is not only used to store download state (which you already do using Isar, so you don't need that functionality) but also to store resume information for paused tasks etc. While the default implementation of the interface (LocalStorePersistentStorage) works fine, it stores its data using the raw file system. Since you already have Isar up and running it should be trivial to create an implementation of the interface using Isar, and initialize the downloader with that. Just a suggestion :)

781flyingdutchman avatar Dec 28 '23 18:12 781flyingdutchman

Also, as of background_downloader version 8.0.5, minSdk dropped from 24 to 21 Android, in case that is of interest

781flyingdutchman avatar Dec 28 '23 18:12 781flyingdutchman

And finally, as of background_downloader 8.0.3 there's an option to bypass permission requests on iOS that you're not using. It seems likely that you don't use the Photo Library permissions, so to avoid this triggering a requirement (by Apple) to explain why you need that in Info.plist, you should bypass those permissions altogether as explained here.

781flyingdutchman avatar Dec 28 '23 18:12 781flyingdutchman

This pull request now contains pretty much all the noteworthy changes I had in mind. I ended up writing a custom TaskQueue for background_downloader that stores enqueued items in Isar, and it stops enqueuing while in offline mode. It also applies the latest status of the requiresWifi setting to the task while enqueuing, so only the <30 active downloads can have an out of date setting. Downloading to external directories seems to work, at least in the testing I've done, and downloading entire libraries is included as well. All the UI bits are at least functional.

Open questions/Issues

  • When in offline mode, all the delete buttons on artists/albums are hidden, but the long press menus still have delete buttons. Should those be scrubbed? If items are deleted via those or the downloads page you end up with empty albums/artists on the music screen. Is it worth trying to get that to refresh?
  • I have modeled artists/genres/libraries as collections of albums for the purpose of downloading. Is this actually the correct way to think about things? I basically only use albums myself so I'm not entirely sure how jellyfin models things.
  • The downloads overview has downloads by state in the right and a large total downloads count in the center left, all of which live update. Below this is a count of downloads split songs - labeled items - and images, which does not live update. Is this discrepancy confusing? Is it worth making this display update, and is there any more useful information that could be displayed?
  • There's a number of minor UI issues. The coloring on the failed downloads header and active download icon on songs is bad in light mode. The sync button shown on playlists is emphasized when the playlist is believed to be out of date by turning yellow, which looks bad. The icon shown on transitively required items that turns them into fully required downloads is a lock, which doesn't really explain what it does to the user that well.
  • Based on a TODO comment in one of the files I moved the default internal download directory from applicationDocuments to applicationSupport. I believe the iOS iCloud exclusions need to be updated to handle this, and I don't have the environment to try myself.
  • Similarly, I threw the code to prevent background_downloader requesting photo permissions into the iOS files, but I'm not messing with the plist files to try to remove the permissions there.
  • I've implemented external download directories by basically just using the existing directory path setup with background_downloaders BaseDirectory.root. Based on a comment on one of the github issues I just removed the permissions check in addDownloads because it ostensibly always returns failed without doing anything in newer android versions. Supposedly we should have access to our external directory and files we create in user chosen directories without doing anything else, and it seems to work? But I'm not entirely sure the app doesn't have looser restrictions in debug mode, and I also don't know how older versions of android might behave because it seems like file permissions have been changing a lot.
  • I'm not entirely sure how this behaves from a performance perspective. Is 30 active downloads too many? Should I be throttling deletes harder/softer/more granularly? I've only tested in emulator, so I don't know how laggy this may or may not be on actual hardware.

Komodo5197 avatar Jan 04 '24 06:01 Komodo5197

Okay, so I'll build and install the release version of this branch in a bit to test it out and check permissions/performance. I'll let you know if I encounter any problems.

Regarding your questions:

  1. Is there a reason why deleting shouldn't be possible in offline mode? Unless there is a technical restriction, I'd say deleting should still work just fine. And if you can manage to refresh the tabs to reflect updated/deleted albums, that would be nice. But it's not a hard requirement from my side.
  2. Jellyfin also models these things as collections of albums, although I'm not a huge fan of that myself. But that's not an issue for this PR, the current implementation should be fine. Btw, do the top songs of an artist still show on the artist page in offline mode?
  3. I'll take a look at the downloads page, but generally the more things do live updates the better! 4.The UI issues are not an issue, I'll have to redesign all of these screens anyway. The important bit is that the functionality is there, and is being used, so that I can easily redesign everything.
  4. @jmshrv can probably take a look at the iOS stuff. He's interested in this PR anyway ^^
  5. I'll try out external downloads, but don't have an SD card slot in my phone. I also only have access to Android 12 and 14 (and maybe 9 or 10)
  6. I'll try out performance, but I have another concern regarding the 30 active downloads. If someone stores their music as FLAC, each file is around 50 MB, so in the worst case people could be downloading 1.5 GB over mobile data after turning on WiFi only downloading. So I'd limit the active downloads to a maximum of 5. Or is there a particular advantage of queuing more downloads at once?

All in all, your work here is really impressive and I'm excited to check out the changes! 😁

Chaphasilor avatar Jan 04 '24 09:01 Chaphasilor

I wanted to note that I'm using this pull on my finamp build, and it is FAST compared the speeds I've been getting previously, will note if I have any issues later on. Just wanted to thank @Komodo5197 for their work.

mcutshaw avatar Jan 04 '24 18:01 mcutshaw

I think there's something missing when downloading playlists, the album and songs tab don't get populated when downloading a playlist. I initially thought it didn't work at all, but it seems to work fine with albums and artists.

Here's what I originally wrote

Okay, so after taking a brief look while I was at work, I noticed that there is a pretty significant regression (at least for me):
When I download a collection that contains albums, the albums and songs will not be shown while offline mode is active!

Previously, when I downloaded a playlist, all the songs inside that playlist would then appear on the songs tab, and the corresponding albums in the album tab (containing only the songs that were in the playlist). With this PR, only the playlist is shown, and the album and songs tabs are completely empty.

What I would like to see (given that you already implemented showing "missing songs" by downloading all of the metadata) is the following:
If I download a playlist, it will download all songs inside the playlist, along with the full metadata for each album (I don't thing metadata for genres is needed here, as that is a lot). All these albums are then visible on the albums tab, and when you click on an album that has more than the downloaded song, the remaining tracks are shown grayed-out. And on the songs tab, all downloaded songs, no matter their source, as shown.

Also, I'm getting a lot of errors when trying to download stuff. Repairing doesn't seem to help, and there are discrepancies between how many errors are shown on different screens:

Screenshots ![image](https://github.com/jmshrv/finamp/assets/18015147/72181ec9-e76c-44e2-8924-b577b5659082) ![image](https://github.com/jmshrv/finamp/assets/18015147/8cfed2d9-9213-42cf-b3f7-e1a3491dff0e) ![image](https://github.com/jmshrv/finamp/assets/18015147/bee1b00e-5914-4466-a53c-98c3285bcbb2)

Here are some logs for you:
finamp-logs.txt Hope it helps!

Chaphasilor avatar Jan 04 '24 18:01 Chaphasilor

Not having delete buttons during while offline is an existing thing from before these changes. I think it's to not show out of date stuff, but I'm not sure. I can go the other way and try to get all the delete buttons showing at all times if you want.

The benefit to having more active downloads is that the native downloader should keep them going even once the app completely closes. The enqueued downloads need the app still active in the foreground or background to submit them. Very large downloads like FLAC to present a bit of an issue in how long it takes for the offline switch or requiresWiFi setting to apply, but they are also the best case scenario where true background downloading is most useful.

Regarding albums/songs not showing up, I believe the issue is that we don't know what library songs/albums downloaded by playlists should be shown in, because playlists aren't really associated with a specific library. It's probably better behavior to show items with unknown libraries in all libraries instead of none, so I'll change that. Or I could try to calculate what library they are in, but I never found any item->library path so I'd have to get all album children of every library to figure that out.

Regarding the failed download count discrepancies, I think the active downloads screen is showing failed collections as well as songs/images. It probably shouldn't.

Regarding failing downloads, unfortunately that the earliest log in that file is during the sync delete from repair downloads, which doesn't tell me anything about the downloading step. It looks like we only store 1000 logs which is less than I expected. Just running a regular sync of the items does retry failed downloads so the logs from one of those are more likely to not have the important bits cut off. Is this specific items that consistently stay failed across syncs/repairs?

Komodo5197 avatar Jan 04 '24 19:01 Komodo5197

I pulled this branch and figured out how to build it (I dont know anything about android development). Great work everyone. I will keep silently lurking.

Titaniumtown avatar Jan 04 '24 19:01 Titaniumtown

I don't see a reason why the delete buttons shouldn't be shown if your implementation can handle offline deletions just fine. You'd need to keep the metadata though when deleting (for showing missing / not downloaded tracks in lists), so it could be a bit complicated. If that's the case, feel free to stick to the current behavior!

Could you just add a settings page for downloading? Move the WiFi only switch there, add the "Download locations" as a subpage, and then maybe add a setting to configure max active downloads (possibly different values for on wifi and on mobile data?). There will probably be more settings added in the future anyway (transcoded downloads, etc.), so I think it's a good solution.
You can just use a slider or number input for now, but we could always simplify the options later on :)

Yeah Jellyfin playlists are a mess. There was a breaking change in 10.8.11 I think, they reverted it in 10.8.12. Last time I checked they still hadn't decided how it should work in 10.9, so we'll have to wait and see. Showing from unknown libraries seems like a good solution to me, but adding a setting for it is also possible.

I guess we'll need to increase the log limit then. But that's a topic for another PR. I'll try to do some more investigation and testing tomorrow!

Chaphasilor avatar Jan 04 '24 22:01 Chaphasilor

I wanted to ask your input on an idea I have to improve the requiresWifi issue that you have discussed in this PR, to make it work more like you'd like. This would be a modification to the background_downloader plugin. Can you take a look here and comment on the proposal? Thanks!

781flyingdutchman avatar Jan 04 '24 23:01 781flyingdutchman

  • I've implemented external download directories by basically just using the existing directory path setup with background_downloaders BaseDirectory.root. Based on a comment on one of the github issues I just removed the permissions check in addDownloads because it ostensibly always returns failed without doing anything in newer android versions. Supposedly we should have access to our external directory and files we create in user chosen directories without doing anything else, and it seems to work? But I'm not entirely sure the app doesn't have looser restrictions in debug mode, and I also don't know how older versions of android might behave because it seems like file permissions have been changing a lot.

See here: if you're using app-specific external storage (as obtained via Android's getExternalFilesDir() or equivalent via path_provider, i.e. this is data only accessible to your app) then you don't need permissions. If you use external Media storage (these are the destinations like SharedStorage.audio in background_downloader, allowing files to show up in the Android Audio folder, accessible by anyone) then you need to use the Android Media API and require permissions on API 29 or lower. If you use the FileDownloader().moveToSharedStorage method this is done for you.

781flyingdutchman avatar Jan 04 '24 23:01 781flyingdutchman

So the error rate is not great 😅

Screenshot_20240105-075110~2.png

That was a test for downloading my whole library to an external location. I think the extermal location worked just fine, and the errors are mostly images. Basically all images are missing for some reason...

The app and my whole phone was also really laggy, even though it's a very recent "flagship" with lots of RAM. So maybe 30 active downloads is simply too much?

Chaphasilor avatar Jan 05 '24 07:01 Chaphasilor

Restoring an offline-only queue (created in offline mode) in online mode but with the server unreachable also didn't work, when I think (?) it did before.

Logs
[MusicPlayerBackgroundTask/INFO] 2024-01-05 08:38:13.929321: Starting audio service

null
[QueueService/FINE] 2024-01-05 08:38:13.929446: Loop mode set to FinampLoopMode.all

null
[QueueService/INFO] 2024-01-05 08:38:13.929449: Restored loop mode to FinampLoopMode.all from settings

null
[MusicPlayerBackgroundTask/INFO] 2024-01-05 08:38:13.929816: Shuffle mode changed to AudioServiceShuffleMode.none (false).

null
[MusicPlayerBackgroundTask/INFO] 2024-01-05 08:38:13.929823: Loop mode changed to AudioServiceRepeatMode.all (LoopMode.all).

null
[MusicPlayerBackgroundTask/INFO] 2024-01-05 08:38:13.929931: Loop mode changed to AudioServiceRepeatMode.all (LoopMode.all).

null
[QueueService/FINEST] 2024-01-05 08:38:13.943076: Stored queue dates: [2024-01-04 11:14:09.209, 2024-01-04 15:31:58.453, 2024-01-04 15:32:08.453, 2024-01-04 17:22:30.133, 2024-01-04 19:19:30.589, 2024-01-04 23:12:14.056, 2024-01-04 23:32:34.112, 2024-01-05 07:51:30.894, 2024-01-05 08:35:30.897, 2024-01-05 08:35:30.897]

null
[QueueService/INFO] 2024-01-05 08:38:13.943086: Stopping playback

null
[MusicPlayerBackgroundTask/INFO] 2024-01-05 08:38:13.943088: Stopping audio service

null
[QueueService/FINE] 2024-01-05 08:38:13.943173: Queue is empty

null
[Chopper/INFO] 2024-01-05 08:38:13.956701: --> GET BASEURL/Users/617b05f80f92493c80f676df50511ea3/Items?IncludeItemTypes=MusicAlbum&ParentId=7e64e319657a9516ec78490da03edccb&Recursive=true&SortBy=SortName&SortOrder=Ascending&Fields=parentId%2CindexNumber%2CsongCount%2CchildCount%2CproviderIds%2Cgenres%2Ctags%2CEtag%2CalbumPrimaryImageTag%2CparentPrimaryImageItemId&StartIndex=0&Limit=100
Content-Type: application/json
X-Emby-Authorization: MediaBrowser UserId="617b05f80f92493c80f676df50511ea3", Client="Finamp", Device="ASUS_AI2302", DeviceId="187349badd5273fd", Version="0.6.19"
X-Emby-Token: TOKEN
content-length: 0
--> END GET

null
[Chopper/INFO] 2024-01-05 08:38:13.957927: --> GET BASEURL/Users/617b05f80f92493c80f676df50511ea3/Items?ids=f98ac5f1cfab5019056df0e93916efb9%2C05ea0f238867168957f98e5653b6a3ab%2C0d34be2b97db22657959d6a7e4986588%2Cf4928ce4acb64ff13986cb34e286fd42%2C7fce2552f9f2db5686646cf099b3d438%2Cb753e9d3c1566d9054baeda4b600eca1%2Cb14989d2076fd48cb781ed0bcb03931c%2Ca8e011d9cb36f0e3df644574ba2f0522%2Cbd52082300e611ef061ca96eff102f48%2Cf3c2e8801d8f000948487481d9474bb9%2C0d4eb3271c8f919917127b536e6a096d%2C37f9e1f43f15ac77d93502a3b9e84d2e%2C3a812bd5a8cdbf0c1161729b0411a3b6%2C60f39b17a535f450c4eea97ec3ad8384%2Ce06bc21f2200837a041bed314b809daf%2C523c3a4f968e57414501a3a772715cd3%2Ca96a16bd09de5f682189c70334c45620%2Cbb147e4783b0da3de50a533b94e1b9a4%2Cda0b3934788ea7079445d47472342321%2C060ee6c5f6e7190c17ac755becd4874f%2C5b77b8aef6db3b77d2aa1ba2ca68236f%2C3c4f58844969e980b05661cee342731d%2C15f39d52f46ec65d8852cc15f0054a4a%2C8f424ccf607b7f3fb649a1361f60b2c7%2Ca0f051c6a74c4050983629ca5faa0be1%2C8b904548acb95feacc5379bdbb328505%2C75a5977783bf11924c6921436d8c6536%2C92f07a1bdfafa5bb13ad740bd9cd2d1c%2Cb089f36df054a1728dc9c98c7a890996%2C5337608ca78b9085553d5ea692759c33%2C942beaf7d262224e45d8ee0a6d4ab98e%2C0425393d18330a45c6cccfdf30c73da6%2C2851af70e53a5ba365ffefe8d53e17ca%2Cb4829ba340507928298067f8492154b0%2C4367160862a583ff9fe69595629fbed3%2Cdb111a1276e6f93ea7b6868b27591bce%2Cf86bca2af9f43a0cfbe48210702c5814%2C845b6fe7b7768496cc6ed0dcaf5d33db%2C8fce0f6065daff078e9918b906431647%2C41e693a5740358cfa6c9d7098428bb56%2C6bc47a27af247178b333524167347775%2C5615c3ac39916dc59ebdb3360569fbe9%2Cd2a2e122cc68726196c4680e3b6bc834%2Ca3c52628593346454f1ebf4c0c994736%2Ce431f3dc1d2a94d834287e6e5b5eb680%2C5ccce7087e2a64f01149d44413ac5349%2C15e521840e76e2fdca170d790d35f52e%2C2aab263f0cd635cfd0d9df7a0fdded1e%2C096c0fac2e1fe11a2658d70b2581286d%2Cbfa68770ef628a20ff6792a19e122952%2C1d4774bd0a276e540a7a43349f8f69ed%2C20cacc36759a57db27de55d5032078cd%2C69ccd8cb17a002dc270b000492475c8d%2C25cf49f99236f47dde9bdf67fe3ab80b%2C024c2c96a852b3fdc2831664938f6e2b%2C91f26e0bd1efdb5f5c933472406a2c6d%2C17b41149e96ecf3e954c55a29d9dd303%2C04978c0932a5e146eccae0286babe875%2C3004f8e92502b9ae6844f9e7987755c4%2Cced0dbd78ceb7cfc15ae4cf540fc5b2e%2C0adc52c43a43ecd81828f4ddfc293f12%2C128da97dfa9b488618a0a5d6cbbae314%2Ce99fc22ea32281d58907c36183f6a1b2%2C0dac5c2dacc459fa6c79bbb79e5d0a7d%2C7c47a6b367000ec29d5bffe131c6b675%2C1e44e8f77268f311c0b48964ea993f60%2C8c21e5262988d8f1f046f3feaf42adb2%2C3133c8984efc02f5c03bd3d9f2befa76%2Cfea8a67ed4acc9cd499894a01863e213%2Ce326bebd2218846bb5e649d0d98af439%2C21e67ffe8a65b2767a54ff65cc6d9b89%2C4c40e37d54d3750c95aafcf5d8beeffc%2C7d728aeeda9c8fb2b9659af2bcec64da%2C3a4b3f832610efcab4ac988e681c8872%2C740a428e1421902d8bc257d2a9f205ff%2Cf3a59c3dbc8a62450c369e692eb0bf20%2C9f99159f4286862406cd264ed461fd04%2C8d56120d70aca0f2547bb1b582b45a35%2C96f34f4187bfedd5841b99b3b2778c28%2C1a2f31bfcd12d37cc9a625ad12452d6f%2C4b1b1fcbdba299834d95ca933db8670e%2C2c7bb125eb65d5784fa39d6628b47737%2C41ea6b29697ed14058b0d33187e1fb88%2Cdd440eef2eb9364585195e2fb3f175d1%2C9d46558aee824ce0e7c8526be6fe5f85%2C82e652e4bebb7171293a39e58d467a70%2C3f4e1b075cbc9aa43d800350b0196fb0%2C652ae0981d267f6e5e9f91531ca61b04%2C571c7a3a5ce89807fa02fde658b3fd6c%2Ceb66ddbcaecef2db30208b26438e9a77%2Cccbb966f179c61443b9d8391503d2d12%2C97e210c44e7620acf124047a0c098444%2C3cf0b4581efae863dc11723f1fb94bd7%2C8c47d886ea2b36fc071f4ef2f91ede4f%2Caa70dbf918665016bbcf039b1be30fb7%2Ca844c35c87c0087dd65b711c157dcd01%2C8d4a87b5f993212f60d4b6254d70f6ed%2Cd6cb1a052468c5613894c89454749ba8%2Cc0dcd4e40470270e6ae95a6ffb58edd0%2Cc09a660053f25f8fd0535e8025c6fbe2%2C0f4d5cdc202940364f0946b1e284155b%2C0bbcadab3ad87bae141e29fc060c0778%2Cde286e22bba1469d64cf3dda2d9b861e%2Ce3e2138524de083c0483b405ee378f08%2C8fee3095f18be976b5b5a36580265303%2C511af85b0a7b0d885d6a03e897f9c69f%2C8aa78809358231a6076ae88dd405ffed%2C9139ed49a62dd1c787034625c4934fd7%2C5029ea1d1bb1ecc6e53e1dfa9cbee837%2Ca46d5a10c3f805aca1e2c7bf7929bd73%2C70bf1ff4fe9a5b4bec2307897a2f0270%2C47a014f7aaead54729b1a196dfffa94a%2C55eb746d799cbc9282d573e2e95f7263%2C9a4e285479a7bbbc87b4ca6696ae6dbb%2C41345cae3685d65a171a1ef1935fca83%2C2b948a3641331fc6a3ad17a8af049c4b%2Cf0ce6108cf5ab0f66857766fd8838739%2C6715aa34b94fac92806a138da376e07a%2Cc1d77be25271e613da93e31e23c21cea%2C8026f2c2aefe7776d7d918378266c295%2C29c253f5ea30b5f298f555aefdfbd4b9%2C75acea76bca8fdd13dee6210215cc504%2C9460f85b64abc26da9a1ee8fba8a472b%2C39a134a5df9d4cbc493baaff29352849%2Ccfb6e151090e009351e1183f6c188a17%2C76a9c134123e7b1c90fa2631febd694f%2C3f9f70ec1073bd3aed21a46dcf079425%2C756e0866e493b9123ba9f91edbca6bb5%2Ce029ab9fef34c6de5cb9ff76a42b3320%2C501a7b48c7f086fccba79bc23cee5c5d%2C9d8da54f0f2e0592726864f87c403783%2C51a68754b220030ecb6abe8ffc600262%2C3ccffaa695d027538c9818a1aed0a67c%2C0748ade9aee99d054799200a9367a96d%2Cb3799097bfe75b59a3bf89a6497166f9%2Ce22da5a963719f75ff7424fa02f89d2e%2Ce68efa639527cd6b4d2366f49224309c%2Cb8b9bd8aef5e0589da79f90d62d1d11c%2C2caf905cf1fc74d8644963d473a0a6d4%2C6ab536aecbcf3f826be4405cdd594091%2C9d1baae079fe8ccd3c223110436f2934%2C73d19e0a7a26d42a214faab731c9bb2b%2C2c4520e202fc4b67c188c05b35529750%2Ca129441776fdea52b241d3ec891db2f6%2C5aa46159ab7802de1120aada4c00a4ca%2C68221da3711d4d15575f88c59e3305bc%2C7c40220e9bc521152e4d0ac0161e3ad2%2C088ef0bc429c9bc00d03c8a4bae17b4d%2C17580eca2d9649ef8d7d772d85a2d091%2C3cdf866e144de3226b09ae44cf3d1539%2Cef77eb98108306a508c223a7052cc216%2C368dd5107c3dee18734fdd904f60a8cb%2C8f8d625760ba9e9cf769a4c4176943c8%2C0f39e01faab9464c02657ae7b11f11ff%2C108fa2ba62dda6195a08f79d63c98563%2Cd4414449b4ca198e4c104803843109ee%2C8e2a620e503ab4d018fce5a60f730593%2C401b23b89607f2efb25654892cc1873b%2C146f772934f3d6cdd094fcd3ea94397a%2C7b1ede41515c7f1ee14d58ff2d7a3dc3%2Cc67981c05ef1a967fcff32cad0273ff7%2Cc0537ba9b8f17992bf99e73362ff0b07%2Cde5131bb2a9d9400cab8370d94cff053%2C266ec02433ccabcd784c2236c00e4d84%2C05dba93bbf45d78f8ae00a842e1fdcaa%2C859814ef36b4718683e4978bd164bc96%2Cb46dfc70cc21d2aa73c2e1c3c19482c8%2C2df5bc7daca3c79740d1e03f4eab275e%2C34346c43b3a477126674d0119840e6c6%2C57a638a9bb1cbc14382661a0476d109d%2C775ee0302d7b9f94e91f8e409d0eef3b%2C8cc6a217e61119d579bbbce4eba84d8c%2Cdebffd9111d36818733b1a16637dd398%2Cb01a7fff3cc5670d50f891474f369cef%2C6bbedb2b42025f12986f6a8f87ba2a61%2C12d445e71f2199ba0a5baa1d1e3e2a27%2C22a95bb631e440d88586e7b1f070a24f%2C4b87facbec3f32993f3256ff984a68a6%2C8100b1a7c21c13eb5f45405621730ff3%2C876a9ccb3cb99e00c516506ff3f0efed%2C3fcde5be5a44469aac63fbeca115fa53%2C21eb834efd5294e791b72aa396c0dc22%2Cf53481dd8c7279436f42093f6d19eb58%2Cd735bb29554120cc2bc300c273f2cbac%2Cb72077f70fd6d6f74bec004c22ffee4c%2C9689e832e99c284ee7e3b049f8bda2a9%2C7342e9bc2d4abe8e3d58ccf781535a4f%2C500ab2cf92b284602e7a8f4e5c5a07c2%2Cf3f8a77d3a90564ddc625a901cb855f3%2Cbb1acc7fb47a2e8e6c51b051a689dc5d%2Cfd4d80c765c8499ba9dcbaa49d4f0364%2Cf2f7265838c2c86f31c8237fa88947cc%2Cb2b08553db2b5ba64277243c5ba142c1%2C2a610a7ee38c127522ddf50a85bf1683%2C9d727d67eabfbbcc7e4d53a545ecd3dc%2Caa1e4b8ac805ae86632fa62dfa944278%2Cc65d3e555bd33788c18dfbbf0c83dd37%2C486cf15d79a20c672148b78f0cc72be5%2C183dd4c882eef69096e37e28d78dc3a5%2C652ea5c52f3f98817ef6c37de72e5b4b&Recursive=true&Fields=parentId%2CindexNumber%2CsongCount%2CchildCount%2CproviderIds%2Cgenres%2Ctags%2CEtag%2CalbumPrimaryImageTag%2CparentPrimaryImageItemId
Content-Type: application/json
X-Emby-Authorization: MediaBrowser UserId="617b05f80f92493c80f676df50511ea3", Client="Finamp", Device="ASUS_AI2302", DeviceId="187349badd5273fd", Version="0.6.19"
X-Emby-Token: TOKEN
content-length: 0
--> END GET

null
[GlobalSnackbar/WARNING] 2024-01-05 08:38:32.576420: Displaying error: Closure: (dynamic) => void from Function 'error': static.

null
[QueueService/SEVERE] 2024-01-05 08:38:32.576990: ClientException with SocketException: No route to host (OS Error: No route to host, errno = 113), address = 192.168.31.40, port = 37982, uri=BASEURL/Users/617b05f80f92493c80f676df50511ea3/Items?ids=f98ac5f1cfab5019056df0e93916efb9%2C05ea0f238867168957f98e5653b6a3ab%2C0d34be2b97db22657959d6a7e4986588%2Cf4928ce4acb64ff13986cb34e286fd42%2C7fce2552f9f2db5686646cf099b3d438%2Cb753e9d3c1566d9054baeda4b600eca1%2Cb14989d2076fd48cb781ed0bcb03931c%2Ca8e011d9cb36f0e3df644574ba2f0522%2Cbd52082300e611ef061ca96eff102f48%2Cf3c2e8801d8f000948487481d9474bb9%2C0d4eb3271c8f919917127b536e6a096d%2C37f9e1f43f15ac77d93502a3b9e84d2e%2C3a812bd5a8cdbf0c1161729b0411a3b6%2C60f39b17a535f450c4eea97ec3ad8384%2Ce06bc21f2200837a041bed314b809daf%2C523c3a4f968e57414501a3a772715cd3%2Ca96a16bd09de5f682189c70334c45620%2Cbb147e4783b0da3de50a533b94e1b9a4%2Cda0b3934788ea7079445d47472342321%2C060ee6c5f6e7190c17ac755becd4874f%2C5b77b8aef6db3b77d2aa1ba2ca68236f%2C3c4f58844969e980b05661cee342731d%2C15f39d52f46ec65d8852cc15f0054a4a%2C8f424ccf607b7f3fb649a1361f60b2c7%2Ca0f051c6a74c4050983629ca5faa0be1%2C8b904548acb95feacc5379bdbb328505%2C75a5977783bf11924c6921436d8c6536%2C92f07a1bdfafa5bb13ad740bd9cd2d1c%2Cb089f36df054a1728dc9c98c7a890996%2C5337608ca78b9085553d5ea692759c33%2C942beaf7d262224e45d8ee0a6d4ab98e%2C0425393d18330a45c6cccfdf30c73da6%2C2851af70e53a5ba365ffefe8d53e17ca%2Cb4829ba340507928298067f8492154b0%2C4367160862a583ff9fe69595629fbed3%2Cdb111a1276e6f93ea7b6868b27591bce%2Cf86bca2af9f43a0cfbe48210702c5814%2C845b6fe7b7768496cc6ed0dcaf5d33db%2C8fce0f6065daff078e9918b906431647%2C41e693a5740358cfa6c9d7098428bb56%2C6bc47a27af247178b333524167347775%2C5615c3ac39916dc59ebdb3360569fbe9%2Cd2a2e122cc68726196c4680e3b6bc834%2Ca3c52628593346454f1ebf4c0c994736%2Ce431f3dc1d2a94d834287e6e5b5eb680%2C5ccce7087e2a64f01149d44413ac5349%2C15e521840e76e2fdca170d790d35f52e%2C2aab263f0cd635cfd0d9df7a0fdded1e%2C096c0fac2e1fe11a2658d70b2581286d%2Cbfa68770ef628a20ff6792a19e122952%2C1d4774bd0a276e540a7a43349f8f69ed%2C20cacc36759a57db27de55d5032078cd%2C69ccd8cb17a002dc270b000492475c8d%2C25cf49f99236f47dde9bdf67fe3ab80b%2C024c2c96a852b3fdc2831664938f6e2b%2C91f26e0bd1efdb5f5c933472406a2c6d%2C17b41149e96ecf3e954c55a29d9dd303%2C04978c0932a5e146eccae0286babe875%2C3004f8e92502b9ae6844f9e7987755c4%2Cced0dbd78ceb7cfc15ae4cf540fc5b2e%2C0adc52c43a43ecd81828f4ddfc293f12%2C128da97dfa9b488618a0a5d6cbbae314%2Ce99fc22ea32281d58907c36183f6a1b2%2C0dac5c2dacc459fa6c79bbb79e5d0a7d%2C7c47a6b367000ec29d5bffe131c6b675%2C1e44e8f77268f311c0b48964ea993f60%2C8c21e5262988d8f1f046f3feaf42adb2%2C3133c8984efc02f5c03bd3d9f2befa76%2Cfea8a67ed4acc9cd499894a01863e213%2Ce326bebd2218846bb5e649d0d98af439%2C21e67ffe8a65b2767a54ff65cc6d9b89%2C4c40e37d54d3750c95aafcf5d8beeffc%2C7d728aeeda9c8fb2b9659af2bcec64da%2C3a4b3f832610efcab4ac988e681c8872%2C740a428e1421902d8bc257d2a9f205ff%2Cf3a59c3dbc8a62450c369e692eb0bf20%2C9f99159f4286862406cd264ed461fd04%2C8d56120d70aca0f2547bb1b582b45a35%2C96f34f4187bfedd5841b99b3b2778c28%2C1a2f31bfcd12d37cc9a625ad12452d6f%2C4b1b1fcbdba299834d95ca933db8670e%2C2c7bb125eb65d5784fa39d6628b47737%2C41ea6b29697ed14058b0d33187e1fb88%2Cdd440eef2eb9364585195e2fb3f175d1%2C9d46558aee824ce0e7c8526be6fe5f85%2C82e652e4bebb7171293a39e58d467a70%2C3f4e1b075cbc9aa43d800350b0196fb0%2C652ae0981d267f6e5e9f91531ca61b04%2C571c7a3a5ce89807fa02fde658b3fd6c%2Ceb66ddbcaecef2db30208b26438e9a77%2Cccbb966f179c61443b9d8391503d2d12%2C97e210c44e7620acf124047a0c098444%2C3cf0b4581efae863dc11723f1fb94bd7%2C8c47d886ea2b36fc071f4ef2f91ede4f%2Caa70dbf918665016bbcf039b1be30fb7%2Ca844c35c87c0087dd65b711c157dcd01%2C8d4a87b5f993212f60d4b6254d70f6ed%2Cd6cb1a052468c5613894c89454749ba8%2Cc0dcd4e40470270e6ae95a6ffb58edd0%2Cc09a660053f25f8fd0535e8025c6fbe2%2C0f4d5cdc202940364f0946b1e284155b%2C0bbcadab3ad87bae141e29fc060c0778%2Cde286e22bba1469d64cf3dda2d9b861e%2Ce3e2138524de083c0483b405ee378f08%2C8fee3095f18be976b5b5a36580265303%2C511af85b0a7b0d885d6a03e897f9c69f%2C8aa78809358231a6076ae88dd405ffed%2C9139ed49a62dd1c787034625c4934fd7%2C5029ea1d1bb1ecc6e53e1dfa9cbee837%2Ca46d5a10c3f805aca1e2c7bf7929bd73%2C70bf1ff4fe9a5b4bec2307897a2f0270%2C47a014f7aaead54729b1a196dfffa94a%2C55eb746d799cbc9282d573e2e95f7263%2C9a4e285479a7bbbc87b4ca6696ae6dbb%2C41345cae3685d65a171a1ef1935fca83%2C2b948a3641331fc6a3ad17a8af049c4b%2Cf0ce6108cf5ab0f66857766fd8838739%2C6715aa34b94fac92806a138da376e07a%2Cc1d77be25271e613da93e31e23c21cea%2C8026f2c2aefe7776d7d918378266c295%2C29c253f5ea30b5f298f555aefdfbd4b9%2C75acea76bca8fdd13dee6210215cc504%2C9460f85b64abc26da9a1ee8fba8a472b%2C39a134a5df9d4cbc493baaff29352849%2Ccfb6e151090e009351e1183f6c188a17%2C76a9c134123e7b1c90fa2631febd694f%2C3f9f70ec1073bd3aed21a46dcf079425%2C756e0866e493b9123ba9f91edbca6bb5%2Ce029ab9fef34c6de5cb9ff76a42b3320%2C501a7b48c7f086fccba79bc23cee5c5d%2C9d8da54f0f2e0592726864f87c403783%2C51a68754b220030ecb6abe8ffc600262%2C3ccffaa695d027538c9818a1aed0a67c%2C0748ade9aee99d054799200a9367a96d%2Cb3799097bfe75b59a3bf89a6497166f9%2Ce22da5a963719f75ff7424fa02f89d2e%2Ce68efa639527cd6b4d2366f49224309c%2Cb8b9bd8aef5e0589da79f90d62d1d11c%2C2caf905cf1fc74d8644963d473a0a6d4%2C6ab536aecbcf3f826be4405cdd594091%2C9d1baae079fe8ccd3c223110436f2934%2C73d19e0a7a26d42a214faab731c9bb2b%2C2c4520e202fc4b67c188c05b35529750%2Ca129441776fdea52b241d3ec891db2f6%2C5aa46159ab7802de1120aada4c00a4ca%2C68221da3711d4d15575f88c59e3305bc%2C7c40220e9bc521152e4d0ac0161e3ad2%2C088ef0bc429c9bc00d03c8a4bae17b4d%2C17580eca2d9649ef8d7d772d85a2d091%2C3cdf866e144de3226b09ae44cf3d1539%2Cef77eb98108306a508c223a7052cc216%2C368dd5107c3dee18734fdd904f60a8cb%2C8f8d625760ba9e9cf769a4c4176943c8%2C0f39e01faab9464c02657ae7b11f11ff%2C108fa2ba62dda6195a08f79d63c98563%2Cd4414449b4ca198e4c104803843109ee%2C8e2a620e503ab4d018fce5a60f730593%2C401b23b89607f2efb25654892cc1873b%2C146f772934f3d6cdd094fcd3ea94397a%2C7b1ede41515c7f1ee14d58ff2d7a3dc3%2Cc67981c05ef1a967fcff32cad0273ff7%2Cc0537ba9b8f17992bf99e73362ff0b07%2Cde5131bb2a9d9400cab8370d94cff053%2C266ec02433ccabcd784c2236c00e4d84%2C05dba93bbf45d78f8ae00a842e1fdcaa%2C859814ef36b4718683e4978bd164bc96%2Cb46dfc70cc21d2aa73c2e1c3c19482c8%2C2df5bc7daca3c79740d1e03f4eab275e%2C34346c43b3a477126674d0119840e6c6%2C57a638a9bb1cbc14382661a0476d109d%2C775ee0302d7b9f94e91f8e409d0eef3b%2C8cc6a217e61119d579bbbce4eba84d8c%2Cdebffd9111d36818733b1a16637dd398%2Cb01a7fff3cc5670d50f891474f369cef%2C6bbedb2b42025f12986f6a8f87ba2a61%2C12d445e71f2199ba0a5baa1d1e3e2a27%2C22a95bb631e440d88586e7b1f070a24f%2C4b87facbec3f32993f3256ff984a68a6%2C8100b1a7c21c13eb5f45405621730ff3%2C876a9ccb3cb99e00c516506ff3f0efed%2C3fcde5be5a44469aac63fbeca115fa53%2C21eb834efd5294e791b72aa396c0dc22%2Cf53481dd8c7279436f42093f6d19eb58%2Cd735bb29554120cc2bc300c273f2cbac%2Cb72077f70fd6d6f74bec004c22ffee4c%2C9689e832e99c284ee7e3b049f8bda2a9%2C7342e9bc2d4abe8e3d58ccf781535a4f%2C500ab2cf92b284602e7a8f4e5c5a07c2%2Cf3f8a77d3a90564ddc625a901cb855f3%2Cbb1acc7fb47a2e8e6c51b051a689dc5d%2Cfd4d80c765c8499ba9dcbaa49d4f0364%2Cf2f7265838c2c86f31c8237fa88947cc%2Cb2b08553db2b5ba64277243c5ba142c1%2C2a610a7ee38c127522ddf50a85bf1683%2C9d727d67eabfbbcc7e4d53a545ecd3dc%2Caa1e4b8ac805ae86632fa62dfa944278%2Cc65d3e555bd33788c18dfbbf0c83dd37%2C486cf15d79a20c672148b78f0cc72be5%2C183dd4c882eef69096e37e28d78dc3a5%2C652ea5c52f3f98817ef6c37de72e5b4b&Recursive=true&Fields=parentId%2CindexNumber%2CsongCount%2CchildCount%2CproviderIds%2Cgenres%2Ctags%2CEtag%2CalbumPrimaryImageTag%2CparentPrimaryImageItemId

null
[GlobalSnackbar/WARNING] 2024-01-05 08:38:32.577075: Displaying error: Closure: (dynamic) => void from Function 'error': static.

null

Chaphasilor avatar Jan 05 '24 07:01 Chaphasilor

I think the new global error snackbar isn't fully working:

Screenshot_20240105-091908.png

Chaphasilor avatar Jan 05 '24 08:01 Chaphasilor

It looks like the snackbar was logging/displaying the GlobalSnackbar.error function instead of the supplied error, so I've fixed that. I found an issue where switching to offline mode while downloading would create undeletable items with a null file path that broke a bunch of stuff, so if you happened to do that some issues could be from there. Regarding lag, there's two phases of downloading with different characteristics. There the initial sync phase where downloads are enqueued, and that is generally quite laggy and probably needs more throttling in some manner. After this, you get a notification that all downloads have been queued, and this is where the 30 download cap comes into play. You can see this on the downloads screen where generally the download count will go up laggily for a bit with running downloads generally staying low, and then the total download count stops climbing and the existing downloads slowly move from enqueued to running to complete as running pegs at 30. This stage does not lag in my tests, if it does for you we need to pull the 30 down. There's obviously something up somewhere, because I've had essentially a zero download failure rate in emulator for a while now.

Komodo5197 avatar Jan 05 '24 09:01 Komodo5197

What I noticed when trying to download my whole library, it took quite some time (many minutes) to queue everything. So maybe there's a way to throttle the queueing somehow?
Also, I think it's a bit strange to get the notification that everything has been queued after a delay. It's better to show a confirmation that the download has been initialized at the start, then silently queue and download everything, and only show a notification if something goes wrong.

I'll pull your fixes and do some more testing. Will send logs in a bit!

Chaphasilor avatar Jan 05 '24 16:01 Chaphasilor

So definitely made some improvements, not a single error this time around!

image

I didn't download the entire library yet, just a selection of all different item types. I also reduced the max concurrent downloads to 10. I'll definitely do some stress testing later.

Oh and showing the delete button while offline is fine, but the sync or download buttons shouldn't be shown, pressing them will just result in errors (as it should). The delete button is also missing for songs when oflfine.
There are also some more improvements that would be nice, for example downloading a song that is inside a album for a single (so only that one track in the album), the album should be marked as fully downloaded. Right now there's still a download button (which should be a lock button, really) that doesn't have any visible effect. But maybe that messes with some other things, you know best!

And with the new download system that fetches metadata for dependencies, we can re-enabled some features in offline mode, like going to album or artist, and showing artist images within the artist chips

Chaphasilor avatar Jan 05 '24 17:01 Chaphasilor

Right now you're showing completed, failed, enqueued and running on the downloads page, but "remaining" would also be very useful I think!

Chaphasilor avatar Jan 05 '24 19:01 Chaphasilor

Wouldn't that just be enqueued+running? I don't know how many items there's going to be until the sync finishes, and everything's enqueued by then. I could put that somewhere I guess.

Komodo5197 avatar Jan 05 '24 20:01 Komodo5197

Oh okay, the "enqueued" value was going up so slowly at first I though it was saying something different. In that case, no need to show something else :)

I also just saw #449, so maybe we need one more collection type (so that we can sync new favorites)? For now, just put the download action somewhere, maybe long-pressing the favorites toggle? 😅

Chaphasilor avatar Jan 05 '24 20:01 Chaphasilor

A comment on the parallel download of many files from the same server, in case this is helpful:

  • When you enqueue a large number of files, the WorkManager (Android) or URLSession (iOS) determine how many of these are run in parallel
  • On Android, this appears to be limited by the number of cores by default, so typically ~10 for modern devices. It is possible to restrict the number of tasks executed in parallel (see here) but it's somewhat complicated and I have not tried this
  • On iOS, you can configure URLSession's httpMaximumConnectionsPerHost. However, if the files are pulled from the same host, nowadays the server likely uses HTTP2 instead of HTTP1, and that means it establishes only one connection, over which it sends many streams simultaneously. Therefore, even if you were to set the httpMaximumConnectionsPerHost to 1, you'll still see many parallel downloads happening in practice - likely as many as your limit of 30 - and this may affect performance and reliability of the download. It's also why I haven't added this configuration option, as it is useless in practice.

The result of all of this is that really the best way to avoid overloading a server (or the client) is to limit the enqueuing of download tasks itself - as you do via the TaskQueue you created. It may be more elegant to do this on the native side (inside the plugin), but that requires building a task queue there (before the actual enqueue) with persistent storage, and seems a little too complicated for most applications, so I have not done this.

Hope this helps.

781flyingdutchman avatar Jan 05 '24 20:01 781flyingdutchman

After downloading the whole library (in addition to the stuff I downloaded before), it went up to one error and three stuck (1 enqueued, 2 running) downloads.

Screenshot

image

Clicking "sync" on the downloads page had no effect, clicking "repair" made it worse (30 failed, 1 enqueued, 0 running now). Only images seem to have failed. Clicking the "repair" button yesterday grayed-out the sync and repair icons, and they are still grayed-out now: image

I could've sworn I saved some logs yesterday, but seems like I messed up. Will try to repro later.

Chaphasilor avatar Jan 06 '24 09:01 Chaphasilor