[gnome-music/wip/jfelder/coredisc-updates: 7/7] Handle CoreDisc updates




commit cefe56e8f161c24dd6c2bb49b5c953850f3e4c05
Author: Jean Felder <jfelder src gnome org>
Date:   Fri Feb 21 21:41:45 2020 +0100

    Handle CoreDisc updates
    
    CoreDiscs need to be updated in 3 cases:
    1. a song is removed from an album
    2. a song is added to an album
    3. a song disc number changes
    
    1. a song is removed from an album
    This case is already partially handled. Indeed, the song is already
    part of the initial list of songs. Therefore, when, the song is
    removed from the list, the associated coredisc model gets
    updated. However, if the coredisc becomes empty, it is not removed.
    This issue is fixed by checking the album model when a CoreDisc
    becomes empty ('GrlTrackerWrapper.check_album_disc_changes' is
    called).
    
    2. a song is added to an album
    In that case, the song is not added to the album (and the
    associated coredisc) if the model has already been loaded because the
    filter func has not been updated.
    This issue is fixed by adding a '_check_album_content_change' method
    to GrlTrackerWrapper when a new song is added or changed. This
    function checks that the album already exists and updates the album if
    necessary ('check_album_disc_changes' is called).
    A new CoreDisc may be created, but not CoreDisc will be removed.
    
    3. a song disc number changes
    It's almost the same case as the previous
    one. '_check_album_content_change' method is also called. In that
    case, a CoreDisc may be added and an other CoreDisc may be removed.
    
    This change is based on an initial implementation by Marinus Schraal.

 gnomemusic/corealbum.py                       |  10 ++-
 gnomemusic/coredisc.py                        |  12 +++
 gnomemusic/coregrilo.py                       |   8 ++
 gnomemusic/grilowrappers/grltrackerwrapper.py | 125 ++++++++++++++++++++++++++
 4 files changed, 154 insertions(+), 1 deletion(-)
---
diff --git a/gnomemusic/corealbum.py b/gnomemusic/corealbum.py
index 2df21be75..6cded2145 100644
--- a/gnomemusic/corealbum.py
+++ b/gnomemusic/corealbum.py
@@ -150,9 +150,17 @@ class CoreAlbum(GObject.GObject):
                     coredisc.connect(
                         "notify::duration", self._on_duration_changed)
 
+    def update_discs(self) -> None:
+        """Update CoreDiscs model"""
+        if not self.props.model_loaded:
+            return
+
+        for coredisc in self.props.model:
+            coredisc.update_content()
+
     def _on_duration_changed(
             self, coredisc: CoreDisc, value: GObject.ParamSpec) -> None:
-        duration: int = 0
+        duration = 0
 
         for coredisc in self._disc_model:
             duration += coredisc.props.duration
diff --git a/gnomemusic/coredisc.py b/gnomemusic/coredisc.py
index 697089a65..eed441fe3 100644
--- a/gnomemusic/coredisc.py
+++ b/gnomemusic/coredisc.py
@@ -77,6 +77,13 @@ class CoreDisc(GObject.GObject):
         return self._model
 
     def _on_disc_changed(self, model, position, removed, added):
+        # If a CoreDisc becomes empty, it needs to be removed from its
+        # CoreAlbum.
+        if (removed > 0
+                and model.get_n_items() == 0):
+            self._coregrilo.check_album_disc_changes(self.props.media)
+            return
+
         with self.freeze_notify():
             duration = 0
             for coresong in model:
@@ -85,6 +92,11 @@ class CoreDisc(GObject.GObject):
 
             self.props.duration = duration
 
+    def update_content(self) -> None:
+        """Update the model"""
+        self._get_album_disc(
+            self.props.media, self.props.disc_nr, self._filter_model)
+
     def _get_album_disc(self, media, discnr, model):
         album_ids = []
         model_filter = model
diff --git a/gnomemusic/coregrilo.py b/gnomemusic/coregrilo.py
index d3f9b4e0e..8c93a3f93 100644
--- a/gnomemusic/coregrilo.py
+++ b/gnomemusic/coregrilo.py
@@ -200,6 +200,14 @@ class CoreGrilo(GObject.GObject):
         self._wrappers[source].populate_album_disc_songs(
             media, discnr, callback)
 
