reworked "SearchWindow" to use threading

This commit is contained in:
Martin Wagner 2021-09-24 18:45:58 +02:00
parent 3bb968a772
commit 11e3699d28

View File

@ -1793,75 +1793,127 @@ class ArtistPopover(Gtk.Popover):
# browser #
###########
class SearchThread(threading.Thread):
def __init__(self, client, search_entry, songs_window, hits_label, search_tag):
super().__init__(daemon=True)
self._client=client
self._search_entry=search_entry
self._songs_view=songs_window.get_treeview()
self._store=self._songs_view.get_model()
self._action_bar=songs_window.get_action_bar()
self._hits_label=hits_label
self._search_tag=search_tag
self._stop_flag=False
self._callback=None
def set_callback(self, callback):
self._callback=callback
def stop(self):
self._stop_flag=True
def start(self):
self._songs_view.clear()
self._hits_label.set_text("")
self._action_bar.set_sensitive(False)
self._search_text=self._search_entry.get_text()
if self._search_text:
super().start()
else:
self._exit()
def run(self):
hits=0
stripe_size=1000
songs=self._get_songs(0, stripe_size)
stripe_start=stripe_size
while songs:
hits+=len(songs)
if not self._append_songs(songs):
self._exit()
return
GLib.idle_add(self._search_entry.progress_pulse)
GLib.idle_add(self._hits_label.set_text, ngettext("{hits} hit", "{hits} hits", hits).format(hits=hits))
stripe_end=stripe_start+stripe_size
songs=self._get_songs(stripe_start, stripe_end)
stripe_start=stripe_end
if hits > 0:
GLib.idle_add(self._action_bar.set_sensitive, True)
self._exit()
def _exit(self):
def callback():
self._search_entry.set_progress_fraction(0.0)
if self._callback is not None:
self._callback()
return False
GLib.idle_add(callback)
@main_thread_function
def _get_songs(self, start, end):
if self._stop_flag:
return []
else:
self._client.restrict_tagtypes("track", "title", "artist", "album")
songs=self._client.search(self._search_tag, self._search_text, "window", f"{start}:{end}")
self._client.tagtypes("all")
return songs
@main_thread_function
def _append_songs(self, songs):
for song in songs:
if self._stop_flag:
return False
try:
int_track=int(song["track"][0])
except ValueError:
int_track=0
self._store.insert_with_valuesv(-1, range(7), [
str(song["track"]), str(song["title"]),
str(song["artist"]), str(song["album"]),
str(song["duration"]), song["file"],
int_track
])
return True
class SearchWindow(Gtk.Box):
def __init__(self, client):
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self._client=client
self._stop_flag=False
self._done=True
self._pending=[]
# tag switcher
# widgets
self._tag_combo_box=Gtk.ComboBoxText()
# search entry
self.search_entry=Gtk.SearchEntry()
# label
self._hits_label=Gtk.Label(xalign=1)
# store
# songs window
# (track, title, artist, album, duration, file, sort track)
self._store=Gtk.ListStore(str, str, str, str, str, str, int)
self._store.set_default_sort_func(lambda *args: 0)
# songs window
self._songs_window=SongsWindow(self._client, self._store, 5)
# action bar
self._action_bar=self._songs_window.get_action_bar()
self._action_bar.set_sensitive(False)
# songs view
self._songs_view=self._songs_window.get_treeview()
# columns
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True)
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0)
column_data=(
(_("No"), renderer_text_ralign, False, 0, 6),
(_("Title"), renderer_text, True, 1, 1),
(_("Artist"), renderer_text, True, 2, 2),
(_("Album"), renderer_text, True, 3, 3),
(_("Length"), renderer_text_ralign, False, 4, 4),
)
for title, renderer, expand, text, sort in column_data:
column=Gtk.TreeViewColumn(title, renderer, text=text)
column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
column.set_property("resizable", False)
column.set_property("expand", expand)
column.set_sort_column_id(sort)
self._songs_view.append_column(column)
column_track=Gtk.TreeViewColumn(_("No"), renderer_text_ralign, text=0)
column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
column_track.set_property("resizable", False)
self._songs_view.append_column(column_track)
column_title=Gtk.TreeViewColumn(_("Title"), renderer_text, text=1)
column_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
column_title.set_property("resizable", False)
column_title.set_property("expand", True)
self._songs_view.append_column(column_title)
column_artist=Gtk.TreeViewColumn(_("Artist"), renderer_text, text=2)
column_artist.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
column_artist.set_property("resizable", False)
column_artist.set_property("expand", True)
self._songs_view.append_column(column_artist)
column_album=Gtk.TreeViewColumn(_("Album"), renderer_text, text=3)
column_album.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
column_album.set_property("resizable", False)
column_album.set_property("expand", True)
self._songs_view.append_column(column_album)
column_time=Gtk.TreeViewColumn(_("Length"), renderer_text, text=4)
column_time.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
column_time.set_property("resizable", False)
self._songs_view.append_column(column_time)
column_track.set_sort_column_id(6)
column_title.set_sort_column_id(1)
column_artist.set_sort_column_id(2)
column_album.set_sort_column_id(3)
column_time.set_sort_column_id(4)
# search thread
self._search_thread=SearchThread(self._client, self.search_entry, self._songs_window, self._hits_label, "any")
# connect
self.search_entry.connect("activate", self._search)
@ -1883,107 +1935,44 @@ class SearchWindow(Gtk.Box):
self.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
self.pack_start(self._songs_window, True, True, 0)
def _clear(self, *args):
if self._done:
self.search_entry.handler_block(self._search_entry_changed)
self._tag_combo_box.handler_block(self._tag_combo_box_changed)
self._songs_view.clear()
self.search_entry.set_text("")
self._tag_combo_box.remove_all()
self.search_entry.handler_unblock(self._search_entry_changed)
self._tag_combo_box.handler_unblock(self._tag_combo_box_changed)
elif not self._clear in self._pending:
self._stop_flag=True
self._pending.append(self._clear)
def _on_disconnected(self, *args):
self._tag_combo_box.set_sensitive(False)
self.search_entry.set_sensitive(False)
self._clear()
self._search_thread.stop()
def _on_reconnected(self, *args):
if self._done:
def callback():
self._action_bar.set_sensitive(False)
self._songs_view.clear()
self._hits_label.set_text("")
self.search_entry.handler_block(self._search_entry_changed)
self.search_entry.set_text("")
self.search_entry.handler_unblock(self._search_entry_changed)
self._tag_combo_box.handler_block(self._tag_combo_box_changed)
self._tag_combo_box.remove_all()
self._tag_combo_box.append_text(_("all tags"))
for tag in self._client.tagtypes():
if not tag.startswith("MUSICBRAINZ"):
self._tag_combo_box.append_text(tag)
self._tag_combo_box.set_active(0)
self._tag_combo_box.set_sensitive(True)
self.search_entry.set_sensitive(True)
self._tag_combo_box.handler_unblock(self._tag_combo_box_changed)
elif not self._on_reconnected in self._pending:
self._stop_flag=True
self._pending.append(self._on_reconnected)
if self._search_thread.is_alive():
self._search_thread.set_callback(callback)
self._search_thread.stop()
else:
callback()
def _search(self, *args):
if self._done:
self._done=False
self._songs_view.clear()
self._hits_label.set_text("")
self._action_bar.set_sensitive(False)
hits=0
if self.search_entry.get_text():
if self._tag_combo_box.get_active() == 0:
search_tag="any"
else:
search_tag=self._tag_combo_box.get_active_text()
search_text=self.search_entry.get_text()
stripe_size=1000
self._client.restrict_tagtypes("track", "title", "artist", "album")
try: # client cloud meanwhile disconnect
songs=self._client.search(search_tag, search_text, "window", f"0:{stripe_size}")
except MPDBase.ConnectionError:
self._done_callback()
return
stripe_start=stripe_size
while songs:
hits+=len(songs)
for song in songs:
if self._stop_flag:
self._done_callback()
return
try:
int_track=int(song["track"][0])
except ValueError:
int_track=0
self._store.insert_with_valuesv(-1, range(7), [
str(song["track"]), str(song["title"]),
str(song["artist"]), str(song["album"]),
str(song["duration"]), song["file"],
int_track
])
self.search_entry.progress_pulse()
self._hits_label.set_text(ngettext("{hits} hit", "{hits} hits", hits).format(hits=hits))
while Gtk.events_pending():
Gtk.main_iteration_do(True)
stripe_end=stripe_start+stripe_size
try: # client cloud meanwhile disconnect
songs=self._client.search(search_tag, search_text, "window", f"{stripe_start}:{stripe_end}")
except MPDBase.ConnectionError:
self._done_callback()
return
stripe_start+=stripe_size
self._client.tagtypes("all")
if hits > 0:
self._action_bar.set_sensitive(True)
self._done_callback()
elif not self._search in self._pending:
self._stop_flag=True
self._pending.append(self._search)
def _done_callback(self, *args):
self.search_entry.set_progress_fraction(0.0)
self._stop_flag=False
self._done=True
pending=self._pending
self._pending=[]
for p in pending:
try:
p()
except:
pass
return False
def callback():
if self._tag_combo_box.get_active() == 0:
search_tag="any"
else:
search_tag=self._tag_combo_box.get_active_text()
self._search_thread=SearchThread(self._client, self.search_entry, self._songs_window, self._hits_label, search_tag)
self._search_thread.start()
if self._search_thread.is_alive():
self._search_thread.set_callback(callback)
self._search_thread.stop()
else:
callback()
def _on_search_entry_focus_event(self, widget, event, focus):
app=self.get_toplevel().get_application()