Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sync button to playlist screen #534

Merged
merged 9 commits into from
Dec 24, 2023
Merged

Add sync button to playlist screen #534

merged 9 commits into from
Dec 24, 2023

Conversation

shayaantx
Copy link
Contributor

@shayaantx shayaantx commented Nov 13, 2023

Downloads playlists on the playlist screen (will add anything missing and remove anything that doesn't exist in playlist anymore that is downloaded)

@Chaphasilor
Copy link
Collaborator

Thanks for the PR!
Is this similar to #460?
Maybe you could add a small description or a short video that clarifies how this works :)

@shayaantx
Copy link
Contributor Author

added summary (lemme know if you want some screenshots or a video)

@Chaphasilor it is similar, however my understanding of #460 is that it will sync every single parent (which i think could be an album or playlist) that exists on the downloads screen.

The difference here is this lives solely on the playlist screen (not albums), this also removes any songs that are no longer part of the playlist anymore (need to test some edge cases with this tho). This also only syncs whatever playlist you are currently viewing (not all the playlists like the other button)

@Chaphasilor
Copy link
Collaborator

@shayaantx okay, got it. Would there be a way to re-use some of the code from the other PR to keep things organized? Or are you already doing that?

@shayaantx
Copy link
Contributor Author

@Chaphasilor the only clean way I see to reuse that code would to not reuse it and replace that code with this code (since this removes items too)

I can try to refactor it and see what I can come up with it

@Chaphasilor
Copy link
Collaborator

Okay, then maybe you could split the logic into a separate file and function(s) so that it's easier to replace the existing code later on. Once I get around to testing this out, we can try to get this merged 😁

@Chaphasilor
Copy link
Collaborator

Not sure why the build it failing. I'll take a look soon!

@shayaantx
Copy link
Contributor Author

@Chaphasilor I was looking into the case where you have a downloaded item shared across playlists/album

so it won't remove the downloaded item as long as the user synced both album and playlist (or playlist and playlist or album and album etc etc)

So the user would have to had to press sync on the album/playlist with the song, then also sync on the other, then the DownloadSong::requiredBy won't be empty.

I also wasn't passing "deleteFor" to DownloadsHelper::deleteDownloads() which was making it always remove it everytime (I fixed this in my recent push).

I refactored it out as requested, still looking into possibly reusing this logic for the SyncDownloads stuff (just been busy - will try to look more this weekend)

@Chaphasilor
Copy link
Collaborator

That sounds great! My own big PR got merged in recently so I'll be taking a look at the other open PRs soon, including yours. Should I wait until you looked into the reusability?

@shayaantx
Copy link
Contributor Author

That sounds great! My own big PR got merged in recently so I'll be taking a look at the other open PRs soon, including yours. Should I wait until you looked into the reusability?

Yea I'll repost once once I've figured out the reusability part

@shayaantx
Copy link
Contributor Author

@Chaphasilor so I refactored a few things locally:

  1. Replaced download button on album/playlist with just a delete button if the album has been downloaded (so I removed the download button in lieu of the sync button I added)
  2. Then replaced the underlying logic sync downloads button on downloads page with my sync logic

However I found one issue, specifically with the delete part of the logic. If you recall before I mentioned I wasn't using "deletedFor" attribute when DownloadsHelper::deleteDownloads()

This particular function always removes whatever the deletedFor value is from the parent caches (_downloadedParentsBox for example). This results in every sync removing the parent item (which basically makes the app look like nothing was downloaded)

I'm investigating the best way to address this, basically seems like I might need to refactor some of the delete logic to be separate so I don't delete parent unless the user asks to delete the entire downloaded album/playlist and in the sync logic just delete items (unless they sync and the album/playlist is empty)

@Chaphasilor
Copy link
Collaborator

Ah I see, that makes sense. I haven't looked at the code yet, but maybe adding a removeParentItem flag would help, or even better detecting if the parent has any downloaded children and not deleting the parent if that is the case.
But I guess that depends on how the downloading logic handles deleting a parent (including any children), for example if yoi want to get rid of a previously downloaded album.

I'm not familiar with the downloading stuff myself, maybe @jmshrv has some additional input...

@shayaantx
Copy link
Contributor Author

@Chaphasilor yea I did something like that locally, need to test it a bit, then ill repush

  • Specifically I renamed the existing deleteDownloads to deleteParentAndChildDownloads since that really attempts to force delete all the downloads (including parent if you give)
  • So I split out deleting children and deleting parent, then in my sync delete function I explicitly check if the parent has anymore children left, and delete it if it does not.

@Chaphasilor
Copy link
Collaborator

Awesome! Looking forward to testing it out 😁

