[gnome-music/wip/mschraal/core] player: Restore song validation



commit 8946b0cc6495328d5fe41410916101a6760bf936
Author: Jean Felder <jfelder src gnome org>
Date:   Sun Jul 14 13:38:28 2019 +0200

    player: Restore song validation
    
    Song Validation logic was removed with the big core rework. This
    commits restores it.

 gnomemusic/coremodel.py          |  12 +++++
 gnomemusic/coresong.py           |  11 ++++
 gnomemusic/player.py             | 109 ++++++++++++++++++++++++++++-----------
 gnomemusic/songliststore.py      |   8 +++
 gnomemusic/views/songsview.py    |  16 +++---
 gnomemusic/widgets/songwidget.py |  16 ++++++
 6 files changed, 133 insertions(+), 39 deletions(-)
---
diff --git a/gnomemusic/coremodel.py b/gnomemusic/coremodel.py
index 2b7a7b02..cafedc59 100644
--- a/gnomemusic/coremodel.py
+++ b/gnomemusic/coremodel.py
@@ -212,6 +212,9 @@ class CoreModel(GObject.GObject):
                     song.bind_property(
                         "state", coresong, "state",
                         GObject.BindingFlags.SYNC_CREATE)
+                    song.bind_property(
+                        "validation", coresong, "validation",
+                        GObject.BindingFlags.SYNC_CREATE)
 
         with model.freeze_notify():
 
@@ -236,6 +239,9 @@ class CoreModel(GObject.GObject):
                     song.bind_property(
                         "state", model_song, "state",
                         GObject.BindingFlags.SYNC_CREATE)
+                    song.bind_property(
+                        "validation", model_song, "validation",
+                        GObject.BindingFlags.SYNC_CREATE)
 
                 self.emit("playlist-loaded")
             elif playlist_type == PlayerPlaylist.Type.ARTIST:
@@ -259,6 +265,9 @@ class CoreModel(GObject.GObject):
                     song.bind_property(
                         "state", model_song, "state",
                         GObject.BindingFlags.SYNC_CREATE)
+                    song.bind_property(
+                        "validation", model_song, "validation",
+                        GObject.BindingFlags.SYNC_CREATE)
 
                 self.emit("playlist-loaded")
             elif playlist_type == PlayerPlaylist.Type.SONGS:
@@ -308,6 +317,9 @@ class CoreModel(GObject.GObject):
                     song.bind_property(
                         "state", model_song, "state",
                         GObject.BindingFlags.SYNC_CREATE)
+                    song.bind_property(
+                        "validation", model_song, "validation",
+                        GObject.BindingFlags.SYNC_CREATE)
 
                 # self._search_signal_id = self._song_search_model.connect(
                 #     "items-changed", _on_items_changed)
diff --git a/gnomemusic/coresong.py b/gnomemusic/coresong.py
index 973cb537..a79f81ed 100644
--- a/gnomemusic/coresong.py
+++ b/gnomemusic/coresong.py
@@ -22,6 +22,8 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from enum import IntEnum
+
 import gi
 gi.require_version('Grl', '0.3')
 from gi.repository import Grl, GLib, GObject
@@ -44,6 +46,14 @@ class CoreSong(GObject.GObject):
     title = GObject.Property(type=str)
     track_number = GObject.Property(type=int)
     url = GObject.Property(type=str)
+    validation = GObject.Property()  # FIXME: How to set an IntEnum type?
+
+    class Validation(IntEnum):
+        """Enum for song validation"""
+        PENDING = 0
+        IN_PROGRESS = 1
+        FAILED = 2
+        SUCCEEDED = 3
 
     def __init__(self, media, coreselection, grilo):
         super().__init__()
@@ -54,6 +64,7 @@ class CoreSong(GObject.GObject):
         self._selected = False
 
         self.props.grlid = media.get_source() + media.get_id()
+        self.props.validation = CoreSong.Validation.PENDING
         self.update(media)
 
     def __eq__(self, other):
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index ea9cef08..18c74135 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -50,14 +50,6 @@ class RepeatMode(IntEnum):
     SHUFFLE = 3
 
 
-class ValidationStatus(IntEnum):
-    """Enum for song validation"""
-    PENDING = 0
-    IN_PROGRESS = 1
-    FAILED = 2
-    SUCCEEDED = 3
-
-
 class PlayerField(IntEnum):
     """Enum for player model fields"""
     SONG = 0
@@ -79,10 +71,6 @@ class PlayerPlaylist(GObject.GObject):
         PLAYLIST = 3
         SEARCH_RESULT = 4
 
-    __gsignals__ = {
-        'song-validated': (GObject.SignalFlags.RUN_FIRST, None, (int, int)),
-    }
-
     repeat_mode = GObject.Property(type=int, default=RepeatMode.NONE)
 
     def __repr__(self):
@@ -100,10 +88,10 @@ class PlayerPlaylist(GObject.GObject):
         self._type = -1
         self._id = -1
 
