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

Fix / improve Gnome SearchProvider #4501

Merged
merged 4 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 27 additions & 16 deletions quodlibet/ext/events/searchprovider.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2013 Christoph Reiter <reiter.christoph@gmail.com>
# 2023 Nick Boultbee
# 2024 Nick Boultbee
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
Expand All @@ -16,6 +16,10 @@

import os
import sys
from pathlib import Path
from collections.abc import Collection

from quodlibet.util.thumbnails import get_thumbnail, get_cache_info

if os.name == "nt" or sys.platform == "darwin":
from quodlibet.plugins import PluginNotSupportedError
Expand All @@ -24,7 +28,7 @@
from gi.repository import GLib
from gi.repository import Gio

from quodlibet import _
from quodlibet import _, print_d
from quodlibet import app
from quodlibet.util.dbusutils import dbus_unicode_validate
from quodlibet.plugins.events import EventPlugin
Expand All @@ -34,18 +38,19 @@
from quodlibet.qltk import Icons

DEFAULT_SEARCH_PROVIDER_DIR = "/usr/share/gnome-shell/search-providers"
THUMBNAIL_SIZE = (256, 256)


def get_gs_provider_files():
def get_gs_provider_files() -> Collection[Path]:
"""Return all installed search provider files for GNOME Shell"""

ini_files = []
for d in xdg_get_system_data_dirs():
path = os.path.join(d, "gnome-shell", "search-providers")
path = Path(d) / "gnome-shell" / "search-providers"
try:
for entry in os.listdir(path):
if entry.endswith(".ini"):
ini_files.append(os.path.join(path, entry))
ini_files.append(path / entry)
except OSError:
pass
return ini_files
Expand Down Expand Up @@ -106,6 +111,7 @@ def get_songs_for_ids(library, ids):
ids.discard(song_id)
if not ids:
break
print_d(f"Got {len(songs)} songs matching {ids}")
return songs


Expand Down Expand Up @@ -199,6 +205,7 @@ def Introspect(self):
return self.__doc__

def GetInitialResultSet(self, terms):
print_d(f"Getting initial result set for {terms}")
if terms:
query = Query("")
for term in terms:
Expand All @@ -211,26 +218,30 @@ def GetInitialResultSet(self, terms):
return ids

def GetSubsearchResultSet(self, previous_results, terms):
query = Query("")
for term in terms:
query &= Query(term)

songs = get_songs_for_ids(app.library, previous_results)
ids = [get_song_id(s) for s in songs if query.search(s)]
return ids
# Eager searching-as-you-type in Gnome makes this useless it seems,
# so just use the full terms each time.
return self.GetInitialResultSet(terms)

def GetResultMetas(self, identifiers):
print_d(f"Getting result metas for {identifiers}")
metas = []
for song in get_songs_for_ids(app.library, identifiers):
name = song("title")
description = song("~artist~title")
description = song.comma("~people")
song_id = get_song_id(song)
cover = app.cover_manager.get_cover(song)
if cover:
# We need a permanent-ish copy of this for DBus clients
get_thumbnail(cover.name, THUMBNAIL_SIZE, ignore_temp=False)
p = get_cache_info(Path(cover.name), THUMBNAIL_SIZE)[0]
gicon = str(p)
else:
gicon = ENTRY_ICON
meta = {
"name": GLib.Variant("s", dbus_unicode_validate(name)),
"id": GLib.Variant("s", song_id),
"description": GLib.Variant(
"s", dbus_unicode_validate(description)),
"gicon": GLib.Variant("s", ENTRY_ICON)
"description": GLib.Variant("s", dbus_unicode_validate(description)),
"gicon": GLib.Variant("s", gicon)
}
metas.append(meta)

Expand Down
4 changes: 2 additions & 2 deletions quodlibet/formats/_id3.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def get_images(self):
return images

for frame in tag.getall("APIC"):
f = get_temp_cover_file(frame.data)
f = get_temp_cover_file(frame.data, frame.mime)
images.append(EmbeddedImage(f, frame.mime, type_=frame.type))

images.sort(key=lambda c: c.sort_key)
Expand Down Expand Up @@ -506,7 +506,7 @@ def get_primary_image(self):
break

if cover:
f = get_temp_cover_file(cover.data)
f = get_temp_cover_file(cover.data, cover.mime)
return EmbeddedImage(f, cover.mime, type_=cover.type)

def set_image(self, image):
Expand Down
4 changes: 2 additions & 2 deletions quodlibet/formats/mp4.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def get_images(self):
else:
mime = "image/"

f = get_temp_cover_file(cover)
f = get_temp_cover_file(cover, mime)
images.append(EmbeddedImage(f, mime))