@Chaphasilor Chaphasilor self-assigned this Nov 29, 2023
@shayaantx
Copy link
Contributor Author

@Chaphasilor i tested out the changes with my own usage (seems fine), let me know if you find anything weird

Behavior now:

  • Now there is a delete and sync button (delete wipes out all offline downloads), sync will add/remove any changes to playlist/album
    image
  • If you have never synced the playlist/album, the delete button doesn't show, but the sync button will always show
    image
  • If you remove all items from a playlist (in jellyfin), then sync, it will remove all downloaded items, but leave behind the playlist (which can be manually removed) - the entries for each downloaded item will be gone.
  • If an item is shared across offline downloaded album/playlists, that item should not be removed (at least in my testing it was not). The caveat to this is, you must actually sync that offline item for both album/playlists (i.e., if you never click sync on playlistB but did on playlistA, the requiredBy field for the downloaded item has no reference to playlistB - which makes sense I think)

@Chaphasilor Chaphasilor linked an issue Dec 1, 2023 that may be closed by this pull request
@Chaphasilor
Copy link
Collaborator

@shayaantx I'd prefer that in the case where the playlist/album has not been downloaded and the delete icon is not shown, you show the download icon instead of the sync icon. The sync icon could be mistaken for a refresh icon, leaving users confused about how they can download the playlist :)

Regarding the outdate requiredBy reference, that is expected behavior I think.
In the end, the "Sync All" button in the settings will probably be the preferred way to sync downloads anyway, because it handles all the playlists and albums, but now with your improved logic.

Only thing I'm wondering about for the "Sync All" button, say that Playlist A removes a requiredBy for a certain song and Playlist B adds one for the same song, while syncing we should try to first update all "requiredBy"s and only then start deleting song.
Otherwise we end up deleting the song because it's not required by Playlist A anymore and that one is checked first, and then re-downloading it for Playlist B. What do you think? I guess we could address this in a follow-up PR once this one has been merged :)

@Chaphasilor
Copy link
Collaborator

Did some quick testing, so far everything works 😁
Also merged the main branch to fix some build errors due to conflicting dependencies for me.
I'll look into merging this once #197 is merged. I'm guessing there will be a few merge conflicts, as both PRs deal with the download button...

@shayaantx
Copy link
Contributor Author

Did some quick testing, so far everything works 😁 Also merged the main branch to fix some build errors due to conflicting dependencies for me. I'll look into merging this once #197 is merged. I'm guessing there will be a few merge conflicts, as both PRs deal with the download button...

Nice, I still need to change those buttons that you mentioned

Only thing I'm wondering about for the "Sync All" button, say that Playlist A removes a requiredBy for a certain song and Playlist B adds one for the same song, while syncing we should try to first update all "requiredBy"s and only then start deleting song.

This might be hard to do, need to think about it

@shayaantx
Copy link
Contributor Author

@Chaphasilor ok I made a slight modification to show the download button only if album/playlist hasn't been downloaded yet
image

then if you click download, the buttons state updates, and you get a delete/sync button
image

@Chaphasilor
Copy link
Collaborator

Chaphasilor commented Dec 5, 2023

Does your implementation add the new downloads to the downloads "queue"? So that it shows up on the "Downloads" screen as enqueued or running?
Because I noticed that the "Retry" button that attempts to re-download failed items does not reliably increase the "enqueued" amount...

@shayaantx
Copy link
Contributor Author

Does your implementation add the new downloads to the downloads "queue"? So that it shows up on the "Downloads" screen as enqueued or running? Because I noticed that the "Retry" button that attempts to re-download failed items does not reliably increase the "enqueued" amount...

I would assume this issue has always existed, cause looking at original code there is nothing explicit for that (that looks to all be within the downloads helper). I've changed the stuff above that (i.e., what to download and what to remove)

I will also mention that button (sync button on downloads screen), doesn't really do anything with UI when you click it, however I see it sync-ing my downloads if I leave the screen and come back.

@Chaphasilor
Copy link
Collaborator

Yeah, the UI part also needs some work. But that's a different story...

Is this PR ready to merge from your side?

@Chaphasilor
Copy link
Collaborator

Just fixed the conflicts after merging #197, please let me know if I made a mistake anywhere!

@Chaphasilor
Copy link
Collaborator

@shayaantx I noticed that if I download an album and then enable offline mode, it doesn't show up in the albums list until I restart the app. Can you reproduce?

@Chaphasilor
Copy link
Collaborator

Chaphasilor commented Dec 13, 2023

Another thing I noticed: if I delete an album but a track from that album is still in a playlist, the track stays downloaded on the device, but the album image does not. So the track is missing the album image, which isn't ideal. Not sure if that is in-scope for this PR though...