-        # self._validation_indexes = None
-        # self._discoverer = GstPbutils.Discoverer()
-        # self._discoverer.connect('discovered', self._on_discovered)
-        # self._discoverer.start()
+        self._validation_songs = {}
+        self._discoverer = GstPbutils.Discoverer()
+        self._discoverer.connect("discovered", self._on_discovered)
+        self._discoverer.start()
 
         self._model = self._app.props.coremodel.props.playlist_sort
 
@@ -185,10 +173,14 @@ class PlayerPlaylist(GObject.GObject):
             next_position = self.props.position + 1
 
         self._model[self.props.position].props.state = SongWidget.State.PLAYED
-        self._model[next_position].props.state = SongWidget.State.PLAYING
-
         self._position = next_position
 
+        next_song = self._model[next_position]
+        if next_song.props.validation == CoreSong.Validation.FAILED:
+            return self.next()
+
+        next_song.props.state = SongWidget.State.PLAYING
+        self._validate_next_song()
         return True
 
     @log
@@ -210,10 +202,14 @@ class PlayerPlaylist(GObject.GObject):
             previous_position = self.props.position - 1
 
         self._model[self.props.position].props.state = SongWidget.State.PLAYED
-        self._model[previous_position].props.state = SongWidget.State.PLAYING
-
         self._position = previous_position
 
+        previous_song = self._model[previous_position]
+        if previous_song.props.validation == CoreSong.Validation.FAILED:
+            return self.previous()
+
+        self._model[previous_position].props.state = SongWidget.State.PLAYING
+        self._validate_previous_song()
         return True
 
     @GObject.Property(type=int, default=0, flags=GObject.ParamFlags.READABLE)
@@ -264,11 +260,17 @@ class PlayerPlaylist(GObject.GObject):
                 position = 0
             song = self._model.get_item(position)
             song.props.state = SongWidget.State.PLAYING
+            self._position = position
+            self._validate_song(song)
+            self._validate_next_song()
             return song
 
-        for coresong in self._model:
+        for idx, coresong in enumerate(self._model):
             if coresong == song:
                 coresong.props.state = SongWidget.State.PLAYING
+                self._position = idx
+                self._validate_song(song)
+                self._validate_next_song()
                 return song
 
         return None
@@ -294,6 +296,62 @@ class PlayerPlaylist(GObject.GObject):
         elif self.props.repeat_mode in [RepeatMode.NONE, RepeatMode.ALL]:
             self._model.set_sort_func(None)
 
+    def _validate_song(self, coresong):
+        # Song is being processed or has already been processed.
+        # Nothing to do.
+        if coresong.props.validation > CoreSong.Validation.PENDING:
+            return
+
+        url = coresong.props.url
+        if not url:
+            logger.warning(
+                "The item {} doesn't have a URL set.".format(coresong))
+            return
+        if not url.startswith("file://"):
+            logger.debug(
+                "Skipping validation of {} as not a local file".format(url))
+            return
+
+        coresong.props.validation = CoreSong.Validation.IN_PROGRESS
+        self._validation_songs[url] = coresong
+        self._discoverer.discover_uri_async(url)
+
+    def _validate_next_song(self):
+        if self.props.repeat_mode == RepeatMode.SONG:
+            return
+
+        current_position = self.props.position
+        next_position = current_position + 1
+        if next_position == self._model.get_n_items():
+            if self.props.repeat_mode != RepeatMode.ALL:
+                return
+            next_position = 0
+
+        self._validate_song(self._model[next_position])
+
+    def _validate_previous_song(self):
+        if self.props.repeat_mode == RepeatMode.SONG:
+            return
+
+        current_position = self.props.position
+        previous_position = current_position - 1
+        if previous_position < 0:
+            if self.props.repeat_mode != RepeatMode.ALL:
+                return
+            previous_position = self._model.get_n_items() - 1
+
+        self._validate_song(self._model[previous_position])
+
+    def _on_discovered(self, discoverer, info, error):
+        url = info.get_uri()
+        coresong = self._validation_songs[url]
+
+        if error:
+            logger.warning("Info {}: error: {}".format(info, error))
+            coresong.props.validation = CoreSong.Validation.FAILED
+        else:
+            coresong.props.validation = CoreSong.Validation.SUCCEEDED
+
     @GObject.Property(type=int, flags=GObject.ParamFlags.READABLE)
     def playlist_id(self):
         """Get playlist unique identifier.
@@ -322,8 +380,7 @@ class Player(GObject.GObject):
     __gsignals__ = {
         'playlist-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
         'seek-finished': (GObject.SignalFlags.RUN_FIRST, None, ()),
-        'song-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
-        'song-validated': (GObject.SignalFlags.RUN_FIRST, None, (int, int)),
+        'song-changed': (GObject.SignalFlags.RUN_FIRST, None, ())
     }
 
     state = GObject.Property(type=int, default=Playback.STOPPED)
@@ -350,7 +407,6 @@ class Player(GObject.GObject):
         self._gapless_set = False
 
         self._playlist = PlayerPlaylist(self._app)
-        self._playlist.connect('song-validated', self._on_song_validated)
 
         self._settings = application.props.settings
         self._settings.connect(
@@ -535,11 +591,6 @@ class Player(GObject.GObject):
         self._playlist.add_song(song, song_index)
         self.emit('playlist-changed')
 
-    @log
-    def _on_song_validated(self, playlist, index, status):
-        self.emit('song-validated', index, status)
-        return True
-
     @log
     def playing_playlist(self, playlist_type, playlist_id):
         """Test if the current playlist matches type and id.