+    def check_album_disc_changes(self, media: Grl.Media) -> None:
+        """Update album and corediscs model
+
+        :param Grl.Media media: media with the album id
+        """
+        source: str = media.get_source()
+        self._wrappers[source].check_album_disc_changes(media)
+
     def writeback(self, media, key):
         """Store the values associated with the key.
 
diff --git a/gnomemusic/grilowrappers/grltrackerwrapper.py b/gnomemusic/grilowrappers/grltrackerwrapper.py
index 72e477be3..dfddaa27b 100644
--- a/gnomemusic/grilowrappers/grltrackerwrapper.py
+++ b/gnomemusic/grilowrappers/grltrackerwrapper.py
@@ -370,6 +370,130 @@ class GrlTrackerWrapper(GObject.GObject):
         self.props.source.query(
             query, metadata_keys, self._fast_options, check_artist_cb)
 
+    def check_album_disc_changes(self, media):
+        """Update album and corediscs model
+
+        :param Grl.Media media: media with the album id
+        """
+        def _check_discs_cb(source, op_id, media, remaining, error):
+            if error:
+                self._log.warning(
+                    "Error on album disc change check: {}".format(error))
+                return
+
+            if not media:
+                corealbum = self._album_ids[album_id]
+                new_discs = [media.get_album_disc_number() for media in medias]
+                discs = [disc.props.disc_nr
+                         for disc in corealbum.props.disc_model]
+                changed_discs = set(new_discs) ^ set(discs)
+                album_model = corealbum.props.disc_model.get_model()
+                for disc_nr in changed_discs:
+                    if disc_nr in new_discs:
+                        for media in medias:
+                            if media.get_album_disc_number() == disc_nr:
+                                break
+                        self._log.debug(
+                            "Add Disc {} to album {}".format(
+                                disc_nr, corealbum.props.title))
+                        coredisc = CoreDisc(self._application, media, disc_nr)
+                        album_model.append(coredisc)
+                    elif disc_nr in discs:
+                        for idx, coredisc in enumerate(album_model):
+                            if coredisc.props.disc_nr == disc_nr:
+                                self._log.debug(
+                                    "Remove disc {} from album {}".format(
+                                        disc_nr, corealbum.props.title))
+                                coredisc = corealbum.props.disc_model[idx]
+                                album_model.remove(idx)
+                                break
+
+                corealbum.update_discs()
+                return
+
+            medias.append(media)
+
+        medias = []
+        album_id = media.get_id()
+        query = """
+        SELECT
+            ?type ?id ?albumDiscNumber
+        WHERE {
+            SERVICE <dbus:%(miner_fs_busname)s> {
+                GRAPH tracker:Audio {
+                    SELECT DISTINCT
+                        %(media_type)s AS ?type
+                        ?album AS ?id
+                        nmm:setNumber(nmm:musicAlbumDisc(?song))
+                            AS ?albumDiscNumber
+                    WHERE {
+                        ?song a nmm:MusicPiece;
+                                nmm:musicAlbum ?album .
+                        FILTER ( ?album = <%(album_id)s> )
+                        %(location_filter)s
+                    }
+                    ORDER BY ?albumDiscNumber
+                }
+            }
+        }
+        """.replace("\n", " ").strip() % {
+            "miner_fs_busname": self._tracker_wrapper.props.miner_fs_busname,
+            "media_type": int(Grl.MediaType.CONTAINER),
+            "album_id": album_id,
+            "location_filter": self._tracker_wrapper.location_filter()
+        }
+
+        options = self._fast_options.copy()
+        self.props.source.query(
+            query, [Grl.METADATA_KEY_ID, Grl.METADATA_KEY_ALBUM_DISC_NUMBER],
+            options, _check_discs_cb)
+
+    def _check_album_content_change(self, song_id):
+        def _check_change_cb(source, op_id, media, remaining, error):
+            if error:
+                self._log.warning(
+                    "Error on album content change check: {}".format(error))
+                return
+
+            if not media:
+                return
+
+            corealbum = self._album_ids.get(media.get_id(), None)
+            if (corealbum is None
+                    or not corealbum.props.model_loaded):
+                return
+
+            self.check_album_disc_changes(media)
+
+        query = """
+        SELECT
+            ?type ?id
+        WHERE {
+            SERVICE <dbus:%(miner_fs_busname)s> {
+                GRAPH tracker:Audio {
+                    SELECT
+                        %(media_type)s AS ?type
+                        ?album AS ?id
+                    WHERE {
+                        ?song a nmm:MusicPiece ;
+                                nmm:musicAlbum ?album .
+                        FILTER ( ?song = <%(song_id)s> )
+                        %(location_filter)s
+                    }
+                }
+            }
+        }
+        """.replace("\n", " ").strip() % {
+            "miner_fs_busname": self._tracker_wrapper.props.miner_fs_busname,
+            "media_type": int(Grl.MediaType.CONTAINER),
+            "song_id": song_id,
+            "location_filter": self._tracker_wrapper.location_filter()
+        }
+
+        options = self._fast_options.copy()
+        self.props.source.query(
+            query, [Grl.METADATA_KEY_ID], options, _check_change_cb)
+
     def _remove_media(self, media_ids: List[str]) -> None:
         for media_id in media_ids:
             try:
@@ -457,6 +581,7 @@ class GrlTrackerWrapper(GObject.GObject):
                 self._hash[media_id].update(media)
 
             media_ids.remove(media_id)
+            self._check_album_content_change(media.get_id())
 
         self.props.source.query(
             self._song_media_query(media_ids),


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