mirror of
https://github.com/SoongNoonien/mpdevil.git
synced 2023-08-10 21:12:44 +03:00
reworked "SearchWindow" to use threading
This commit is contained in:
parent
3bb968a772
commit
11e3699d28
265
bin/mpdevil
265
bin/mpdevil
|
@ -1793,75 +1793,127 @@ class ArtistPopover(Gtk.Popover):
|
||||||
# browser #
|
# 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):
|
class SearchWindow(Gtk.Box):
|
||||||
def __init__(self, client):
|
def __init__(self, client):
|
||||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||||
self._client=client
|
self._client=client
|
||||||
self._stop_flag=False
|
|
||||||
self._done=True
|
|
||||||
self._pending=[]
|
|
||||||
|
|
||||||
# tag switcher
|
# widgets
|
||||||
self._tag_combo_box=Gtk.ComboBoxText()
|
self._tag_combo_box=Gtk.ComboBoxText()
|
||||||
|
|
||||||
# search entry
|
|
||||||
self.search_entry=Gtk.SearchEntry()
|
self.search_entry=Gtk.SearchEntry()
|
||||||
|
|
||||||
# label
|
|
||||||
self._hits_label=Gtk.Label(xalign=1)
|
self._hits_label=Gtk.Label(xalign=1)
|
||||||
|
|
||||||
# store
|
# songs window
|
||||||
# (track, title, artist, album, duration, file, sort track)
|
# (track, title, artist, album, duration, file, sort track)
|
||||||
self._store=Gtk.ListStore(str, str, str, str, str, str, int)
|
self._store=Gtk.ListStore(str, str, str, str, str, str, int)
|
||||||
self._store.set_default_sort_func(lambda *args: 0)
|
self._store.set_default_sort_func(lambda *args: 0)
|
||||||
|
|
||||||
# songs window
|
|
||||||
self._songs_window=SongsWindow(self._client, self._store, 5)
|
self._songs_window=SongsWindow(self._client, self._store, 5)
|
||||||
|
|
||||||
# action bar
|
|
||||||
self._action_bar=self._songs_window.get_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()
|
self._songs_view=self._songs_window.get_treeview()
|
||||||
|
|
||||||
# columns
|
# columns
|
||||||
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True)
|
renderer_text=Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.END, ellipsize_set=True)
|
||||||
renderer_text_ralign=Gtk.CellRendererText(xalign=1.0)
|
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)
|
# search thread
|
||||||
column_track.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
self._search_thread=SearchThread(self._client, self.search_entry, self._songs_window, self._hits_label, "any")
|
||||||
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)
|
|
||||||
|
|
||||||
# connect
|
# connect
|
||||||
self.search_entry.connect("activate", self._search)
|
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(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0)
|
||||||
self.pack_start(self._songs_window, True, True, 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):
|
def _on_disconnected(self, *args):
|
||||||
self._tag_combo_box.set_sensitive(False)
|
self._search_thread.stop()
|
||||||
self.search_entry.set_sensitive(False)
|
|
||||||
self._clear()
|
|
||||||
|
|
||||||
def _on_reconnected(self, *args):
|
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.handler_block(self._tag_combo_box_changed)
|
||||||
|
self._tag_combo_box.remove_all()
|
||||||
self._tag_combo_box.append_text(_("all tags"))
|
self._tag_combo_box.append_text(_("all tags"))
|
||||||
for tag in self._client.tagtypes():
|
for tag in self._client.tagtypes():
|
||||||
if not tag.startswith("MUSICBRAINZ"):
|
if not tag.startswith("MUSICBRAINZ"):
|
||||||
self._tag_combo_box.append_text(tag)
|
self._tag_combo_box.append_text(tag)
|
||||||
self._tag_combo_box.set_active(0)
|
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)
|
self._tag_combo_box.handler_unblock(self._tag_combo_box_changed)
|
||||||
elif not self._on_reconnected in self._pending:
|
if self._search_thread.is_alive():
|
||||||
self._stop_flag=True
|
self._search_thread.set_callback(callback)
|
||||||
self._pending.append(self._on_reconnected)
|
self._search_thread.stop()
|
||||||
|
else:
|
||||||
|
callback()
|
||||||
|
|
||||||
def _search(self, *args):
|
def _search(self, *args):
|
||||||
if self._done:
|
def callback():
|
||||||
self._done=False
|
if self._tag_combo_box.get_active() == 0:
|
||||||
self._songs_view.clear()
|
search_tag="any"
|
||||||
self._hits_label.set_text("")
|
else:
|
||||||
self._action_bar.set_sensitive(False)
|
search_tag=self._tag_combo_box.get_active_text()
|
||||||
hits=0
|
self._search_thread=SearchThread(self._client, self.search_entry, self._songs_window, self._hits_label, search_tag)
|
||||||
if self.search_entry.get_text():
|
self._search_thread.start()
|
||||||
if self._tag_combo_box.get_active() == 0:
|
if self._search_thread.is_alive():
|
||||||
search_tag="any"
|
self._search_thread.set_callback(callback)
|
||||||
else:
|
self._search_thread.stop()
|
||||||
search_tag=self._tag_combo_box.get_active_text()
|
else:
|
||||||
search_text=self.search_entry.get_text()
|
callback()
|
||||||
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 _on_search_entry_focus_event(self, widget, event, focus):
|
def _on_search_entry_focus_event(self, widget, event, focus):
|
||||||
app=self.get_toplevel().get_application()
|
app=self.get_toplevel().get_application()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user