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

Switch mpris cover art cache to use a hash of the url #47

Closed
wants to merge 17 commits into from

Conversation

ferrreo
Copy link

@ferrreo ferrreo commented Aug 18, 2023

This fixes an issue where if the track + artist name is missing you would get whatever the cover art for the first track with those missing you listened to is. This way as long as there is a url for the cover art you will always get the correct one back.

The original bug happens for me when opening spotify with a track paused as it will return cover art via mpris but no artist or track title (gotta love spotify bugs).

@Aylur
Copy link
Owner

Aylur commented Aug 19, 2023

I feel like a cryptographic hash function is a but overkill for this, why not just use the url instead of artist+title?

@ferrreo
Copy link
Author

ferrreo commented Aug 19, 2023

Using a hash as a cache key is an extremely common usecase for hashes. It ensures a minimum of clashes whilst maximising hit rates and ensuring a consistent format that doesn't need extra processing for length/format to work with fillesystem etc.

I have added another improvement, a cache index limited to 100 that repopulates on restart and will purge the oldest entry if it goes over the 100. I added this because I noticed my media cache dir was hitting 3.5gb in size which is a bit excessive.

Copy link
Owner

@Aylur Aylur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of deleting cache if it hits a certain point
There should be a CacheService, and then Mpris, Notifications, Applications could use it and so could the user for custom stuff
then we could remove the clear-cache cli option since it would be obsolete

src/service/mpris.ts Outdated Show resolved Hide resolved
src/service/mpris.ts Outdated Show resolved Hide resolved
@@ -137,18 +144,50 @@ class MprisPlayer extends GObject.Object {
}

_cacheCoverArt() {
const { trackCoverUrl, coverPath } = this;
if (trackCoverUrl === '') {
if (this.trackCoverUrl === '') {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be

if (!this.trackCoverUrl)
  return

and mark coverPath as optional

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't set this.coverPath here if we go from song with cover to song without cover it will continue to have the old cover showing instead of a blank/no cover.

@@ -163,10 +202,46 @@ class MprisPlayer extends GObject.Object {
this.emit('changed');
}
catch (e) {
logError(e as Error, `failed to cache ${coverPath}`);
logError(e as Error, `failed to cache ${trackCoverUrl}`);
return "";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an async function this return value wont get assigned to coverPath
and even if there was an error caching, the function continues to mark it cached in the json file

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

timestamp: Date.now(),
};

if (Object.keys(this._coverCache).length > 100) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of hardcoding it to 100, there should be an option in App.config for it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


const cachePath = MEDIA_CACHE_PATH + '/covercache.json';
const file = Gio.File.new_for_path(cachePath);
const result = file.replace_contents(JSON.stringify(this._coverCache), null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Utils.writeFile instead

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@ferrreo
Copy link
Author

ferrreo commented Aug 24, 2023

I like the idea of deleting cache if it hits a certain point There should be a CacheService, and then Mpris, Notifications, Applications could use it and so could the user for custom stuff then we could remove the clear-cache cli option since it would be obsolete

Will work on making this a service tonight

@ferrreo
Copy link
Author

ferrreo commented Aug 26, 2023

I like the idea of deleting cache if it hits a certain point There should be a CacheService, and then Mpris, Notifications, Applications could use it and so could the user for custom stuff then we could remove the clear-cache cli option since it would be obsolete

Will work on making this a service tonight

done

@Aylur
Copy link
Owner

Aylur commented Aug 30, 2023

for some reason this breaks mpris for me, ListNamesRemote runs twice on startup once correcty and again with the names undefined, but even though the names are correctly checked they are not added
seems like it hangs on the new MprisPlayer(busName) line

@Aylur
Copy link
Owner

Aylur commented Aug 30, 2023

I was thinking something like this

class CacheObject {
    static {
        Service.register(this, {
            'added': ['string'], // signal the path or url
            'removed': ['string'], // signal the path or url
        });
    }

    private _limit: number;
    private _path: string;
    private _cache: {
        [sha: string]: {
            path: string,
            timestamp: number,
        }
    };

    // or consider using a Map object for performance
    // although this needs a bit more work to write and read from a json file
    private _cacheMap: Map<string, {
        path: string,
        timestamp: number,
    }>;

    // url is the input for the sha, returns the path to file
    getImage(url: string): string { }

    // cache, or overwrite it and check for the oldest
    addImage(url: string): Promise<string>;
}

class CacheService {
    // no signals for this

    private _caches: { [name: string]: CacheObject };

    constructor() {
        // reload from caches.json that stores the paths to the cache json files
    }

    // creates a new CacheObject or returns the existing one
    getCache(name: string): CacheObject
}

the cache user can then decide whether to overwrite the image by calling addImage or not

I would also consider updating the .json files only when the app has quit, although this might not matter that much

App.instance.connect('shutdown', writeFile)

@ferrreo
Copy link
Author

ferrreo commented Aug 31, 2023

I like the simplified design I'll update to that tomorrow and fix the conflicts.

I don't think writing on shutdown is a good idea or you risk orphans on an improper shutdown and it isn't exactly costly. Though a batch add might be useful so you can cache many things at once and only write the index once.

As for the issue in startup, I've not changed the flow around that at all? Maybe the changes have uncovered/made worse an existing race condition? It sounds similar to the issues I also get on the original mpris code. I'll try replicate and fix.

@Eclextic
Copy link

I like the simplified design I'll update to that tomorrow and fix the conflicts.

Well that aged like fine wine XD

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.

3 participants