return images
Expand All @@ -193,7 +193,7 @@ def get_primary_image(self):
else:
mime = "image/"

f = get_temp_cover_file(cover)
f = get_temp_cover_file(cover, mime)
return EmbeddedImage(f, mime)

can_change_images = True
Expand Down
4 changes: 2 additions & 2 deletions quodlibet/formats/wma.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def get_images(self):
(mime, desc, data, type_) = unpack_image(image.value)
except ValueError:
continue
f = get_temp_cover_file(data)
f = get_temp_cover_file(data, mime)
images.append(EmbeddedImage(f, mime, type_=type_))

images.sort(key=lambda c: c.sort_key)
Expand All @@ -186,7 +186,7 @@ def get_primary_image(self):
except ValueError:
continue
if type_ == APICType.COVER_FRONT: # Only cover images
f = get_temp_cover_file(data)
f = get_temp_cover_file(data, mime)
return EmbeddedImage(f, mime, type_=type_)

can_change_images = True
Expand Down
10 changes: 5 additions & 5 deletions quodlibet/formats/xiph.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def get_images(self):
if cover:
mime = audio.get("coverartmime")
mime = (mime and mime[0]) or "image/"
f = get_temp_cover_file(cover)
f = get_temp_cover_file(cover, mime)
images.append(EmbeddedImage(f, mime))

images.sort(key=lambda c: c.sort_key)
Expand Down Expand Up @@ -179,7 +179,7 @@ def get_primary_image(self):
cover = cover or pic

if cover:
f = get_temp_cover_file(cover.data)
f = get_temp_cover_file(cover.data, cover.mime)
return EmbeddedImage(
f, cover.mime, cover.width, cover.height, cover.depth,
cover.type)
Expand All @@ -196,7 +196,7 @@ def get_primary_image(self):

mime = audio.get("coverartmime")
mime = (mime and mime[0]) or "image/"
f = get_temp_cover_file(cover)
f = get_temp_cover_file(cover, mime)
return EmbeddedImage(f, mime)

def clear_images(self):
Expand Down Expand Up @@ -407,7 +407,7 @@ def get_images(self):
return images

for cover in tag.pictures:
fileobj = get_temp_cover_file(cover.data)
fileobj = get_temp_cover_file(cover.data, cover.mime)
images.append(EmbeddedImage(
fileobj, cover.mime, cover.width, cover.height, cover.depth,
cover.type))
Expand All @@ -431,7 +431,7 @@ def get_primary_image(self):
covers.sort(key=lambda c: APICType.sort_key(c.type))
cover = covers[0]

fileobj = get_temp_cover_file(cover.data)
fileobj = get_temp_cover_file(cover.data, cover.mime)
return EmbeddedImage(
fileobj, cover.mime, cover.width, cover.height, cover.depth,
cover.type)
Expand Down
4 changes: 2 additions & 2 deletions quodlibet/util/cover/built_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from quodlibet import _
from quodlibet.plugins.cover import CoverSourcePlugin
from quodlibet.util.dprint import print_w
from quodlibet.util.dprint import print_w, print_d
from quodlibet import config


Expand Down Expand Up @@ -95,7 +95,7 @@ def cover(self):
# TODO: Deserves some refactoring
if not self.song.is_file:
return None

print_d(f"Searching for local cover for {self.song('~filename')}")
base = self.song("~dirname")
images = []

Expand Down
17 changes: 13 additions & 4 deletions quodlibet/util/path.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2004-2009 Joe Wreschnig, Michael Urman, Steven Robertson
# 2011-2022 Nick Boultbee
# 2011-2024 Nick Boultbee
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -13,6 +13,7 @@
import errno
import codecs
import shlex
from typing import Any
from urllib.parse import urlparse, quote, unquote

from gi.repository import GLib
Expand Down Expand Up @@ -313,17 +314,25 @@ def xdg_get_user_dirs():
return {}


def get_temp_cover_file(data):
def get_temp_cover_file(data: bytes,
mime: str | None = None) -> Any:
"""Returns a file object or None"""

try:
suffix = None
if mime:
mime = mime.lower()
if "png" in mime:
suffix = fsnative(".png")
elif "jpg" in mime or "jpeg" in mime:
suffix = fsnative(".jpg")
# pass fsnative so that mkstemp() uses unicode on Windows
fn = NamedTemporaryFile(prefix=fsnative("tmp"))
fn = NamedTemporaryFile(prefix=fsnative("cover-"), suffix=suffix)
fn.write(data)
fn.flush()
fn.seek(0, 0)
except OSError:
return
return None
else:
return fn

Expand Down