diff --git a/gnomemusic/songliststore.py b/gnomemusic/songliststore.py
index a460aeb0..b923943e 100644
--- a/gnomemusic/songliststore.py
+++ b/gnomemusic/songliststore.py
@@ -70,6 +70,8 @@ class SongListStore(Gtk.ListStore):
                      int(coresong.props.favorite), coresong])
                 coresong.connect(
                     "notify::favorite", self._on_favorite_changed)
+                coresong.connect(
+                    "notify::state", self._on_state_changed)
 
     def _on_favorite_changed(self, coresong, value):
         for row in self:
@@ -77,6 +79,12 @@ class SongListStore(Gtk.ListStore):
                 row[6] = coresong.props.favorite
                 break
 
+    def _on_state_changed(self, coresong, value):
+        for row in self:
+            if coresong == row[7]:
+                row[8] = coresong.props.validation
+                break
+
     @GObject.Property(
         type=Gio.ListStore, default=None, flags=GObject.ParamFlags.READABLE)
     def model(self):
diff --git a/gnomemusic/views/songsview.py b/gnomemusic/views/songsview.py
index 07b79b3e..5e6b9d5f 100644
--- a/gnomemusic/views/songsview.py
+++ b/gnomemusic/views/songsview.py
@@ -27,6 +27,7 @@ from gettext import gettext as _
 from gi.repository import Gdk, Gtk, Pango
 
 from gnomemusic import log
+from gnomemusic.coresong import CoreSong
 from gnomemusic.player import PlayerPlaylist
 from gnomemusic.views.baseview import BaseView
 
@@ -65,7 +66,6 @@ class SongsView(BaseView):
 
         self.player = player
         self.player.connect('song-changed', self._update_model)
-        self.player.connect('song-validated', self._on_song_validated)
 
         self._model = self._view.props.model
         self._view.show()
@@ -142,7 +142,11 @@ class SongsView(BaseView):
         if current_song is None:
             return
 
-        if model[itr][7].props.grlid == current_song.props.grlid:
+        coresong = model[itr][7]
+        if coresong.props.validation == CoreSong.Validation.FAILED:
+            cell.props.icon_name = self._error_icon_name
+            cell.props.visible = True
+        elif coresong.props.grlid == current_song.props.grlid:
             cell.props.icon_name = self._now_playing_icon_name
             cell.props.visible = True
         else:
@@ -236,14 +240,6 @@ class SongsView(BaseView):
 
         return False
 
-    @log
-    def _on_song_validated(self, player, index, status):
-        if not player.playing_playlist(PlayerPlaylist.Type.SONGS, None):
-            return
-
-        iter_ = self.model.get_iter_from_string(str(index))
-        self.model[iter_][8] = status
-
     @log
     def _populate(self, data=None):
         """Populates the view"""
diff --git a/gnomemusic/widgets/songwidget.py b/gnomemusic/widgets/songwidget.py
index 54079c18..17878d2f 100644
--- a/gnomemusic/widgets/songwidget.py
+++ b/gnomemusic/widgets/songwidget.py
@@ -154,6 +154,8 @@ class SongWidget(Gtk.EventBox):
         self.props.coresong.bind_property(
             "state", self, "state",
             GObject.BindingFlags.SYNC_CREATE)
+        self.props.coresong.connect(
+            "notify::validation", self._on_validation_changed)
 
         self._number_label.props.no_show_all = True
 
@@ -284,8 +286,22 @@ class SongWidget(Gtk.EventBox):
         style_ctx.remove_class('playing-song-label')
         self._play_icon.set_visible(False)
 
+        coresong = self.props.coresong
+        if coresong.props.validation == CoreSong.Validation.FAILED:
+            self._play_icon.set_visible(True)
+            style_ctx.add_class("dim-label")
+            return
+
         if value == SongWidget.State.PLAYED:
             style_ctx.add_class('dim-label')
         elif value == SongWidget.State.PLAYING:
             self._play_icon.set_visible(True)
             style_ctx.add_class('playing-song-label')
+
+    def _on_validation_changed(self, coresong, sate):
+        validation_status = coresong.props.validation
+        if validation_status == CoreSong.Validation.FAILED:
+            self._play_icon.props.icon_name = "dialog-error-symbolic"
+            self._play_icon.set_visible(True)
+        else:
+            self._play_icon.props.icon_name = "media-playback-start-symbolic"


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]