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




commit 7638a14b04f7d40627bcc0ee1380752c75c6edd8
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                       |   8 ++
 gnomemusic/coredisc.py                        |  12 +++
 gnomemusic/coregrilo.py                       |   8 ++
 gnomemusic/grilowrappers/grltrackerwrapper.py | 114 ++++++++++++++++++++++++++
 4 files changed, 142 insertions(+)
---
diff --git a/gnomemusic/corealbum.py b/gnomemusic/corealbum.py
index 72fe2f0e4..1ed685863 100644
--- a/gnomemusic/corealbum.py
+++ b/gnomemusic/corealbum.py
@@ -116,6 +116,14 @@ 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, duration):
         duration = 0
 
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 1dec4fabe..3638ba1db 100644
--- a/gnomemusic/grilowrappers/grltrackerwrapper.py
+++ b/gnomemusic/grilowrappers/grltrackerwrapper.py
@@ -365,6 +365,119 @@ 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.model]
+                changed_discs = set(new_discs) ^ set(discs)
+                album_model = corealbum.props.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(
+                            media, disc_nr, self._coremodel)
+                        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.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 ?album_disc_number
+                    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
+            rdf:type(?album)
+            tracker:id(?album) AS ?id
+        WHERE {
+            ?album a nmm:MusicAlbum .
+            ?song a nmm:MusicPiece ;
+                    nmm:musicAlbum ?album .
+            FILTER ( tracker:id(?song) = %(song_id)s )
+        }
+        """.replace("\n", " ").strip() % {
+            "song_id": song_id
+        }
+
+        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:
@@ -452,6 +565,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]