Edit: this also results in a null check exception when trying to go to the album

@shayaantx
Copy link
Contributor Author

shayaantx commented Dec 13, 2023

I noticed that if I download an album and then enable offline mode, it doesn't show up in the albums list until I restart the app. Can you reproduce?

Thats the thing I mentioned earlier about refreshing a bit more aggressively. So if you do following:

  1. Sync fresh album
  2. Enable offline mode
  3. I do see it doesn't show up, but if you tap the arrow button, it will appear.

So when the tab is in focus, its not doing a fresh load (offline enabled or not) - at least this is what I've seen.

Another thing I noticed: if I delete an album but a track from that album is still in a playlist, the track stays downloaded on the device, but the album image does not. So the track is missing the album image, which isn't ideal. Not sure if that is in-scope for this PR though...

Lemme investigate this

@Chaphasilor
Copy link
Collaborator

Ahh okay I see, yeah that sounds like it could be improved. But I think we can get this merged first, since it's not a regression!

@shayaantx
Copy link
Contributor Author

Ahh okay I see, yeah that sounds like it could be improved. But I think we can get this merged first, since it's not a regression!

Up to you boss, I can still look into the images removing kerfuffle too, doesn't matter to me.

@Chaphasilor
Copy link
Collaborator

Yeah the album covers fix would be nice. But no need to look into the refreshing :)

@shayaantx
Copy link
Contributor Author

@Chaphasilor

Another thing I noticed: if I delete an album but a track from that album is still in a playlist, the track stays downloaded on the device, but the album image does not. So the track is missing the album image, which isn't ideal. Not sure if that is in-scope for this PR though...

How are you reproducing it? Are you

  1. Create 2 playlists with one shared song that has an image
  2. Go to playlist A, sync
  3. Go to playlist B, sync
  4. Delete playlist A
  5. Then see missing image on playlist B

If I follow above steps I can't seem to reproduce the behavior you described, however when I debug the code, it seems to delete the image, but the UI doesn't reflect it - dunno, but it def seems like it is executing the remove download image future erroneously


  Future<void> addDownloads({
    required List<BaseItemDto> items,
    required BaseItemDto parent,
    required bool useHumanReadableNames,
    required DownloadLocation downloadLocation,

    /// The view that this download is in. Used for sorting in offline mode.
    required String viewId,
  }) async {
....
      for (final item in items) {
        _downloadsLogger.info("Attempting to download ${item.id}");
        if (_downloadedItemsBox.containsKey(item.id)) {
          // If the item already exists, add the parent item to its requiredBy field and skip actually downloading the song.
          // We also add the item to the downloadedChildren of the parent that we're downloading.
          _downloadsLogger.info(
              "Item ${item.id} already exists in downloadedItemsBox, adding requiredBy to DownloadedItem and adding to ${parent.id}'s downloadedChildren");

          // This is technically nullable but we check if it contains the key
          // in order to get to this point.
          DownloadedSong itemFromBox = _downloadedItemsBox.get(item.id)!;

          itemFromBox.requiredBy.add(parent.id);
          addDownloadedSong(itemFromBox);
          _addItemToDownloadedAlbum(parent.id, item);
          continue;
        }
....

This is the download jellyfin items function snippet, basically if the song already exists, it will basically put the song in all the cache/collections/requiredBy fields then continues to next item. What's missing here is adding requiredby updates to the downloaded image itself (if it exists) for the item we are downloading (which already exists). So this seems like an existing bug cause I didn't change this logic - but I can push a commit to fix 2moro or sometime this weekend.

@Chaphasilor
Copy link
Collaborator

What I did (iirc) was:

  1. Download playlist
  2. Download album of song in playlist
  3. Delete album

Could also be that I did 1 and 2 in reverse order...

If you could figure out how to fix it, that would be awesome! 😁

@shayaantx
Copy link
Contributor Author

@Chaphasilor I think I need to rewrite this entire download/remove function to make sense of this, like the way downloadImage gets managed is with requiredBy collection as well (however the ids we pass into this are song ids themselves). This is behavior I'm seeing so far:

  1. Sync both playlists
    image
  2. Delete "te1" playlist
    image
  3. Switch to offline mode
    image

You can see above the playlist loses parent image, but retains song images. I saw some more weird logic (not sure if its from debugging), where after deleting first playlist, the downloaded song was missing - but I can't reproduce that without breakpoints (maybe the futures weren't executing properly cause of my breakpoints)

Now if you modify steps to reproduce:

  1. Sync both playlists
  2. Delete one song from "te1" - "Inner Assassins"
  3. Then come back to downloads page
    image

You will get the entire shared song missing (this doesn't happen with the main sync button), but more cause the downloads helper does a hard delete in deleteDownloadChildren (which I kind of workaround in the download sync helper), the lil bit below

        if (downloadedSong.requiredBy.isEmpty || deletedFor == null) {

I dunno honestly, things get weird deleting, and all these collections used randomly everywhere to maintain persistent state isn't great. There seems like a greater need for a simpler model to store this data imo, but I'm super lazy :)

@shayaantx
Copy link
Contributor Author

Lemme debug it some more and see what I come up with

@shayaantx
Copy link
Contributor Author

@Chaphasilor ok I fixed the 2nd bug I mentioned by taking out the logic out of deleteDownloadChildren() that just deletes the song not including the deleteFor parameter (as that parameter causes the issue in this case and caused me some other problems down the line). The only annoying part is I duplicated that logic in a new method deleteSong(), cause the existing method honestly should be dismantled.

I'm still not able to reproduce the first issue you mentioned, maybe I can add push some logging changes, and you reproduce and post logs?

@Chaphasilor
Copy link
Collaborator

I'll try to repro it with your changes first, I'll take a look at those tomorrow! Sorry for not responding earlier...

@shayaantx
Copy link
Contributor Author

I'll try to repro it with your changes first, I'll take a look at those tomorrow! Sorry for not responding earlier...

fyi the recent change isn't for your reported issue (although I think* that happens with this entire PR), its for when someone manually deletes a single shared song

@Chaphasilor
Copy link
Collaborator

Repro is here:

2023-12-18-08-53-25_1.mp4

It really is just:

  1. Download playlist
  2. Download album of track in playlist
  3. Delete album again
  4. Track is now missing the cover

@shayaantx
Copy link
Contributor Author

@Chaphasilor oh you meant the cover image of the playlist/album not the individual track right? Cause I can reproduce the cover image disappearing with these steps, but the individual song still retains its image

@Chaphasilor
Copy link
Collaborator

Actually no, at the end on the songs tab you can see the song is missing the cover. The album is also missing the cover, but since it's deleted it disappears right after refreshing anyway...

@shayaantx
Copy link
Contributor Author

Actually no, at the end on the songs tab you can see the song is missing the cover. The album is also missing the cover, but since it's deleted it disappears right after refreshing anyway...

FYI I've gone a different direction with this one, basically trying to decouple the download storage (which I'm calling the download store or whatever) from the downloads helper while trying to refactor how all this storage works and hopefully in the end these type of cases become easier (I hope)

It is also possible I give up and just fix this specific use case :), but it really seems like all the Hive boxes shouldn't be all over the downloads helper cause then its always a case of did I put the right object in the right collection in new or existing methods (just makes seeing the downloaded songs and assets relationships much nicer I think).

