diff --git a/lollypop/application.py b/lollypop/application.py index 785b9639752abb42a8dee9afc6519a70ed820645..dc7ad13240973fd408ceed71069a6cfd43ff56a8 100644 --- a/lollypop/application.py +++ b/lollypop/application.py @@ -28,7 +28,7 @@ from urllib.parse import urlparse from lollypop.utils import init_proxy_from_gnome, emit_signal from lollypop.application_actions import ApplicationActions from lollypop.utils_file import is_audio, is_pls, install_youtube_dl -from lollypop.define import Type, LOLLYPOP_DATA_PATH, ScanType, StorageType +from lollypop.define import LOLLYPOP_DATA_PATH, ScanType, StorageType from lollypop.database import Database from lollypop.player import Player from lollypop.inhibitor import Inhibitor @@ -46,8 +46,6 @@ from lollypop.notification import NotificationManager from lollypop.playlists import Playlists from lollypop.objects_track import Track from lollypop.objects_album import Album -from lollypop.objects_radio import Radio -from lollypop.radios import Radios from lollypop.helper_task import TaskHelper from lollypop.helper_art import ArtHelper from lollypop.collection_scanner import CollectionScanner @@ -175,7 +173,6 @@ class Application(Gtk.Application, ApplicationActions): self.artists = ArtistsDatabase(self.db) self.genres = GenresDatabase(self.db) self.tracks = TracksDatabase(self.db) - self.radios = Radios() self.player = Player() self.inhibitor = Inhibitor() self.scanner = CollectionScanner() @@ -365,13 +362,6 @@ class Application(Gtk.Application, ApplicationActions): open(LOLLYPOP_DATA_PATH + "/player.bin", "wb")) dump(self.player.queue, open(LOLLYPOP_DATA_PATH + "/queue.bin", "wb")) - # Save current playlist - if isinstance(self.player.current_track, Radio): - playlist_ids = [Type.RADIOS] - else: - playlist_ids = [] - dump(playlist_ids, - open(LOLLYPOP_DATA_PATH + "/playlist_ids.bin", "wb")) if self.player.current_track.id is not None: position = self.player.position else: @@ -397,7 +387,6 @@ class Application(Gtk.Application, ApplicationActions): SqlCursor.remove(self.db) self.cache.clean(True) - from lollypop.radios import Radios with SqlCursor(self.db) as sql: sql.isolation_level = None sql.execute("VACUUM") @@ -406,10 +395,6 @@ class Application(Gtk.Application, ApplicationActions): sql.isolation_level = None sql.execute("VACUUM") sql.isolation_level = "" - with SqlCursor(Radios()) as sql: - sql.isolation_level = None - sql.execute("VACUUM") - sql.isolation_level = "" except Exception as e: Logger.error("Application::__vacuum(): %s" % e) @@ -538,13 +523,7 @@ class Application(Gtk.Application, ApplicationActions): for uri in audio_uris: parsed = urlparse(uri) if parsed.scheme in ["http", "https"]: - from lollypop.objects_radio import Radio - radio = Radio(Type.RADIOS) - radio.set_name(uri) - radio.set_uri(uri) - self.player.load(radio) - break - # Lollypop does not support radio playlists + pass else: to_scan_uris.append(uri) self.scanner.update(ScanType.EXTERNAL, to_scan_uris) diff --git a/lollypop/art.py b/lollypop/art.py index 77c88935c743bb1f406481f2c621dfef638deff2..380cc4d33580ed9e1a3356c958f1927f78d1e788 100644 --- a/lollypop/art.py +++ b/lollypop/art.py @@ -17,7 +17,6 @@ from hashlib import md5 from lollypop.art_base import BaseArt from lollypop.art_album import AlbumArt from lollypop.art_artist import ArtistArt -from lollypop.art_radio import RadioArt from lollypop.art_downloader import DownloaderArt from lollypop.logger import Logger from lollypop.define import CACHE_PATH, ALBUMS_WEB_PATH, ALBUMS_PATH @@ -27,7 +26,7 @@ from lollypop.utils import emit_signal from lollypop.utils_file import create_dir, remove_oldest -class Art(BaseArt, AlbumArt, ArtistArt, RadioArt, DownloaderArt): +class Art(BaseArt, AlbumArt, ArtistArt, DownloaderArt): """ Global artwork manager """ @@ -39,7 +38,6 @@ class Art(BaseArt, AlbumArt, ArtistArt, RadioArt, DownloaderArt): BaseArt.__init__(self) AlbumArt.__init__(self) ArtistArt.__init__(self) - RadioArt.__init__(self) DownloaderArt.__init__(self) # Move old store # FIXME: Remove this later diff --git a/lollypop/art_base.py b/lollypop/art_base.py index 474677f096f98e6038a2561b885cc69c4cfd614e..011f0cbe4dd4e5c00c318597ff1000ae87032a02 100644 --- a/lollypop/art_base.py +++ b/lollypop/art_base.py @@ -28,7 +28,6 @@ class BaseArt(GObject.GObject): "album-artwork-changed": (GObject.SignalFlags.RUN_FIRST, None, (int,)), "artist-artwork-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)), - "radio-artwork-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)), "uri-artwork-found": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), } diff --git a/lollypop/art_radio.py b/lollypop/art_radio.py deleted file mode 100644 index 164d3458844aab0ff0c80251fc5737664facd428..0000000000000000000000000000000000000000 --- a/lollypop/art_radio.py +++ /dev/null @@ -1,207 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import GLib, GdkPixbuf, Gio - -import re - -from lollypop.helper_task import TaskHelper -from lollypop.logger import Logger -from lollypop.define import ArtBehaviour, CACHE_PATH -from lollypop.utils import escape, emit_signal -from lollypop.utils_file import create_dir - - -class RadioArt: - """ - Manage radio artwork - """ - _RADIOS_PATH = GLib.get_user_data_dir() + "/lollypop/radios" - - def __init__(self): - """ - Init radio art - Should be inherited by a BaseArt - """ - create_dir(self._RADIOS_PATH) - - def get_radio_cache_path(self, name, width, height): - """ - get cover cache path for radio - @param name as str - @param width as int - @param height as int - @return cover path as string or None if no cover - """ - filename = "" - try: - filename = self.__get_radio_cache_name(name) - cache_path_png = "%s/%s_%s_%s.png" % (CACHE_PATH, - filename, - width, - height) - f = Gio.File.new_for_path(cache_path_png) - if f.query_exists(): - return cache_path_png - else: - self.get_radio_artwork(name, width, height, 1) - if f.query_exists(): - return cache_path_png - except Exception as e: - Logger.error("RadioArt::get_radio_cache_path(): %s, %s" % - (e, ascii(filename))) - return None - - def get_radio_artwork(self, name, width, height, scale_factor, - behaviour=ArtBehaviour.CACHE | - ArtBehaviour.CROP_SQUARE): - """ - Return a cairo surface for radio name - @param name as string - @param width as int - @param height as int - @param scale_factor as int - @param behaviour as ArtBehaviour - @return GdkPixbuf.Pixbuf - """ - width *= scale_factor - height *= scale_factor - filename = self.__get_radio_cache_name(name) - cache_path_png = "%s/%s_%s_%s.png" % (CACHE_PATH, filename, - width, height) - pixbuf = None - try: - # Look in cache - f = Gio.File.new_for_path(cache_path_png) - if not behaviour & ArtBehaviour.NO_CACHE and f.query_exists(): - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(cache_path_png, - width, - height) - else: - filepath = self.__get_radio_art_path(name) - f = Gio.File.new_for_path(filepath) - if f.query_exists(): - pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath) - pixbuf = self.load_behaviour(pixbuf, cache_path_png, - width, height, behaviour) - except Exception as e: - Logger.error("RadioArt::get_radio_artwork(): %s" % e) - return pixbuf - - def cache_radio_uri(self, uri, name): - """ - Copy uri to cache at size - @param uri as str - @param name as str - """ - helper = TaskHelper() - helper.load_uri_content(uri, - None, - self.__on_uri_content, - name) - - def rename_radio(self, old_name, new_name): - """ - Rename artwork - @param old_name as str - @param new_name as str - """ - old = self.__get_radio_art_path(old_name) - new = self.__get_radio_art_path(new_name) - try: - src = Gio.File.new_for_path(old) - dst = Gio.File.new_for_path(new) - if src.query_exists(): - src.move(dst, Gio.FileCopyFlags.OVERWRITE, None, None) - except Exception as e: - Logger.error("RadioArt::rename_radio(): %s" % e) - - def add_radio_artwork(self, name, data): - """ - Add radio artwork to store - @param name as str - @param data as bytes - @thread safe - """ - self.uncache_radio_artwork(name) - filepath = "%s/%s.png" % (self._RADIOS_PATH, escape(name)) - if data is None: - f = Gio.File.new_for_path(filepath) - fstream = f.replace(None, False, - Gio.FileCreateFlags.REPLACE_DESTINATION, None) - fstream.close() - else: - bytes = GLib.Bytes.new(data) - stream = Gio.MemoryInputStream.new_from_bytes(bytes) - pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, None) - stream.close() - pixbuf.savev(filepath, "png", [None], [None]) - emit_signal(self, "radio-artwork-changed", name) - - def uncache_radio_artwork(self, name): - """ - Remove radio artwork from cache - @param name as string - """ - cache_name = self.__get_radio_cache_name(name) - try: - f = Gio.File.new_for_path(CACHE_PATH) - infos = f.enumerate_children( - "standard::name", - Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, - None) - for info in infos: - f = infos.get_child(info) - basename = f.get_basename() - if re.search(r"%s_.*\.png" % re.escape(cache_name), basename): - f.delete() - infos.close(None) - except Exception as e: - Logger.error("RadioArt::clean_radio_cache(): %s, %s" % - (e, cache_name)) - -####################### -# PRIVATE # -####################### - def __get_radio_art_path(self, name): - """ - Get radio artwork path - @param name as str - @return filepath as str - """ - return "%s/%s.png" % (self._RADIOS_PATH, escape(name)) - - def __get_radio_cache_name(self, name): - """ - Get a uniq string for radio - @param album_id as int - @param sql as sqlite cursor - """ - return "@@" + escape(name) + "@@radio@@" - - def __on_uri_content(self, uri, status, content, name): - """ - Save image - @param uri as str - @param status as bool - @param content as bytes # The image - @param name as str - """ - if status: - cache_path_png = self.__get_radio_art_path(name) - bytes = GLib.Bytes.new(content) - stream = Gio.MemoryInputStream.new_from_bytes(bytes) - pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, None) - stream.close() - pixbuf.savev(cache_path_png, "png", [None], [None]) - del pixbuf - emit_signal(self, "radio-artwork-changed", name) diff --git a/lollypop/container_lists.py b/lollypop/container_lists.py index d8576a9f7fde4487e71e6cb60f59900169180bb7..765427a911bddb3ac5be09be44941acc2311e130 100644 --- a/lollypop/container_lists.py +++ b/lollypop/container_lists.py @@ -10,7 +10,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from gi.repository import Gtk, GLib +from gi.repository import Gtk, GLib, Pango from lollypop.logger import Logger from lollypop.selectionlist import SelectionList @@ -18,7 +18,7 @@ from lollypop.define import App, Type, SelectionListMask, StorageType, ViewType from lollypop.shown import ShownLists from lollypop.helper_gestures import GesturesHelper from lollypop.view import View -from lollypop.utils import emit_signal, get_default_storage_type +from lollypop.utils import emit_signal, get_default_storage_type, get_icon_name class NoneView(View): @@ -237,7 +237,34 @@ class ListsContainer: elif selected_id == Type.WEB: view = self._get_view_albums([selected_id], [], StorageType.SAVED) elif selected_id == Type.RADIOS: - view = self._get_view_radios() + message = """Radio support has been removed, sorry for that. + For a better radio player, use Shortwave: + """ + view = View(StorageType.ALL, ViewType.DEFAULT) + image = Gtk.Image.new_from_icon_name(get_icon_name(Type.RADIOS), + Gtk.IconSize.INVALID) + image.set_pixel_size(256) + image.set_property("expand", True) + style = image.get_style_context() + style.add_class("dim-label") + label = Gtk.Label() + style = label.get_style_context() + style.add_class("dim-label") + style.add_class("text-xx-large") + label.set_markup("%s" % GLib.markup_escape_text(message)) + label.set_line_wrap_mode(Pango.WrapMode.WORD) + label.set_line_wrap(True) + button = Gtk.LinkButton.new( + "https://flathub.org/apps/details/de.haeckerfelix.Shortwave") + grid = Gtk.Grid() + grid.set_valign(Gtk.Align.CENTER) + grid.set_row_spacing(20) + grid.set_orientation(Gtk.Orientation.VERTICAL) + grid.add(image) + grid.add(label) + grid.add(button) + view.add_widget(grid) + view.show_all() elif selected_id == Type.YEARS: view = self._get_view_albums_decades(storage_type) elif selected_id == Type.GENRES: diff --git a/lollypop/container_views.py b/lollypop/container_views.py index 0f543f46b0858775954fed5c318db825c69bf5dc..9115f86826f507e022af3c2b36e14ae8a292e9b1 100644 --- a/lollypop/container_views.py +++ b/lollypop/container_views.py @@ -116,8 +116,6 @@ class ViewsContainer: view = self._get_view_albums_years(data, storage_type) elif item_ids[0] == Type.PLAYLISTS: view = self._get_view_playlists(data) - elif item_ids[0] == Type.RADIOS: - view = self._get_view_radios() elif item_ids[0] == Type.EQUALIZER: from lollypop.view_equalizer import EqualizerView view = EqualizerView() @@ -362,17 +360,6 @@ class ViewsContainer: view.populate() return view - def _get_view_radios(self): - """ - Get radios view - @return RadiosView - """ - view_type = ViewType.SCROLLED - from lollypop.view_radios import RadiosView - view = RadiosView(view_type) - view.populate() - return view - def _get_view_info(self): """ Get view for information diff --git a/lollypop/controller_view.py b/lollypop/controller_view.py deleted file mode 100644 index 9b211dcab2d4c62a006b600ed114b520099aecbb..0000000000000000000000000000000000000000 --- a/lollypop/controller_view.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from lollypop.define import App -from lollypop.helper_signals import SignalsHelper, signals_map - - -class ViewControllerType: - RADIO = "radio" - ALBUM = "album" - - -class ViewController(SignalsHelper): - """ - Update view for registered signals - Should be herited by a Gtk.Widget - """ - - @signals_map - def __init__(self, controller_type): - """ - Init controller - @param controller_type as ViewControllerType - """ - return [ - (App().player, "current-changed", "_on_current_changed"), - (App().player, "duration-changed", "_on_duration_changed"), - (App().art, "%s-artwork-changed" % controller_type, - "_on_artwork_changed") - ] - -####################### -# PROTECTED # -####################### - def _on_current_changed(self, player): - pass - - def _on_artwork_changed(self, artwork, *args): - pass - - def _on_duration_changed(self, player, track_id): - pass - -####################### -# PRIVATE # -####################### diff --git a/lollypop/database_upgrade.py b/lollypop/database_upgrade.py index a441db5892a46f675507188f0eea36b7aac508db..17c25249b3a6a44e051c58cbcb386325b5bd8984 100644 --- a/lollypop/database_upgrade.py +++ b/lollypop/database_upgrade.py @@ -19,7 +19,6 @@ from gettext import gettext as _ from lollypop.sqlcursor import SqlCursor from lollypop.utils import translate_artist_name from lollypop.database_history import History -from lollypop.radios import Radios from lollypop.define import App, Type, StorageType, LOLLYPOP_DATA_PATH from lollypop.logger import Logger from lollypop.helper_task import TaskHelper @@ -140,7 +139,6 @@ class DatabaseAlbumsUpgrade(DatabaseUpgrade): 18: self.__upgrade_18, 19: self.__upgrade_19, 20: self.__upgrade_20, - 21: self.__upgrade_21, 22: self.__upgrade_22, 23: self.__upgrade_23, 24: "ALTER TABLE albums ADD album_id TEXT", @@ -482,14 +480,6 @@ class DatabaseAlbumsUpgrade(DatabaseUpgrade): persistent FROM backup") sql.execute("DROP TABLE backup") - def __upgrade_21(self, db): - """ - Add rate to radios - """ - with SqlCursor(Radios()) as sql: - sql.execute("ALTER TABLE radios ADD rate\ - INT NOT NULL DEFAULT -1") - def __upgrade_22(self, db): """ Remove Charts/Web entries diff --git a/lollypop/fullscreen.py b/lollypop/fullscreen.py index 7c59efe9a0f1bd16e5c29eb159cbaebebd85eb74..5f738287b414ebe6b947bcfc7134e60f7f243613 100644 --- a/lollypop/fullscreen.py +++ b/lollypop/fullscreen.py @@ -20,7 +20,6 @@ from lollypop.widgets_player_progress import ProgressPlayerWidget from lollypop.widgets_player_buttons import ButtonsPlayerWidget from lollypop.widgets_player_artwork import ArtworkPlayerWidget from lollypop.widgets_player_label import LabelPlayerWidget -from lollypop.objects_radio import Radio from lollypop.container import Container from lollypop.window_adaptive import AdaptiveWindow from lollypop.logger import Logger @@ -285,10 +284,7 @@ class FullScreen(Gtk.Window, AdaptiveWindow, SignalsHelper): Update progress bar visibility """ if App().player.current_track.id is not None: - if isinstance(App().player.current_track, Radio): - self.__progress_widget.hide() - else: - self.__progress_widget.show() + self.__progress_widget.show() else: self.__progress_widget.hide() @@ -309,18 +305,7 @@ class FullScreen(Gtk.Window, AdaptiveWindow, SignalsHelper): ArtBehaviour.DARKER) # We don't want this for background, stored for album cover behaviour &= ~ArtBehaviour.ROUNDED - if isinstance(App().player.current_track, Radio): - if self.__background_id == App().player.current_track.name: - return - App().art_helper.set_radio_artwork( - App().player.current_track.name, - allocation.width, - allocation.height, - self.get_scale_factor(), - behaviour | ArtBehaviour.BLUR_MAX, - self.__on_artwork, - False) - elif not album_artwork and\ + if not album_artwork and\ App().settings.get_value("artist-artwork"): if App().player.current_track.album.artists: artist = App().player.current_track.album.artists[0] diff --git a/lollypop/helper_art.py b/lollypop/helper_art.py index 460a47c8ced816fcc4100477c3f134e2195a9ee8..80c961a31c8c83b9829bad4a27d6e0027d1467e0 100644 --- a/lollypop/helper_art.py +++ b/lollypop/helper_art.py @@ -73,31 +73,6 @@ class ArtHelper(GObject.Object): callback, *args)) - def set_radio_artwork(self, radio, width, height, scale_factor, - effect, callback, *args): - """ - Set artwork for album id - @param radio as str - @param width as int - @param height as int - @param scale_factor as int - @param effect as ArtBehaviour - @param callback as function - """ - App().task_helper.run(App().art.get_radio_artwork, - radio, - width, - height, - scale_factor, - effect, - callback=(self._on_get_artwork_pixbuf, - width, - height, - scale_factor, - effect, - callback, - *args)) - def set_artist_artwork(self, name, width, height, scale_factor, effect, callback, *args): """ diff --git a/lollypop/helper_lyrics.py b/lollypop/helper_lyrics.py index 5e5c2ebbac1e982a84d561e5f7f7b09355a091c0..cd4cb6a2465a16a37e6a4a50874c08c2e54c0454 100644 --- a/lollypop/helper_lyrics.py +++ b/lollypop/helper_lyrics.py @@ -173,13 +173,7 @@ class LyricsHelper: @return str """ # Update lyrics - title = "" - if isinstance(track, Radio): - split = " ".join(track.artists).split(" - ") - if len(split) > 1: - title = split[1] - else: - title = track.name + title = track.name if escape: return GLib.uri_escape_string(title, None, False) else: diff --git a/lollypop/menu_radio.py b/lollypop/menu_radio.py deleted file mode 100644 index 69d14b6fa3a47fa08e10f760f1757629bf9d68a9..0000000000000000000000000000000000000000 --- a/lollypop/menu_radio.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import Gtk, Gio, GObject - -from gettext import gettext as _ - -from lollypop.widgets_rating import RatingWidget -from lollypop.define import App, ArtSize, ArtBehaviour, MARGIN, MARGIN_SMALL -from lollypop.define import ViewType -from lollypop.widgets_artwork_radio import RadioArtworkSearchWidget -from lollypop.art import Art -from lollypop.utils import emit_signal -from lollypop.objects_radio import Radio - - -class RadioMenu(Gtk.Grid): - """ - Popover with radio logos from the web - """ - - __gsignals__ = { - "hidden": (GObject.SignalFlags.RUN_FIRST, None, (bool,)), - } - - def __init__(self, radio, view_type): - """ - Init Popover - @param radio as Radio - @param view_type as ViewType - @param header as bool - """ - Gtk.Grid.__init__(self) - self.set_orientation(Gtk.Orientation.VERTICAL) - self.__view_type = view_type - self.__uri_artwork_id = None - self.__radio = radio if radio is not None else Radio(None) - - self.set_row_spacing(MARGIN) - self.set_margin_start(MARGIN_SMALL) - self.set_margin_end(MARGIN_SMALL) - self.set_margin_top(MARGIN) - self.set_margin_bottom(MARGIN) - - self.__stack = Gtk.Stack() - self.__stack.set_transition_duration(1000) - self.__stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) - self.__stack.show() - - builder = Gtk.Builder() - builder.add_from_resource("/org/gnome/Lollypop/RadioMenu.ui") - builder.connect_signals(self) - - self.__name_entry = builder.get_object("name") - self.__uri_entry = builder.get_object("uri") - self.__artwork_button = builder.get_object("artwork_button") - self.__save_button = builder.get_object("save_button") - self.__stack.add_named(builder.get_object("widget"), "widget") - self.__stack.set_visible_child_name("widget") - - if view_type & ViewType.ADAPTIVE: - button = Gtk.ModelButton.new() - button.set_alignment(0, 0.5) - button.connect("clicked", - lambda x: emit_signal(self, "hidden", True)) - button.show() - label = Gtk.Label.new() - label.show() - if self.__radio.name: - self.__artwork = Gtk.Image.new() - name = "%s" % radio.name - App().art_helper.set_radio_artwork( - self.__radio.name, - ArtSize.SMALL, - ArtSize.SMALL, - self.__artwork.get_scale_factor(), - ArtBehaviour.CACHE | - ArtBehaviour.CROP, - self.__on_radio_artwork) - else: - self.__artwork = Gtk.Image.new_from_icon_name( - "org.gnome.Lollypop-gradio-symbolic", - Gtk.IconSize.INVALID) - self.__artwork.set_pixel_size(ArtSize.SMALL) - name = "%s" % _("New radio") - self.__artwork.show() - label.set_markup(name) - grid = Gtk.Grid() - grid.set_column_spacing(MARGIN) - grid.add(self.__artwork) - grid.add(label) - button.set_image(grid) - button.get_style_context().add_class("padding") - self.add(button) - self.add(self.__stack) - if radio is not None: - if view_type & ViewType.ADAPTIVE: - rating = RatingWidget(radio, Gtk.IconSize.DND) - else: - rating = RatingWidget(radio) - rating.show() - builder.get_object("widget").attach(rating, 0, 2, 2, 1) - builder.get_object("delete_button").show() - self.__name_entry.set_text(radio.name) - if radio.uri: - self.__uri_entry.set_text(radio.uri) - -####################### -# PROTECTED # -####################### - def _on_save_button_clicked(self, widget): - """ - Save radio - @param widget as Gtk.Widget - """ - self.__save_radio() - emit_signal(self, "hidden", True) - - def _on_delete_button_clicked(self, widget): - """ - Delete a radio - @param widget as Gtk.Widget - """ - if self.__radio.id is not None: - store = Art._RADIOS_PATH - name = self.__radio.name - App().radios.remove(self.__radio.id) - App().art.uncache_radio_artwork(name) - f = Gio.File.new_for_path(store + "/%s.png" % name) - if f.query_exists(): - f.delete() - emit_signal(self, "hidden", True) - - def _on_entry_changed(self, entry): - """ - Update modify/add button - @param entry as Gtk.Entry - """ - uri = self.__uri_entry.get_text() - name = self.__name_entry.get_text() - if name != "" and uri.find("://") != -1: - self.__artwork_button.set_sensitive(True) - self.__save_button.set_sensitive(True) - else: - self.__artwork_button.set_sensitive(False) - self.__save_button.set_sensitive(False) - - def _on_artwork_button_clicked(self, widget): - """ - Update radio image - @param widget as Gtk.Widget - """ - self.__stack.get_visible_child().hide() - self.__save_radio() - name = App().radios.get_name(self.__radio.id) - artwork_widget = RadioArtworkSearchWidget(name, self.__view_type) - artwork_widget.populate() - artwork_widget.show() - artwork_widget.connect("hidden", - lambda x, y: emit_signal(self, "hidden", True)) - self.__stack.add_named(artwork_widget, "artwork") - self.__stack.set_visible_child_name("artwork") - -####################### -# PRIVATE # -####################### - def __save_radio(self): - """ - Save radio based on current widget content - """ - new_name = self.__name_entry.get_text() - new_uri = self.__uri_entry.get_text() - if new_name != "" and new_uri != "": - if self.__radio.id is None: - radio_id = App().radios.add(new_name, - new_uri.lstrip().rstrip()) - self.__radio = Radio(radio_id) - else: - name = App().radios.get_name(self.__radio.id) - App().radios.rename(self.__radio.id, new_name) - App().radios.set_uri(self.__radio.id, new_uri) - App().art.rename_radio(name, new_name) - self.__radio.set_uri(new_uri) - self.__radio.set_name(new_name) - - def __on_radio_artwork(self, surface): - """ - Set radio artwork - @param surface as str - """ - if surface is None: - self.__artwork.set_from_icon_name( - "audio-input-microphone-symbolic", - Gtk.IconSize.BUTTON) - else: - self.__artwork.set_from_surface(surface) - del surface diff --git a/lollypop/miniplayer.py b/lollypop/miniplayer.py index dbbb378bbab6ac95c3a52283ac58b712ae066bc1..f974d648e724a8846863d82e657b7d1610b5306f 100644 --- a/lollypop/miniplayer.py +++ b/lollypop/miniplayer.py @@ -19,7 +19,6 @@ from lollypop.widgets_player_buttons import ButtonsPlayerWidget from lollypop.widgets_player_artwork import ArtworkPlayerWidget from lollypop.widgets_player_label import LabelPlayerWidget from lollypop.helper_size_allocation import SizeAllocationHelper -from lollypop.objects_radio import Radio from lollypop.helper_signals import SignalsHelper, signals from lollypop.utils import emit_signal @@ -233,10 +232,7 @@ class MiniPlayer(Gtk.Overlay, SizeAllocationHelper, SignalsHelper): Update progress bar visibility """ if App().player.current_track.id is not None: - if isinstance(App().player.current_track, Radio): - self.__progress_widget.hide() - else: - self.__progress_widget.show() + self.__progress_widget.show() else: self.__progress_widget.hide() diff --git a/lollypop/mpris.py b/lollypop/mpris.py index 09201f5549991e868c050758912d4a7b37aa1ae8..5d06e28808fdb03e7b1c8aa07dd21f31afccaa62 100644 --- a/lollypop/mpris.py +++ b/lollypop/mpris.py @@ -21,7 +21,6 @@ from random import randint from lollypop.logger import Logger from lollypop.define import App, ArtSize, Repeat, Notifications from lollypop.objects_track import Track -from lollypop.objects_radio import Radio class Server: @@ -416,12 +415,7 @@ class MPRIS(Server): self.__metadata["xesam:userRating"] = GLib.Variant( "d", self.__rating / 5) - if isinstance(App().player.current_track, Radio): - cover_path = App().art.get_radio_cache_path( - App().player.current_track.name, - ArtSize.MPRIS, ArtSize.MPRIS) - else: - cover_path = App().art.get_album_cache_path( + cover_path = App().art.get_album_cache_path( App().player.current_track.album, ArtSize.MPRIS, ArtSize.MPRIS) if cover_path is not None: diff --git a/lollypop/notification.py b/lollypop/notification.py index 3aab8248fb7bd8ca6d4fe0a859f2dfa91243a098..dc3388e173c7cc1a4225ad20168326fb05aae946 100644 --- a/lollypop/notification.py +++ b/lollypop/notification.py @@ -15,7 +15,6 @@ from gettext import gettext as _ from lollypop.define import App, ArtSize, Notifications from lollypop.utils import is_gnome -from lollypop.objects_radio import Radio class NotificationManager: @@ -54,14 +53,9 @@ class NotificationManager: App().is_fullscreen: return - if isinstance(track, Radio): - cover_path = App().art.get_radio_cache_path(track.name, - ArtSize.BIG, - ArtSize.BIG) - else: - cover_path = App().art.get_album_cache_path(track.album, - ArtSize.BIG, - ArtSize.BIG) + cover_path = App().art.get_album_cache_path(track.album, + ArtSize.BIG, + ArtSize.BIG) if cover_path is None: icon = Gio.Icon.new_for_string("org.gnome.Lollypop-symbolic") else: diff --git a/lollypop/objects.py b/lollypop/objects.py index 76e0f0a6745e3dc85c12f7a54e47caf504468c45..e8e9d58727884f0962a6351fca096e98c49f1720 100644 --- a/lollypop/objects.py +++ b/lollypop/objects.py @@ -11,9 +11,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from lollypop.radios import Radios from lollypop.logger import Logger -from lollypop.define import App, Type +from lollypop.define import App from lollypop.utils import emit_signal @@ -67,15 +66,9 @@ class Base: return 0 popularity = 0 - if self.id >= 0: - avg_popularity = self.db.get_avg_popularity() - if avg_popularity > 0: - popularity = self.db.get_popularity(self.id) - elif self.id == Type.RADIOS: - radios = Radios() - avg_popularity = radios.get_avg_popularity() - if avg_popularity > 0: - popularity = radios.get_popularity(self._radio_id) + avg_popularity = self.db.get_avg_popularity() + if avg_popularity > 0: + popularity = self.db.get_popularity(self.id) return popularity * 5 / avg_popularity + 0.5 def set_popularity(self, new_rate): @@ -86,49 +79,19 @@ class Base: if self.id is None: return try: - if self.id >= 0: - avg_popularity = self.db.get_avg_popularity() - popularity = int((new_rate * avg_popularity / 5) + 0.5) - best_popularity = self.db.get_higher_popularity() - if new_rate == 5: - popularity = (popularity + best_popularity) / 2 - self.db.set_popularity(self.id, popularity) - elif self.id == Type.RADIOS: - radios = Radios() - avg_popularity = radios.get_avg_popularity() - popularity = int((new_rate * avg_popularity / 5) + 0.5) - best_popularity = self.db.get_higher_popularity() - if new_rate == 5: - popularity = (popularity + best_popularity) / 2 - radios.set_popularity(self._radio_id, popularity) + avg_popularity = self.db.get_avg_popularity() + popularity = int((new_rate * avg_popularity / 5) + 0.5) + best_popularity = self.db.get_higher_popularity() + if new_rate == 5: + popularity = (popularity + best_popularity) / 2 + self.db.set_popularity(self.id, popularity) except Exception as e: Logger.error("Base::set_popularity(): %s" % e) - def get_rate(self): - """ - Get rate - @return int - """ - if self.id is None: - return 0 - - rate = 0 - if self.id >= 0: - rate = self.db.get_rate(self.id) - elif self.id == Type.RADIOS: - radios = Radios() - rate = radios.get_rate(self._radio_id) - return rate - def set_rate(self, rate): """ Set rate @param rate as int between -1 and 5 """ - if self.id == Type.RADIOS: - radios = Radios() - radios.set_rate(self._radio_id, rate) - emit_signal(App().player, "rate-changed", self._radio_id, rate) - else: - self.db.set_rate(self.id, rate) - emit_signal(App().player, "rate-changed", self.id, rate) + self.db.set_rate(self.id, rate) + emit_signal(App().player, "rate-changed", self.id, rate) diff --git a/lollypop/objects_radio.py b/lollypop/objects_radio.py deleted file mode 100644 index b50b52f04a2cf2a346ee433caff8c0eb430e1c76..0000000000000000000000000000000000000000 --- a/lollypop/objects_radio.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# Copyright (c) 2015 Jean-Philippe Braun -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gettext import gettext as _ -from hashlib import md5 - -from lollypop.define import App, Type, StorageType -from lollypop.objects import Base - - -class RadioAlbum: - """ - Fake album - """ - def __init__(self, radio_id): - """ - Init album - @param radio_id as int - """ - self.id = radio_id - self.name = App().radios.get_name(radio_id) - self.storage_type = StorageType.COLLECTION - self.artists = [_("Radio")] - self.artist_ids = [Type.RADIOS] - self.year = None - self.timestamp = self.duration = self.popularity = self.mtime = 0 - self.uri = "" - self.track_count = 1 - self.mb_album_id = None - self.loved = False - self.synced = False - self.tracks = [] - self.track_ids = [] - self.lp_album_id = md5(self.name.encode("utf-8")).hexdigest() - - -class Radio(Base): - """ - Represent a radio - """ - DEFAULTS = {"name": "", - "popularity": 0, - "uri": ""} - - def __init__(self, radio_id): - """ - Init track - @param radio_id as int - """ - Base.__init__(self, App().radios) - self.id = self.album_id = radio_id - self.storage_type = StorageType.COLLECTION - self.album = RadioAlbum(radio_id) - self.artists = self.genres = self.album_artists = [_("Radio")] - self.path = self.discname = "" - self.artist_ids = self.genre_ids = [Type.RADIOS] - self.album_name = self.album.name - self.year = None - self.duration = self.number = self.discnumber =\ - self.timestamp = self.mtime = self.mb_track_id = self.position = 0 - self.loved = self.last = False - self.mb_artist_ids = [] - self.is_web = self.is_http = True - self.lp_track_id = md5(self.album_name.encode("utf-8")).hexdigest() - - def set_name(self, name): - """ - Set uri - @param uri as str - """ - self.name = name - - def set_uri(self, uri): - """ - Set uri - @param uri as str - """ - self.uri = uri - - @property - def title(self): - return self.name diff --git a/lollypop/player.py b/lollypop/player.py index f64c84aaa7d66a6d287f48765d052a74a1a898c7..555f61b7212bce7ee4f0af8e6777b0849804ce84 100644 --- a/lollypop/player.py +++ b/lollypop/player.py @@ -22,18 +22,15 @@ from lollypop.player_bin import BinPlayer from lollypop.player_queue import QueuePlayer from lollypop.player_linear import LinearPlayer from lollypop.player_shuffle import ShufflePlayer -from lollypop.player_radio import RadioPlayer from lollypop.player_transitions import TransitionsPlayer -from lollypop.radios import Radios from lollypop.logger import Logger from lollypop.objects_track import Track -from lollypop.objects_radio import Radio from lollypop.define import App, Type, LOLLYPOP_DATA_PATH from lollypop.utils import emit_signal class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, - AutoSimilarPlayer, QueuePlayer, RadioPlayer, LinearPlayer, + AutoSimilarPlayer, QueuePlayer, LinearPlayer, ShufflePlayer, TransitionsPlayer): """ Player object used to manage playback and playlists @@ -73,7 +70,6 @@ class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, QueuePlayer.__init__(self) LinearPlayer.__init__(self) ShufflePlayer.__init__(self) - RadioPlayer.__init__(self) TransitionsPlayer.__init__(self) self.__stop_after_track_id = None App().settings.connect("changed::repeat", self.update_next_prev) @@ -83,10 +79,7 @@ class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, Stop current track, load track id and play it @param track as Track """ - if isinstance(track, Radio): - self.clear_albums() - RadioPlayer.load(self, track) - elif TransitionsPlayer.load(self, track): + if TransitionsPlayer.load(self, track): pass else: BinPlayer.load(self, track) @@ -145,18 +138,9 @@ class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, self.set_queue(load(open(LOLLYPOP_DATA_PATH + "/queue.bin", "rb"))) albums = load(open(LOLLYPOP_DATA_PATH + "/Albums.bin", "rb")) - playlist_ids = load(open(LOLLYPOP_DATA_PATH + - "/playlist_ids.bin", "rb")) (is_playing, was_party) = load(open(LOLLYPOP_DATA_PATH + "/player.bin", "rb")) - if playlist_ids and playlist_ids[0] == Type.RADIOS: - radios = Radios() - track = Track() - name = radios.get_name(self._current_playback_track.id) - uri = radios.get_uri(self._current_playback_track.id) - track.set_radio(name, uri) - self.load(track) - elif self._current_playback_track.uri: + if self._current_playback_track.uri: if albums: if was_party: App().lookup_action("party").change_state( @@ -197,8 +181,6 @@ class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, """ if self._current_playback_track.id is None: return - if isinstance(self.current_track, Radio): - return try: if App().settings.get_value("shuffle") or self.is_party: prev_track = ShufflePlayer.prev(self) @@ -215,8 +197,7 @@ class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, """ if self._current_playback_track.id is None: return - if isinstance(self.current_track, Radio) or\ - self._current_track.id == self.__stop_after_track_id: + if self._current_track.id == self.__stop_after_track_id: self._next_track = Track() return try: @@ -271,7 +252,7 @@ class Player(GObject.GObject, AlbumsPlayer, BinPlayer, AutoRandomPlayer, Scrobble track, update last played time and increment popularity @param track as Track """ - if track.id is None or isinstance(track, Radio): + if track.id is None: return # Track has been played # for at least half its duration, or for 4 minutes diff --git a/lollypop/player_bin.py b/lollypop/player_bin.py index 4efbfc7b1e6de9f9d01914296c6da50454f6383c..6d634a0e919aa02da480ce2e8dc95f6ee5407288 100644 --- a/lollypop/player_bin.py +++ b/lollypop/player_bin.py @@ -21,7 +21,6 @@ from lollypop.define import GstPlayFlags, App from lollypop.codecs import Codecs from lollypop.logger import Logger from lollypop.objects_track import Track -from lollypop.objects_radio import Radio from lollypop.utils import emit_signal, get_network_available @@ -88,12 +87,8 @@ class BinPlayer: """ Change player state to PAUSED """ - if isinstance(App().player.current_track, Radio): - self._playbin.set_state(Gst.State.NULL) - emit_signal(self, "status-changed") - else: - self._playbin.set_state(Gst.State.PAUSED) - emit_signal(self, "status-changed") + self._playbin.set_state(Gst.State.PAUSED) + emit_signal(self, "status-changed") def stop(self): """ @@ -329,9 +324,7 @@ class BinPlayer: If we are current bus, try to restart playback Else stop playback """ - if isinstance(App().player.current_track, Radio): - self._load_track(App().player.current_track) - elif self._playbin.get_bus() == bus: + if self._playbin.get_bus() == bus: if self._next_track.id is None: self.stop() else: @@ -345,8 +338,6 @@ class BinPlayer: """ try: Logger.debug("Player::__on_stream_about_to_finish(): %s" % playbin) - if isinstance(App().player.current_track, Radio): - return # Don't do anything if crossfade on, track already scrobbled # See TransitionsPlayer if not self.crossfading: diff --git a/lollypop/player_radio.py b/lollypop/player_radio.py deleted file mode 100644 index db9419035b616a31c440af4900d526d944adb773..0000000000000000000000000000000000000000 --- a/lollypop/player_radio.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import TotemPlParser, Gst, Gio, GLib - -from lollypop.define import App -from lollypop.logger import Logger -from lollypop.utils import emit_signal - - -class RadioPlayer: - """ - Radio player - """ - - def __init__(self): - """ - Init radio player - """ - pass - - def load(self, track): - """ - Load radio at uri - @param track as Track - """ - if Gio.NetworkMonitor.get_default().get_network_available(): - try: - # If a web track is loading, indicate lollypop to stop loading - # indicator - if self._current_track.is_web: - emit_signal(self, "loading-changed", False, - self._current_track.album) - emit_signal(self, "loading-changed", True, track.album) - self._current_track = track - if track.uri.find("youtu.be") != -1 or\ - track.uri.find("youtube.com") != -1: - App().task_helper.run(self.__load_youtube_track, track) - else: - parser = TotemPlParser.Parser.new() - parser.connect("entry-parsed", self.__on_entry_parsed, - track) - parser.parse_async(track.uri, True, - None, self.__on_parse_finished, - track) - except Exception as e: - Logger.error("RadioPlayer::load(): %s" % e) - if self.is_party: - self.set_party(False) - -####################### -# PROTECTED # -####################### - -####################### -# PRIVATE # -####################### - def __load_youtube_track(self, track): - """ - Load YouTube track - @param track as Track - """ - from lollypop.helper_web_youtube import YouTubeHelper - helper = YouTubeHelper() - uri = helper.get_uri_content(track) - if uri is not None: - track.set_uri(uri) - GLib.idle_add(self.__start_playback, track, True) - else: - self.stop() - - def __start_playback(self, track): - """ - Start playing track - @param track as Track - """ - self._playbin.set_state(Gst.State.NULL) - self._playbin.set_property("uri", track.uri) - App().radios.set_more_popular(track.id) - self._current_track = track - self._playbin.set_state(Gst.State.PLAYING) - emit_signal(self, "status-changed") - - def __on_parse_finished(self, parser, result, track): - """ - Play stream - @param parser as TotemPlParser.Parser - @param result as Gio.AsyncResult - @param track as Track - """ - # Only start playing if context always True - if self._current_track == track: - self.__start_playback(track) - else: - emit_signal(self, "loading-changed", False, track.album) - - def __on_entry_parsed(self, parser, uri, metadata, track): - """ - Play stream - @param parser as TotemPlParser.Parser - @param track uri as str - @param metadata as GLib.HastTable - @param track as Track - """ - # Only start playing if context always True - if self._current_track == track: - track.set_uri(uri) - else: - emit_signal(self, "loading-changed", False, track.album) diff --git a/lollypop/player_shuffle.py b/lollypop/player_shuffle.py index 73649380093af63198ecd241feadf8b173b4b315..f2e40e865d3cde96602b54d106129bced62c5785 100644 --- a/lollypop/player_shuffle.py +++ b/lollypop/player_shuffle.py @@ -14,7 +14,6 @@ from random import shuffle, random from lollypop.define import Repeat, App from lollypop.objects_track import Track -from lollypop.objects_radio import Radio from lollypop.objects_album import Album from lollypop.list import LinkedList from lollypop.utils import emit_signal, get_default_storage_type @@ -85,8 +84,7 @@ class ShufflePlayer: def start_party(*ignore): if self._albums: # Start a new song if not playing - if self._current_playback_track.id is None or\ - isinstance(self._current_playback_track, Radio): + if self._current_playback_track.id is None: track = self.__get_tracks_random() self.load(track) elif not self.is_playing: diff --git a/lollypop/player_transitions.py b/lollypop/player_transitions.py index 1ec4195f619bfc6d6820b5be99b2a894ee3ced79..025081fd228aef918f24c2e7b3e1e45969a040a0 100644 --- a/lollypop/player_transitions.py +++ b/lollypop/player_transitions.py @@ -15,7 +15,6 @@ from gi.repository import Gst, GLib, GstAudio from time import sleep from lollypop.define import App -from lollypop.objects_radio import Radio class TransitionsPlayer: @@ -41,8 +40,7 @@ class TransitionsPlayer: """ if self.crossfading and\ self._current_track.id is not None and\ - self.is_playing and\ - not isinstance(track, Radio): + self.is_playing: transition_duration = App().settings.get_value( "transitions-duration").get_int32() self.__do_crossfade(transition_duration, track) diff --git a/lollypop/pop_tunein.py b/lollypop/pop_tunein.py deleted file mode 100644 index 41a18f189c241437c92b68a7d480fb6429d35943..0000000000000000000000000000000000000000 --- a/lollypop/pop_tunein.py +++ /dev/null @@ -1,389 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import Gtk, GLib, Gio, GdkPixbuf, Gdk, Pango - -from gettext import gettext as _ - -from lollypop.logger import Logger -from lollypop.define import App, ArtSize, Type -from lollypop.utils import get_network_available -from lollypop.list import LinkedList -from lollypop.objects_radio import Radio - - -class TuneItem: - TEXT = "" - URL = "" - LOGO = "" - - -class TuneinPopover(Gtk.Popover): - """ - Popover showing tunin radios - """ - - def __init__(self): - """ - Init Popover - """ - Gtk.Popover.__init__(self) - self.__cancellable = Gio.Cancellable() - self.__timeout_id = None - self.__history = None - self.__covers_to_download = [] - - self.__stack = Gtk.Stack() - self.__stack.set_property("expand", True) - self.__stack.show() - - builder = Gtk.Builder() - builder.add_from_resource("/org/gnome/Lollypop/TuneinPopover.ui") - builder.connect_signals(self) - widget = builder.get_object("widget") - widget.attach(self.__stack, 0, 2, 5, 1) - - self.__back_btn = builder.get_object("back_btn") - self.__home_btn = builder.get_object("home_btn") - self.__label = builder.get_object("label") - - self.__view = Gtk.FlowBox() - self.__view.set_selection_mode(Gtk.SelectionMode.NONE) - self.__view.set_max_children_per_line(100) - self.__view.set_property("row-spacing", 10) - self.__view.set_property("expand", True) - self.__view.show() - - self.__spinner = builder.get_object("spinner") - - builder.get_object("viewport").add(self.__view) - builder.get_object("viewport").set_property("margin", 10) - - self.__scrolled = builder.get_object("scrolled") - self.__stack.add_named(self.__spinner, "spinner") - self.__stack.add_named(self.__label, "label") - self.__stack.add_named(self.__scrolled, "scrolled") - self.add(widget) - self.connect("map", self.__on_map) - self.connect("unmap", self.__on_unmap) - - def populate(self, uri="http://opml.radiotime.com/Browse.ashx?c="): - """ - Populate view for uri - @param uri as str - """ - if self.__view.get_children(): - return - self.__populate(uri) - -####################### -# PROTECTED # -####################### - def _on_back_btn_clicked(self, btn): - """ - Go to previous URL - @param btn as Gtk.Button - """ - if self.__history.prev is None: - return - self.__history = self.__history.prev - self.__stack.set_visible_child_name("spinner") - self.__spinner.start() - self.__clear() - self.__populate(self.__history.value) - if self.__history.prev is None: - self.__back_btn.set_sensitive(False) - - def _on_home_btn_clicked(self, btn): - """ - Go to root URL - @param btn as Gtk.Button - """ - self.__history = None - self.__populate() - - def _on_search_changed(self, widget): - """ - Timeout filtering, call _really_do_filterting() - after timeout - @param widget as Gtk.TextEntry - """ - self.__history = None - if self.__timeout_id is not None: - GLib.source_remove(self.__timeout_id) - self.__timeout_id = None - - text = widget.get_text() - if text != "": - self.__home_btn.set_sensitive(True) - self.__timeout_id = GLib.timeout_add(1000, - self.__on_search_timeout, - text) - else: - self.__history = None - self.__populate() - -####################### -# PRIVATE # -####################### - def __populate(self, uri="http://opml.radiotime.com/Browse.ashx?c="): - """ - Populate view for uri - @param uri as str - """ - self.__back_btn.set_sensitive(False) - self.__home_btn.set_sensitive(False) - if not get_network_available("TUNEIN"): - self.__show_message(_("Can't connect to TuneIn…")) - return - self.__clear() - self.__cancellable = Gio.Cancellable() - self.__spinner.start() - self.__stack.set_visible_child_name("spinner") - self.__label.set_text(_("Please wait…")) - App().task_helper.load_uri_content(uri, - self.__cancellable, - self.__on_uri_content) - - def __show_message(self, message=""): - """ - Show not found message - @param message as str - """ - # TODO Add a string - self.__label.set_text(message) - self.__stack.set_visible_child_name("label") - self.__home_btn.set_sensitive(True) - - def __add_items(self, items): - """ - Add current items - @param items as [TuneItem] - @thread safe - """ - GLib.idle_add(self.__add_item, items) - - def __add_item(self, items): - """ - Add item - @param items as [TuneItem] - """ - if items: - item = items.pop(0) - child = Gtk.Grid() - child.set_column_spacing(5) - child.set_property("halign", Gtk.Align.START) - child.show() - link = Gtk.LinkButton.new_with_label(item.URL, item.TEXT) - # Hack - link.get_children()[0].set_ellipsize(Pango.EllipsizeMode.END) - link.connect("activate-link", self.__on_activate_link, item) - link.show() - if item.TYPE == "audio": - link.set_tooltip_text(_("Play")) - button = Gtk.Button.new_from_icon_name("list-add-symbolic", - Gtk.IconSize.MENU) - button.connect("clicked", self.__on_button_clicked, item) - button.set_relief(Gtk.ReliefStyle.NONE) - button.set_property("valign", Gtk.Align.CENTER) - # Translators: radio context - button.set_tooltip_text(_("Add")) - button.show() - child.add(button) - image = Gtk.Image.new() - image.set_property("width-request", ArtSize.SMALL) - image.set_property("height-request", ArtSize.SMALL) - image.show() - child.add(image) - self.__covers_to_download.append((item, image)) - else: - link.set_tooltip_text("") - child.add(link) - self.__view.add(child) - if not self.__cancellable.is_cancelled(): - GLib.idle_add(self.__add_items, items) - else: # Download images - self.__home_btn.set_sensitive(self.__history is not None) - self.__download_images() - return - # Remove spinner if exist - if self.__stack.get_visible_child_name() == "spinner": - self.__stack.set_visible_child_name("scrolled") - self.__spinner.stop() - self.__label.set_text("") - self.__home_btn.set_sensitive(self.__history is not None) - - def __download_images(self): - """ - Download and set image for TuneItem - @thread safe - """ - if self.__covers_to_download and not self.__cancellable.is_cancelled(): - (item, image) = self.__covers_to_download.pop(0) - App().task_helper.load_uri_content(item.LOGO, self.__cancellable, - self.__on_image_downloaded, - image) - - def __clear(self): - """ - Clear view - """ - self.__cancellable.cancel() - for child in self.__view.get_children(): - self.__view.remove(child) - child.destroy() - - def __add_radio(self, item): - """ - Add selected radio - @param item as TuneIn Item - """ - App().task_helper.run(App().art.cache_radio_uri, - item.LOGO, item.TEXT) - # Tunein in embbed uri in ashx files, so get content if possible - App().task_helper.load_uri_content(item.URL, self.__cancellable, - self.__on_item_content, item.TEXT) - - def __on_map(self, widget): - """ - Resize and disable global shortcuts - @param widget as Gtk.Widget - """ - window_size = App().window.get_size() - height = window_size[1] - width = min(500, window_size[0]) - self.set_size_request(width, height * 0.7) - - def __on_unmap(self, widget): - """ - Enable global shortcuts - @param widget as Gtk.Widget - """ - self.__cancellable.cancel() - - def __on_item_content(self, uri, status, content, name): - """ - Add radio to manager - @param uri as str - @param status as bool - @param content as bytes - @param name as str - """ - if status and content: - uri = content.decode("utf-8").split("\n")[0] - App().radios.add(name.replace("/", "-"), uri) - - def __on_image_downloaded(self, uri, status, content, image): - """ - Set downloaded image - @param uri as str - @param status as bool - @param content as bytes - @param image as Gtk.Image - """ - if status: - bytes = GLib.Bytes.new(content) - stream = Gio.MemoryInputStream.new_from_bytes(bytes) - if stream is not None: - pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( - stream, - ArtSize.SMALL, - ArtSize.SMALL, - True, - None) - stream.close() - surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, - 0, - None) - del pixbuf - image.set_from_surface(surface) - del surface - self.__download_images() - - def __on_activate_link(self, link, item): - """ - Open new uri or just play stream - @param link as Gtk.LinkButton - @param item as TuneIn Item - """ - if item.TYPE == "link": - self.__scrolled.get_vadjustment().set_value(0.0) - self.__populate(item.URL) - elif item.TYPE == "audio": - App().task_helper.run(App().art.cache_radio_uri, - item.LOGO, item.TEXT) - track = Radio(Type.RADIOS) - track.name = item.TEXT - track.set_uri(item.URL) - App().player.load(track) - return True - - def __on_uri_content(self, uri, status, content): - """ - Extract content - @param uri as str - @param status as bool - @param content as bytes - """ - try: - if status: - if self.__history is not None: - self.__back_btn.set_sensitive(True) - self.__history = LinkedList(uri, None, self.__history) - if content: - import xml.etree.ElementTree as xml - items = [] - root = xml.fromstring(content) - for child in root.iter("outline"): - try: - item = TuneItem() - item.URL = child.attrib["URL"] - item.TEXT = child.attrib["text"].replace("/", "-") - try: - item.LOGO = child.attrib["image"] - except: - pass - item.TYPE = child.attrib["type"] - items.append(item) - except: - del item - if items: - self.__add_items(items) - else: - self.__show_message(_("No result…")) - else: - self.__show_message(_("No result…")) - else: - raise Exception(status) - except Exception as e: - Logger.error("TuneinPopover::__on_uri_content(): %s" % e) - self.__show_message(_("Can't connect to TuneIn…")) - - def __on_button_clicked(self, button, item): - """ - Play the radio - @param link as Gtk.Button - @param item as TuneIn Item - """ - self.__timeout_id = None - self.__add_radio(item) - self.hide() - - def __on_search_timeout(self, string): - """ - Populate widget - @param string as str - """ - self.__timeout_id = None - uri = "http://opml.radiotime.com/Search.ashx?query=%s" %\ - GLib.uri_escape_string(string, "/", False) - self.__populate(uri) diff --git a/lollypop/radios.py b/lollypop/radios.py deleted file mode 100644 index 21832d34ac1ad3e0f18633090c560c92b163e028..0000000000000000000000000000000000000000 --- a/lollypop/radios.py +++ /dev/null @@ -1,300 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import GObject, Gio, GLib - -import sqlite3 -import itertools -from threading import Lock - -from lollypop.sqlcursor import SqlCursor -from lollypop.logger import Logger -from lollypop.utils import emit_signal - - -class Radios(GObject.GObject): - """ - Playlists manager - """ - __LOCAL_PATH = GLib.get_user_data_dir() + "/lollypop" - DB_PATH = "%s/radios.db" % __LOCAL_PATH - - create_radios = """CREATE TABLE radios ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - url TEXT NOT NULL, - rate INT NOT NULL DEFAULT -1, - popularity INT NOT NULL)""" - __gsignals__ = { - # Add, rename, delete - "radio-changed": (GObject.SignalFlags.RUN_FIRST, None, (int,)), - } - - def __init__(self): - """ - Init playlists manager - """ - GObject.GObject.__init__(self) - self.thread_lock = Lock() - # Create db schema - try: - f = Gio.File.new_for_path(self.DB_PATH) - if not f.query_exists(): - with SqlCursor(self, True) as sql: - sql.execute(self.create_radios) - except Exception as e: - Logger.error("Radios::__init__(self): %s", e) - - def add(self, name, uri): - """ - Add a radio, update url if radio already exists in db - @param name as str - @param uri as str - @return radio id as int - """ - lastrowid = 0 - with SqlCursor(self, True) as sql: - result = sql.execute("INSERT INTO radios (name, url, popularity)\ - VALUES (?, ?, ?)", - (name, uri, 0)) - lastrowid = result.lastrowid - emit_signal(self, "radio-changed", lastrowid) - return lastrowid - - def exists(self, radio_id): - """ - Return True if radio exists - @param radio_id as int - @return bool - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT rowid\ - FROM radios\ - WHERE rowid=?", - (radio_id,)) - v = result.fetchone() - return v is not None - - def rename(self, radio_id, name): - """ - Rename playlist - @param radio_id as int - @param name as str - """ - with SqlCursor(self, True) as sql: - sql.execute("UPDATE radios\ - SET name=?\ - WHERE rowid=?", - (name, radio_id)) - emit_signal(self, "radio-changed", radio_id) - - def remove(self, radio_id): - """ - Remvoe radio with radio id - @param radio_id as int - """ - with SqlCursor(self, True) as sql: - sql.execute("DELETE FROM radios\ - WHERE rowid=?", - (radio_id,)) - emit_signal(self, "radio-changed", radio_id) - - def get(self): - """ - Return availables radios - @return array of (name, url) as[(str, str)] - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT name, url\ - FROM radios\ - ORDER BY rate DESC,\ - popularity DESC, name") - return list(result) - - def get_ids(self): - """ - Return availables radios - @return [int] - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT rowid\ - FROM radios\ - ORDER BY rate DESC,\ - popularity DESC, name") - return list(itertools.chain(*result)) - - def get_name(self, radio_id): - """ - Get radio name for id - @param radio_id as int - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT name\ - FROM radios\ - WHERE rowid=?", (radio_id,)) - v = result.fetchone() - if v is not None: - return v[0] - return "" - - def get_uri(self, radio_id): - """ - Get URI for id - @param radio_id as int - @return uri as str - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT url\ - FROM radios\ - WHERE rowid=?", (radio_id,)) - v = result.fetchone() - if v is not None: - return v[0] - return "" - - def set_more_popular(self, radio_id): - """ - Set radio more popular - @param radio_id as int - """ - with SqlCursor(self, True) as sql: - result = sql.execute("SELECT popularity from radios WHERE rowid=?", - (radio_id,)) - pop = result.fetchone() - if pop: - current = pop[0] - else: - current = 0 - current += 1 - sql.execute("UPDATE radios set popularity=? WHERE rowid=?", - (current, radio_id)) - - def get_higher_popularity(self): - """ - Get higher available popularity - @return int - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT popularity\ - FROM radios\ - ORDER BY POPULARITY DESC LIMIT 1") - v = result.fetchone() - if v is not None: - return v[0] - return 0 - - def get_avg_popularity(self): - """ - Return avarage popularity - @return avarage popularity as int - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT AVG(popularity)\ - FROM (SELECT popularity\ - FROM radios\ - ORDER BY POPULARITY DESC LIMIT 100)") - v = result.fetchone() - if v and v[0] is not None and v[0] > 5: - return v[0] - return 5 - - def set_popularity(self, radio_id, popularity): - """ - Set popularity - @param radio_id as int - @param popularity as int - """ - with SqlCursor(self, True) as sql: - sql.execute("UPDATE radios SET popularity=? WHERE rowid=?", - (popularity, radio_id)) - - def set_rate(self, radio_id, rate): - """ - Set rate - @param radio_id as int - @param rate as int - """ - with SqlCursor(self, True) as sql: - sql.execute("UPDATE radios SET rate=? WHERE rowid=?", - (rate, radio_id)) - - def set_uri(self, radio_id, uri): - """ - Set uri - @param radio_id as int - @param uri as str - """ - with SqlCursor(self, True) as sql: - sql.execute("UPDATE radios SET url=? WHERE rowid=?", - (uri, radio_id)) - - def get_id(self, name): - """ - Get radio id by name - @param name as str - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT id FROM radios WHERE name=?", (name,)) - v = result.fetchone() - if v is not None: - return v[0] - return 0 - - def get_popularity(self, radio_id): - """ - Get popularity - @param radio_id as int - @return popularity as int - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT popularity FROM radios WHERE rowid=?", - (radio_id,)) - v = result.fetchone() - if v is not None: - return v[0] - return 0 - - def get_rate(self, radio_id): - """ - Get radio rate - @param radio_id as int - @return rate as int - """ - with SqlCursor(self) as sql: - result = sql.execute("SELECT rate FROM radios WHERE rowid=?", - (radio_id,)) - v = result.fetchone() - if v: - return v[0] - return 0 - - def get_cursor(self): - """ - Return a new sqlite cursor - """ - try: - return sqlite3.connect(self.DB_PATH, 600.0) - except: - exit(-1) - -####################### -# PRIVATE # -####################### - def __on_entry_parsed(self, parser, uri, metadata, name): - """ - Import entry - @param parser as TotemPlParser.Parser - @param radio uri as str - @param metadata as GLib.HastTable - @param name as str - """ - self.add(name, uri) diff --git a/lollypop/toolbar_info.py b/lollypop/toolbar_info.py index a8ba85651c8dae33150c2449c74235d9519cbfa4..2dba3cb9099d4596c0e9241c26e3267d3cb5350a 100644 --- a/lollypop/toolbar_info.py +++ b/lollypop/toolbar_info.py @@ -14,7 +14,6 @@ from gi.repository import Gtk, GLib from lollypop.utils import set_cursor_type, popup_widget -from lollypop.objects_radio import Radio from lollypop.widgets_player_artwork import ArtworkPlayerWidget from lollypop.widgets_player_label import LabelPlayerWidget from lollypop.define import App, ArtBehaviour, StorageType, MARGIN_SMALL @@ -100,8 +99,6 @@ class ToolbarInfo(Gtk.Bin, ArtworkPlayerWidget, GesturesHelper): """ if App().window.is_adaptive or not self.__artwork.get_visible(): return - if isinstance(App().player.current_track, Radio): - return if App().player.current_track.id is not None: self.__popup_menu() @@ -114,11 +111,7 @@ class ToolbarInfo(Gtk.Bin, ArtworkPlayerWidget, GesturesHelper): """ if App().window.is_adaptive or not self.__artwork.get_visible(): return - if isinstance(App().player.current_track, Radio): - from lollypop.pop_tunein import TuneinPopover - popover = TuneinPopover() - popover.populate() - elif App().player.current_track.id is not None: + if App().player.current_track.id is not None: from lollypop.pop_information import InformationPopover popover = InformationPopover() popover.populate() diff --git a/lollypop/view.py b/lollypop/view.py index 6c2da32a63eda9b9687c82679ad59e469bb05676..93cd4c02c8e3e071976f34bf7efe9b23abd17c13 100644 --- a/lollypop/view.py +++ b/lollypop/view.py @@ -127,7 +127,7 @@ class View(Gtk.Grid, AdaptiveHelper, FilteringHelper, SignalsHelper): message = new_text\ if new_text is not None\ else self._empty_message - from lollypop.placeholder import Placeholder + from lollypop.widgets_placeholder import Placeholder self.__placeholder = Placeholder(message, self._empty_icon_name) self.__placeholder.show() self.__stack.add(self.__placeholder) diff --git a/lollypop/view_album.py b/lollypop/view_album.py index f5030b179705929334ddde1175d178cfff6731b6..7a6cd5bd9c89a77d9820e0c97fbc8acca3359238 100644 --- a/lollypop/view_album.py +++ b/lollypop/view_album.py @@ -15,12 +15,11 @@ from gi.repository import Gtk from lollypop.define import App, ViewType, MARGIN, ScanUpdate from lollypop.view_tracks_album import AlbumTracksView from lollypop.widgets_banner_album import AlbumBannerWidget -from lollypop.controller_view import ViewController, ViewControllerType from lollypop.view_lazyloading import LazyLoadingView from lollypop.helper_signals import SignalsHelper, signals_map -class AlbumView(LazyLoadingView, ViewController, SignalsHelper): +class AlbumView(LazyLoadingView, SignalsHelper): """ Show artist albums and tracks """ @@ -34,7 +33,6 @@ class AlbumView(LazyLoadingView, ViewController, SignalsHelper): @param view_type as ViewType """ LazyLoadingView.__init__(self, storage_type, view_type) - ViewController.__init__(self, ViewControllerType.ALBUM) self.__tracks_view = None self.__album = album self.__others_boxes = [] @@ -47,7 +45,9 @@ class AlbumView(LazyLoadingView, ViewController, SignalsHelper): self.add_widget(self.__grid, self.__banner) return [ (App().scanner, "scan-finished", "_on_scan_finished"), - (App().scanner, "updated", "_on_collection_updated") + (App().scanner, "updated", "_on_collection_updated"), + (App().player, "current-changed", "_on_current_changed"), + (App().player, "duration-changed", "_on_duration_changed"), ] def populate(self): diff --git a/lollypop/view_albums_box.py b/lollypop/view_albums_box.py index cdd76d04a23bed4a3b53947307ee1ed1caf24a87..09a843cf9e1d7b42b00d0d2c23317265188ea028 100644 --- a/lollypop/view_albums_box.py +++ b/lollypop/view_albums_box.py @@ -25,11 +25,10 @@ from lollypop.utils import get_title_for_genres_artists from lollypop.utils import remove_static from lollypop.utils_file import get_youtube_dl from lollypop.utils_album import get_album_ids_for -from lollypop.controller_view import ViewController, ViewControllerType from lollypop.helper_signals import SignalsHelper, signals_map -class AlbumsBoxView(FlowBoxView, ViewController, SignalsHelper): +class AlbumsBoxView(FlowBoxView, SignalsHelper): """ Show albums in a box """ @@ -44,7 +43,6 @@ class AlbumsBoxView(FlowBoxView, ViewController, SignalsHelper): @param view_type as ViewType """ FlowBoxView.__init__(self, storage_type, view_type) - ViewController.__init__(self, ViewControllerType.ALBUM) self._genre_ids = genre_ids self._artist_ids = artist_ids self._storage_type = storage_type @@ -64,7 +62,8 @@ class AlbumsBoxView(FlowBoxView, ViewController, SignalsHelper): self._empty_icon_name = get_icon_name(genre_ids[0]) return [ (App().scanner, "updated", "_on_collection_updated"), - (App().player, "loading-changed", "_on_loading_changed") + (App().player, "loading-changed", "_on_loading_changed"), + (App().art, "album-artwork-changed", "_on_artwork_changed") ] def populate(self, albums=[]): diff --git a/lollypop/view_albums_list.py b/lollypop/view_albums_list.py index e7b04f72be8489383f9d4041b42c9b6d75a9b766..1deac11db91e40d8db34333a5d5624686aec1e90 100644 --- a/lollypop/view_albums_list.py +++ b/lollypop/view_albums_list.py @@ -15,16 +15,17 @@ from gi.repository import Gtk from lollypop.utils import popup_widget, emit_signal from lollypop.view_lazyloading import LazyLoadingView from lollypop.define import App, ViewType, MARGIN, StorageType -from lollypop.controller_view import ViewController, ViewControllerType from lollypop.widgets_row_album import AlbumRow from lollypop.helper_gestures import GesturesHelper +from lollypop.helper_signals import SignalsHelper, signals_map -class AlbumsListView(LazyLoadingView, ViewController, GesturesHelper): +class AlbumsListView(LazyLoadingView, SignalsHelper, GesturesHelper): """ View showing albums """ + @signals_map def __init__(self, genre_ids, artist_ids, view_type): """ Init widget @@ -33,7 +34,6 @@ class AlbumsListView(LazyLoadingView, ViewController, GesturesHelper): @param view_type as ViewType """ LazyLoadingView.__init__(self, StorageType.ALL, view_type) - ViewController.__init__(self, ViewControllerType.ALBUM) self.__width = 0 self.__genre_ids = genre_ids self.__artist_ids = artist_ids @@ -54,6 +54,11 @@ class AlbumsListView(LazyLoadingView, ViewController, GesturesHelper): self.__dnd_helper = DNDHelper(self._box, view_type) self.__dnd_helper.connect("dnd-insert", self.__on_dnd_insert) self.add_widget(self._box) + return [ + (App().player, "current-changed", "_on_current_changed"), + (App().player, "duration-changed", "_on_duration_changed"), + (App().art, "album-artwork-changed", "_on_artwork_changed") + ] def add_reveal_albums(self, albums): """ diff --git a/lollypop/view_playlists.py b/lollypop/view_playlists.py index 03a0791b4f7e8322e22c3273cefa264fa7010b2c..0001762e5ca7e6d180210220a55182d8d4efcd78 100644 --- a/lollypop/view_playlists.py +++ b/lollypop/view_playlists.py @@ -16,15 +16,13 @@ from lollypop.view_lazyloading import LazyLoadingView from lollypop.define import App, ViewType, MARGIN, Type, Size, StorageType from lollypop.objects_album import Album from lollypop.objects_track import Track -from lollypop.controller_view import ViewController, ViewControllerType from lollypop.widgets_banner_playlist import PlaylistBannerWidget from lollypop.view_albums_list import AlbumsListView from lollypop.helper_signals import SignalsHelper, signals_map from lollypop.helper_size_allocation import SizeAllocationHelper -class PlaylistsView(LazyLoadingView, ViewController, - SignalsHelper, SizeAllocationHelper): +class PlaylistsView(LazyLoadingView, SignalsHelper, SizeAllocationHelper): """ View showing playlists """ @@ -40,7 +38,6 @@ class PlaylistsView(LazyLoadingView, ViewController, view_type | ViewType.SCROLLED | ViewType.OVERLAY) - ViewController.__init__(self, ViewControllerType.ALBUM) SizeAllocationHelper.__init__(self) self.__playlist_id = playlist_id # We remove SCROLLED because we want to be the scrolled view diff --git a/lollypop/view_radios.py b/lollypop/view_radios.py deleted file mode 100644 index 4eb0d4ca3aa6cf85809a973eb65c19046ea239e8..0000000000000000000000000000000000000000 --- a/lollypop/view_radios.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from lollypop.define import App, Type, ViewType, StorageType -from lollypop.view_flowbox import FlowBoxView -from lollypop.widgets_radio import RadioWidget -from lollypop.controller_view import ViewController, ViewControllerType -from lollypop.utils import get_icon_name -from lollypop.helper_signals import SignalsHelper, signals_map -from lollypop.widgets_banner_radios import RadiosBannerWidget - - -class RadiosView(FlowBoxView, ViewController, SignalsHelper): - """ - Show radios flow box - """ - - @signals_map - def __init__(self, view_type): - """ - Init view - @param view_type as ViewType - """ - FlowBoxView.__init__(self, StorageType.ALL, - view_type | ViewType.OVERLAY) - ViewController.__init__(self, ViewControllerType.RADIO) - self._empty_icon_name = get_icon_name(Type.RADIOS) - self.__banner = RadiosBannerWidget(self.view_type) - self.__banner.show() - self.add_widget(self._box, self.__banner) - return [ - (App().radios, "radio-changed", "_on_radio_changed"), - (App().player, "loading-changed", "_on_loading_changed") - ] - - def populate(self): - """ - Add radio widgets - @param radio_ids as [int] - """ - def on_load(items): - FlowBoxView.populate(self, items) - - def load(): - return App().radios.get_ids() - - App().task_helper.run(load, callback=(on_load,)) - - @property - def args(self): - """ - Get default args for __class__ - @return {} - """ - return {"view_type": self.view_type & ~(ViewType.ADAPTIVE | - ViewType.SMALL)} - -####################### -# PROTECTED # -####################### - def _get_child(self, value): - """ - Get a child for view - @param value as object - @return row as SelectionListRow - """ - if self.destroyed: - return None - widget = RadioWidget(value, self.view_type, self.font_height) - self._box.insert(widget, -1) - widget.show() - return widget - - def _get_menu_widget(self, child): - """ - Get menu widget - @param child as RadioWidget - @return Gtk.Widget - """ - from lollypop.menu_radio import RadioMenu - return RadioMenu(child.data, self.view_type) - - def _on_child_activated(self, flowbox, child): - """ - Navigate into child - @param flowbox as Gtk.FlowBox - @param child as Gtk.FlowBoxChild - """ - App().player.load(child.data) - child.set_loading(True) - - def _on_artwork_changed(self, artwork, name): - """ - Update children artwork if matching name - @param artwork as Artwork - @param name as str - """ - for child in self._box.get_children(): - if name == child.name: - child.set_artwork() - - def _on_loading_changed(self, player, status, track): - """ - Stop loading for track child - """ - for child in self.children: - if child.data.id == track.id: - child.set_loading(False) - break - - def _on_radio_changed(self, radios, radio_id): - """ - Update view based on radio_id status - @param radios as Radios - @param radio_id as int - """ - exists = radios.exists(radio_id) - if exists: - item = None - for child in self._box.get_children(): - if child.data.id == radio_id: - item = child - break - if item is None: - self.add_value(radio_id) - else: - name = App().radios.get_name(radio_id) - item.rename(name) - else: - for child in self._box.get_children(): - if child.data.id == radio_id: - child.destroy() - -####################### -# PRIVATE # -####################### diff --git a/lollypop/widgets_artwork_radio.py b/lollypop/widgets_artwork_radio.py deleted file mode 100644 index a882a9802c5a68d42b6cd1d5c95f4091d4b40967..0000000000000000000000000000000000000000 --- a/lollypop/widgets_artwork_radio.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import Gio, GObject - -from lollypop.widgets_artwork import ArtworkSearchWidget, ArtworkSearchChild -from lollypop.define import App -from lollypop.logger import Logger -from lollypop.utils import emit_signal - - -class RadioArtworkSearchWidget(ArtworkSearchWidget): - """ - Search for radio artwork - """ - - __gsignals__ = { - "hidden": (GObject.SignalFlags.RUN_FIRST, None, (bool,)), - } - - def __init__(self, name, view_type): - """ - Init search - @param name as str - @param view_type as ViewType - """ - ArtworkSearchWidget.__init__(self, view_type) - self.__name = name - -####################### -# PROTECTED # -####################### - def _save_from_filename(self, filename): - """ - Save filename as album artwork - @param button as Gtk.button - """ - try: - f = Gio.File.new_for_path(filename) - (status, data, tag) = f.load_contents() - if status: - App().art.add_radio_artwork(self.__name, data) - App().art.clean_radio_cache(self.__name) - App().art.radio_artwork_update(self.__name) - except Exception as e: - Logger.error( - "RadioArtworkSearchWidget::_save_from_filename(): %s" % e) - - def _get_current_search(self): - """ - Return current searches - @return str - """ - search = ArtworkSearchWidget._get_current_search(self) - if search != "": - pass - else: - search = self.__name - return search - - def _on_activate(self, flowbox, child): - """ - Save artwork - @param flowbox as Gtk.FlowBox - @param child as ArtworkSearchChild - """ - try: - if isinstance(child, ArtworkSearchChild): - App().task_helper.run(App().art.add_radio_artwork, - self.__name, child.bytes) - else: - App().task_helper.run(App().art.add_radio_artwork, - self.__name, None) - emit_signal(self, "hidden", True) - except Exception as e: - Logger.error("RadioArtworkSearchWidget::_on_activate(): %s", e) diff --git a/lollypop/widgets_banner_lyrics.py b/lollypop/widgets_banner_lyrics.py index 1a8ea6454a832e3e25e60b09444b4499f49704a3..4f1e15d19910bae231b3ade9eb6cd6191aa0a024 100644 --- a/lollypop/widgets_banner_lyrics.py +++ b/lollypop/widgets_banner_lyrics.py @@ -17,7 +17,6 @@ from gettext import gettext as _ from lollypop.define import App, MARGIN, ViewType from lollypop.define import ArtBehaviour, Size from lollypop.widgets_banner import BannerWidget -from lollypop.objects_radio import Radio from lollypop.utils import emit_signal from lollypop.helper_signals import SignalsHelper, signals_map @@ -150,26 +149,15 @@ class LyricsBannerWidget(BannerWidget, SignalsHelper): else: self._artwork.get_style_context().remove_class( "default-banner") - if isinstance(App().player.current_track, Radio): - App().art_helper.set_radio_artwork( - App().player.current_track.name, - # +100 to prevent resize lag - self.width + 100, - self.height, - self._artwork.get_scale_factor(), - ArtBehaviour.BLUR_HARD | - ArtBehaviour.DARKER, - self._on_artwork) - else: - App().art_helper.set_album_artwork( - App().player.current_track.album, - # +100 to prevent resize lag - self.width + 100, - self.height, - self._artwork.get_scale_factor(), - ArtBehaviour.BLUR_HARD | - ArtBehaviour.DARKER, - self._on_artwork) + App().art_helper.set_album_artwork( + App().player.current_track.album, + # +100 to prevent resize lag + self.width + 100, + self.height, + self._artwork.get_scale_factor(), + ArtBehaviour.BLUR_HARD | + ArtBehaviour.DARKER, + self._on_artwork) def __set_internal_size(self): """ diff --git a/lollypop/widgets_banner_playlist.py b/lollypop/widgets_banner_playlist.py index 0dbac13de9713c54d32114f2a351a2e185a2866a..96cf542eb4edcf119e67674cb059f150eb07a0a9 100644 --- a/lollypop/widgets_banner_playlist.py +++ b/lollypop/widgets_banner_playlist.py @@ -54,6 +54,7 @@ class PlaylistBannerWidget(BannerWidget, SignalsHelper): return [ (view, "initialized", "_on_view_updated"), (view, "updated", "_on_view_updated"), + (App().player, "duration-changed", "_on_view_updated"), (App().player, "playback-added", "_on_playback_changed"), (App().player, "playback-updated", "_on_playback_changed"), (App().player, "playback-setted", "_on_playback_changed"), @@ -102,9 +103,9 @@ class PlaylistBannerWidget(BannerWidget, SignalsHelper): if BannerWidget._handle_width_allocate(self, allocation): self.__set_internal_size() - def _on_view_updated(self, view): + def _on_view_updated(self, *ignore): """ - @param view as AlbumsListView + Update duration """ if self.__view.children: self.__duration_task = True diff --git a/lollypop/widgets_banner_radios.py b/lollypop/widgets_banner_radios.py deleted file mode 100644 index fc2d24c244e40e466f9358cc84c55f8534b4c1c4..0000000000000000000000000000000000000000 --- a/lollypop/widgets_banner_radios.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import Gtk, Pango - -from gettext import gettext as _ - -from lollypop.define import ArtSize, MARGIN, ViewType, Size -from lollypop.utils import get_network_available, popup_widget -from lollypop.widgets_banner import BannerWidget - - -class RadiosBannerWidget(BannerWidget): - """ - Banner for radios - """ - - def __init__(self, view_type): - """ - Init banner - @param view_type as ViewType - """ - BannerWidget.__init__(self, view_type | ViewType.OVERLAY) - self.__pop_tunein = None - grid = Gtk.Grid() - grid.set_property("valign", Gtk.Align.CENTER) - grid.show() - self.__title_label = Gtk.Label.new( - "" + _("Radios") + "") - self.__title_label.show() - self.__title_label.set_use_markup(True) - self.__title_label.set_hexpand(True) - self.__title_label.get_style_context().add_class("dim-label") - self.__title_label.set_property("halign", Gtk.Align.START) - self.__title_label.set_ellipsize(Pango.EllipsizeMode.END) - self.__new_button = Gtk.Button.new_from_icon_name( - "document-new-symbolic", Gtk.IconSize.BUTTON) - self.__new_button.connect("clicked", self.__on_new_button_clicked) - self.__new_button.set_property("halign", Gtk.Align.CENTER) - self.__new_button.get_style_context().add_class("banner-button") - self.__new_button.show() - self.__tunein_button = Gtk.Button.new_from_icon_name( - "edit-find-symbolic", Gtk.IconSize.BUTTON) - self.__tunein_button.show() - self.__tunein_button.get_style_context().add_class("banner-button") - self.__tunein_button.connect("clicked", - self.__on_tunein_button_clicked) - box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) - box.show() - box.get_style_context().add_class("linked") - box.add(self.__new_button) - box.add(self.__tunein_button) - grid.add(self.__title_label) - grid.add(box) - grid.set_margin_start(MARGIN) - grid.set_margin_end(MARGIN) - if not get_network_available("TUNEIN"): - self.__tunein_button.set_sensitive(False) - self._overlay.add_overlay(grid) - self._overlay.set_overlay_pass_through(grid, True) - self.connect("unmap", self.__on_unmap) - - def update_for_width(self, width): - """ - Update banner internals for width, call this before showing banner - @param width as int - """ - BannerWidget.update_for_width(self, width) - self.__set_internal_size() - - @property - def height(self): - """ - Get wanted height - @return int - """ - return ArtSize.SMALL - -####################### -# PROTECTED # -####################### - def _handle_width_allocate(self, allocation): - """ - Update artwork - @param allocation as Gtk.Allocation - """ - if BannerWidget._handle_width_allocate(self, allocation): - self.__set_internal_size() - -####################### -# PRIVATE # -####################### - def __set_internal_size(self): - """ - Update font size - """ - title_context = self.__title_label.get_style_context() - for c in title_context.list_classes(): - title_context.remove_class(c) - if self.width <= Size.MEDIUM: - self.__title_label.get_style_context().add_class( - "text-large") - else: - self.__title_label.get_style_context().add_class( - "text-x-large") - - def __on_unmap(self, widget): - """ - Destroy popover - @param widget as Gtk.Widget - """ - if self.__pop_tunein is not None: - self.__pop_tunein.destroy() - self.__pop_tunein = None - - def __on_new_button_clicked(self, button): - """ - Show RadioPopover - @param button as Gtk.Button - """ - from lollypop.menu_radio import RadioMenu - menu_widget = RadioMenu(None, self.view_type) - menu_widget.show() - popup_widget(menu_widget, button, None, None, button) - - def __on_tunein_button_clicked(self, button): - """ - Show playlist menu - @param button as Gtk.Button - """ - if self.__pop_tunein is None: - from lollypop.pop_tunein import TuneinPopover - self.__pop_tunein = TuneinPopover() - self.__pop_tunein.populate() - self.__pop_tunein.set_relative_to(button) - self.__pop_tunein.popup() diff --git a/lollypop/placeholder.py b/lollypop/widgets_placeholder.py similarity index 100% rename from lollypop/placeholder.py rename to lollypop/widgets_placeholder.py diff --git a/lollypop/widgets_player_artwork.py b/lollypop/widgets_player_artwork.py index 6e3569cc36033279c6c6bef296d69787d1dbadcc..d6f591eb439bd83211fed0d13ba62e51ca7c3f8a 100644 --- a/lollypop/widgets_player_artwork.py +++ b/lollypop/widgets_player_artwork.py @@ -14,7 +14,6 @@ from gi.repository import Gtk from lollypop.define import App, ArtBehaviour from lollypop.objects_album import Album -from lollypop.objects_radio import Radio from lollypop.helper_signals import SignalsHelper, signals @@ -40,7 +39,6 @@ class ArtworkPlayerWidget(Gtk.Image, SignalsHelper): return [ (App().player, "current-changed", "_on_current_changed"), (App().art, "album-artwork-changed", "_on_album_artwork_changed"), - (App().art, "radio-artwork-changed", "_on_radio_artwork_changed") ] def set_art_size(self, width, height): @@ -85,15 +83,7 @@ class ArtworkPlayerWidget(Gtk.Image, SignalsHelper): @param callback as function """ scale_factor = self.get_scale_factor() - if isinstance(App().player.current_track, Radio): - App().art_helper.set_radio_artwork( - App().player.current_track.name, - width, - height, - scale_factor, - behaviour, - callback) - elif App().player.current_track.id is not None: + if App().player.current_track.id is not None: if self.__per_track_cover: behaviour |= ArtBehaviour.NO_CACHE album = Album(App().player.current_track.album.id) @@ -133,16 +123,6 @@ class ArtworkPlayerWidget(Gtk.Image, SignalsHelper): self.set_artwork(self.__width, self.__height, self.__on_artwork, self.__behaviour) - def _on_radio_artwork_changed(self, art, name): - """ - Update logo for name - @param art as Art - @param name as str - """ - if App().player.current_track.album_artist == name: - self.set_artwork(self.__width, self.__height, - self.__on_artwork, self.__behaviour) - ####################### # PRIVATE # ####################### @@ -156,10 +136,7 @@ class ArtworkPlayerWidget(Gtk.Image, SignalsHelper): context_style.add_class("cover-background") if self.__behaviour & ArtBehaviour.ROUNDED: context_style.add_class("rounded") - if isinstance(App().player.current_track, Radio): - icon_name = "audio-input-microphone-symbolic" - else: - icon_name = "folder-music-symbolic" + icon_name = "folder-music-symbolic" self.set_from_icon_name(icon_name, Gtk.IconSize.INVALID) self.set_pixel_size(self.__width / 3) self.set_size_request(self.__width, self.__height) diff --git a/lollypop/widgets_player_buttons.py b/lollypop/widgets_player_buttons.py index b81679e2fcb319f1617d22887b5efcaf92cfbf8b..b70c8aeddde88a9cf00cad49f8c0a3b718441aa7 100644 --- a/lollypop/widgets_player_buttons.py +++ b/lollypop/widgets_player_buttons.py @@ -14,7 +14,6 @@ from gi.repository import GLib, Gtk from gettext import gettext as _ -from lollypop.objects_radio import Radio from lollypop.define import App from lollypop.helper_signals import SignalsHelper, signals @@ -139,10 +138,9 @@ class ButtonsPlayerWidget(Gtk.Box, SignalsHelper): self.__prev_button.set_sensitive(False) self.__next_button.set_sensitive(False) else: - is_radio = isinstance(App().player.current_track, Radio) self.__play_button.set_sensitive(True) - self.__prev_button.set_sensitive(not is_radio) - self.__next_button.set_sensitive(not is_radio) + self.__prev_button.set_sensitive(True) + self.__next_button.set_sensitive(True) def _on_prev_changed(self, player): """ diff --git a/lollypop/widgets_radio.py b/lollypop/widgets_radio.py deleted file mode 100644 index db7b527ea3f4bfb13433c779568a04293c4c63f8..0000000000000000000000000000000000000000 --- a/lollypop/widgets_radio.py +++ /dev/null @@ -1,242 +0,0 @@ -# Copyright (c) 2014-2020 Cedric Bellegarde -# 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 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -from gi.repository import Gtk, Pango, GObject - -from lollypop.define import App, ArtSize, ArtBehaviour, ViewType, MARGIN_SMALL -from lollypop.define import MARGIN -from lollypop.utils import on_query_tooltip, emit_signal -from lollypop.objects_radio import Radio - - -class RadioWidget(Gtk.FlowBoxChild): - """ - Widget with radio cover and title - """ - __gsignals__ = { - "populated": (GObject.SignalFlags.RUN_FIRST, None, ()), - } - - def __init__(self, radio_id, view_type, font_height): - """ - Init radio widget - @param radio_id as int - @param view_type as ViewType - @param font_height as int - """ - Gtk.FlowBoxChild.__init__(self) - self.__artwork = None - self.__font_height = font_height - self._track = Radio(radio_id) - self.__view_type = view_type - self.set_view_type(view_type) - self.set_property("halign", Gtk.Align.CENTER) - self.set_property("margin", MARGIN) - - def populate(self): - """ - Init widget content - """ - if self.__artwork is None: - grid = Gtk.Grid() - grid.set_row_spacing(MARGIN_SMALL) - grid.set_orientation(Gtk.Orientation.VERTICAL) - grid.show() - self.__artwork = Gtk.Image.new() - self.__artwork.show() - self.__label = Gtk.Label.new() - self.__label.set_justify(Gtk.Justification.CENTER) - self.__label.set_ellipsize(Pango.EllipsizeMode.END) - self.__label.set_text(self._track.name) - self.__label.get_style_context().add_class("big-padding") - self.__label.set_property("has-tooltip", True) - self.__label.connect("query-tooltip", on_query_tooltip) - self.__label.show() - self.__spinner = Gtk.Spinner.new() - self.__spinner.get_style_context().add_class("big-padding") - self.__spinner.show() - self.__stack = Gtk.Stack.new() - self.__stack.show() - self.__stack.add(self.__label) - self.__stack.add(self.__spinner) - grid.add(self.__artwork) - grid.add(self.__stack) - self.add(grid) - self.set_artwork() - self.set_selection() - self.show() - else: - self.set_artwork() - - def disable_artwork(self): - """ - Disable widget artwork - """ - if self.__artwork is not None: - self.__artwork.set_size_request(self.__art_size, self.__art_size) - self.__artwork.set_from_surface(None) - - def set_artwork(self): - """ - Set artwork - """ - if self.__artwork is None: - return - if self.__art_size < ArtSize.BIG: - frame = "small-cover-frame" - else: - frame = "cover-frame" - App().art_helper.set_frame(self.__artwork, - frame, - self.__art_size, - self.__art_size) - App().art_helper.set_radio_artwork(self._track.name, - self.__art_size, - self.__art_size, - self.__artwork.get_scale_factor(), - ArtBehaviour.CACHE | - ArtBehaviour.CROP, - self.__on_radio_artwork) - - def set_view_type(self, view_type): - """ - Update artwork size - @param view_type as ViewType - """ - self.__view_type = view_type - if self.__view_type & ViewType.SMALL: - self.__art_size = ArtSize.MEDIUM - elif self.__view_type & ViewType.ADAPTIVE: - self.__art_size = ArtSize.BANNER - else: - self.__art_size = ArtSize.BIG - self.set_size_request(self.__art_size, - self.__art_size + self.__font_height) - - def set_loading(self, loading): - """ - Show spinner - @param loading as bool - """ - if loading: - self.__spinner.start() - self.__stack.set_visible_child(self.__spinner) - else: - self.__spinner.stop() - self.__stack.set_visible_child(self.__label) - - def do_get_preferred_width(self): - """ - Return preferred width - @return (int, int) - """ - if self.__artwork is None: - return (0, 0) - width = Gtk.FlowBoxChild.do_get_preferred_width(self)[0] - return (width, width) - - def rename(self, name): - """ - Set radio name - @param name as str - """ - self.__label.set_label(name) - - def set_selection(self): - """ - Mark widget as selected if currently playing - """ - if self.__artwork is None: - return - selected = isinstance(App().player.current_track, Radio) and\ - self._track.id == App().player.current_track.id - if selected: - self.__artwork.set_state_flags(Gtk.StateFlags.SELECTED, True) - else: - self.__artwork.set_state_flags(Gtk.StateFlags.NORMAL, True) - - @property - def spinner(self): - """ - Get radio spinner - """ - return self.__spinner - - @property - def is_populated(self): - """ - True if album populated - @return bool - """ - return True - - @property - def artwork(self): - """ - Get album artwork - @return Gtk.Image - """ - return self.__artwork - - @property - def name(self): - """ - Get name - @return str - """ - return self._track.name - - @property - def sortname(self): - """ - Get sortname - @return str - """ - return self._track.name - - @property - def data(self): - """ - Get track - @return int - """ - return self._track - -####################### -# PROTECTED # -####################### - def _on_loading_changed(self, player, status, track_id): - """ - Show a spinner while loading - @param player as Player - @param status as bool - @param track_id as int - """ - if track_id != self._track.id: - return - -####################### -# PRIVATE # -####################### - def __on_radio_artwork(self, surface): - """ - Set radio artwork - @param surface as str - """ - if surface is None: - self.__artwork.set_from_icon_name( - "audio-input-microphone-symbolic", - Gtk.IconSize.DIALOG) - else: - self.__artwork.set_from_surface(surface) - del surface - emit_signal(self, "populated") diff --git a/run.sh b/run.sh index ccd41bd74cd66ea7074cb31a9555540f9d1ce6aa..451ae071b8a817fc53e98333fb648e3c86d78ffa 100755 --- a/run.sh +++ b/run.sh @@ -1,4 +1,4 @@ -rm -fr /usr/local/lib/python3.7/site-packages/lollypop/ +rm -fr /usr/local/lib/python3.*/site-packages/lollypop/ ninja -C build install reset lollypop -e