Lemme know if this is overkill too

image

@Chaphasilor
Copy link
Collaborator

@shayaantx are you aware of the planned changes to the download functionality? Also discussed here: #213

Best read through it and make sure you're not putting in too much effort for something that will be completely re-built eventually :)

@shayaantx
Copy link
Contributor Author

@shayaantx are you aware of the planned changes to the download functionality? Also discussed here: #213

Best read through it and make sure you're not putting in too much effort for something that will be completely re-built eventually :)

Lol agree with all those bullet points, yea maybe I won't go into this I guess - I'll go back to trying to fix this specific edge case

@Chaphasilor
Copy link
Collaborator

Also, #568 just dropped, take a look at this. That is a bigger project though and will probably take a bit longer to hit stable, so don't worry about your changes becoming obsolete :)

We can get this PR merged as soon as you're happy with it!

@shayaantx
Copy link
Contributor Author

Also, #568 just dropped, take a look at this. That is a bigger project though and will probably take a bit longer to hit stable, so don't worry about your changes becoming obsolete :)

We can get this PR merged as soon as you're happy with it!

Yea that PR completely removes the downloads helper, which this PR hinges on

Looking at that PR I see _syncDownload() does what appears to be a much easier to grok way of downloading, no idea how it would affect this PR

So I guess you could merge it in this current state and Komodo5197 could merge in these changes into his or we could wait till he is done and merge this?

@Chaphasilor
Copy link
Collaborator

I'd merge this first. The other PR should be rebased anyway.

So we'll leave out the missing covers for now? It's not a huge deal anyway...

@shayaantx
Copy link
Contributor Author

fyi @Chaphasilor merged dec8e85 to resolve conflicts

@Chaphasilor Chaphasilor merged commit cc2c208 into jmshrv:main Dec 24, 2023
2 checks passed
@Chaphasilor
Copy link
Collaborator

Merged. Merry Christmas 🎄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Download sync not working (or PEBKAC) Download all missing songs on playlist/album
2 participants