[gnome-music/wip/jfelder/tracker3] Add NotificationManager



commit af382c5d68d06ab0c03440d828caac37fed80c7f
Author: Marinus Schraal <mschraal gnome org>
Date:   Mon Aug 24 23:57:30 2020 +0200

    Add NotificationManager
    
    A managing class for notifications, thin wrapper around the notification
    widgets for now.
    
    Currently implemented to handle an async issue with flatpak:
    
    Traceback (most recent call last):
      File "/app/lib/python3.8/site-packages/gnomemusic/coregrilo.py", line 154, in _on_source_added
        new_wrapper = GrlTrackerWrapper(
      File "/app/lib/python3.8/site-packages/gnomemusic/grilowrappers/grltrackerwrapper.py", line 107, in 
__init__
        self._initial_songs_fill()
      File "/app/lib/python3.8/site-packages/gnomemusic/grilowrappers/grltrackerwrapper.py", line 409, in 
_initial_songs_fill
        self._window.notifications_popup.push_loading()
    AttributeError: 'NoneType' object has no attribute 'notifications_popup'
    
    _window might not be available yet, NotificationManager does an
    availability check before sending the notification.

 gnomemusic/application.py                       | 13 ++++++
 gnomemusic/grilowrappers/grltrackerplaylists.py | 33 ++++++-------
 gnomemusic/grilowrappers/grltrackerwrapper.py   | 50 ++++++++++----------
 gnomemusic/notificationmanager.py               | 61 +++++++++++++++++++++++++
 4 files changed, 116 insertions(+), 41 deletions(-)
---
diff --git a/gnomemusic/application.py b/gnomemusic/application.py
index e917672a..c92a6aa1 100644
--- a/gnomemusic/application.py
+++ b/gnomemusic/application.py
@@ -40,6 +40,7 @@ from gnomemusic.coreselection import CoreSelection
 from gnomemusic.inhibitsuspend import InhibitSuspend
 from gnomemusic.mpris import MPRIS
 from gnomemusic.musiclogger import MusicLogger
+from gnomemusic.notificationmanager import NotificationManager
 from gnomemusic.pauseonsuspend import PauseOnSuspend
 from gnomemusic.player import Player
 from gnomemusic.scrobbler import LastFmScrobbler
@@ -66,6 +67,7 @@ class Application(Gtk.Application):
         self._log = MusicLogger()
         self._search = Search()
 
+        self._notificationmanager = NotificationManager(self)
         self._coreselection = CoreSelection()
         self._coremodel = CoreModel(self)
         # Order is important: CoreGrilo initializes the Grilo sources,
@@ -175,6 +177,16 @@ class Application(Gtk.Application):
         """
         return self._search
 
+    @GObject.Property(
+        type=NotificationManager, flags=GObject.ParamFlags.READABLE)
+    def notificationmanager(self):
+        """Get notification manager
+
+        :returns: notification manager
+        :rtype: NotificationManager
+        """
+        return self._notificationmanager
+
     def _set_actions(self):
         action_entries = [
             ('about', self._about, None),
@@ -216,6 +228,7 @@ class Application(Gtk.Application):
     def do_activate(self):
         if not self._window:
             self._window = Window(self)
+            self.notify("window")
             self._window.set_default_icon_name(self.props.application_id)
             if self.props.application_id == "org.gnome.Music.Devel":
                 self._window.get_style_context().add_class('devel')
diff --git a/gnomemusic/grilowrappers/grltrackerplaylists.py b/gnomemusic/grilowrappers/grltrackerplaylists.py
index c7854dfc..b5890418 100644
--- a/gnomemusic/grilowrappers/grltrackerplaylists.py
+++ b/gnomemusic/grilowrappers/grltrackerplaylists.py
@@ -76,6 +76,7 @@ class GrlTrackerPlaylists(GObject.GObject):
         self._songs_hash = songs_hash
         self._tracker = tracker_wrapper.props.local_db
         self._tracker_wrapper = tracker_wrapper
+        self._notificationmanager = application.props.notificationmanager
         self._window = application.props.window
 
         self._user_model_filter.set_filter_func(self._user_playlists_filter)
@@ -105,7 +106,7 @@ class GrlTrackerPlaylists(GObject.GObject):
         for playlist in smart_playlists.values():
             self._model.append(playlist)
 
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         query = """
         SELECT DISTINCT
             %(media_type)s AS ?type
@@ -130,10 +131,10 @@ class GrlTrackerPlaylists(GObject.GObject):
             self, source, op_id, media, remaining, data=None, error=None):
         if error:
             self._log.warning("Error: {}".format(error))
-            self._window.notifications_popup.pop_loading()
+            self._notificationmanager.pop_loading()
             return
         if not media:
-            self._window.notifications_popup.pop_loading()
+            self._notificationmanager.pop_loading()
             return
 
         playlist = Playlist(
@@ -185,9 +186,9 @@ class GrlTrackerPlaylists(GObject.GObject):
                         break
 
             self._model_filter.set_filter_func(self._playlists_filter)
-            self._window.notifications_popup.pop_loading()
+            self._notificationmanager.pop_loading()
 
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         query = """
         DELETE {
             ?playlist a rdfs:Resource .
@@ -234,7 +235,7 @@ class GrlTrackerPlaylists(GObject.GObject):
                 query, self.METADATA_KEYS, options, self._add_user_playlist,
                 callback)
 
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         query = """
             INSERT {
                 _:playlist a nmm:Playlist ;
@@ -325,7 +326,7 @@ class Playlist(GObject.GObject):
         self._songs_hash = songs_hash
         self._tracker = tracker_wrapper.props.local_db
         self._tracker_wrapper = tracker_wrapper
-        self._window = application.props.window
+        self._notificationmanager = application.props.notificationmanager
 
         self._fast_options = Grl.OperationOptions()
         self._fast_options.set_resolution_flags(
@@ -347,7 +348,7 @@ class Playlist(GObject.GObject):
         self._model = value
 
     def _populate_model(self):
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
 
         query = """
         SELECT
@@ -404,7 +405,7 @@ class Playlist(GObject.GObject):
             if not media:
                 self.props.count = self._model.get_n_items()
                 self.emit("playlist-loaded")
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             coresong = CoreSong(self._application, media)
@@ -454,7 +455,7 @@ class Playlist(GObject.GObject):
 
         :param str new_name: new playlist name
         """
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
 
         def update_cb(conn, res, data):
             try:
@@ -466,7 +467,7 @@ class Playlist(GObject.GObject):
             else:
                 self._title = new_name
             finally:
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 self.thaw_notify()
 
         query = """
@@ -520,9 +521,9 @@ class Playlist(GObject.GObject):
         def update_cb(conn, res, data):
             # FIXME: Check for failure.
             conn.update_finish(res)
-            self._window.notifications_popup.pop_loading()
+            self._notificationmanager.pop_loading()
 
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         query = """
         INSERT OR REPLACE {
             ?entry nfo:listPosition ?position .
@@ -746,18 +747,18 @@ class SmartPlaylist(Playlist):
         if self._model is None:
             self._model = Gio.ListStore.new(CoreSong)
 
-            self._window.notifications_popup.push_loading()
+            self._notificationmanager.push_loading()
 
             def _add_to_model(source, op_id, media, remaining, error):
                 if error:
                     self._log.warning("Error: {}".format(error))
-                    self._window.notifications_popup.pop_loading()
+                    self._notificationmanager.pop_loading()
                     self.emit("playlist-loaded")
                     return
 
                 if not media:
                     self.props.count = self._model.get_n_items()
-                    self._window.notifications_popup.pop_loading()
+                    self._notificationmanager.pop_loading()
                     self.emit("playlist-loaded")
                     return
 
diff --git a/gnomemusic/grilowrappers/grltrackerwrapper.py b/gnomemusic/grilowrappers/grltrackerwrapper.py
index 83fc41a8..338d17a9 100644
--- a/gnomemusic/grilowrappers/grltrackerwrapper.py
+++ b/gnomemusic/grilowrappers/grltrackerwrapper.py
@@ -91,7 +91,7 @@ class GrlTrackerWrapper(GObject.GObject):
         self._content_changed_timeout = None
         self._tracker_playlists = None
         self._tracker_wrapper = tracker_wrapper
-        self._window = application.props.window
+        self._notificationmanager = application.props.notificationmanager
 
         self._song_search_tracker = Gfm.FilterListModel.new(self._songs_model)
         self._song_search_tracker.set_filter_func(lambda a: False)
@@ -406,19 +406,19 @@ class GrlTrackerWrapper(GObject.GObject):
             options, _update_changed_media)
 
     def _initial_songs_fill(self):
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         songs_added = []
 
         def _add_to_model(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 self._songs_model.splice(
                     self._songs_model.get_n_items(), 0, songs_added)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
 
                 # Initialize the playlists subwrapper after the initial
                 # songs model fill, the playlists expect a filled songs
@@ -483,19 +483,19 @@ class GrlTrackerWrapper(GObject.GObject):
             query, self.METADATA_KEYS, options, _add_to_model)
 
     def _initial_albums_fill(self):
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         albums_added = []
 
         def _add_to_albums_model(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 self._albums_model.splice(
                     self._albums_model.get_n_items(), 0, albums_added)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             album = CoreAlbum(self._application, media)
@@ -552,19 +552,19 @@ class GrlTrackerWrapper(GObject.GObject):
             query, self.METADATA_KEYS, options, _add_to_albums_model)
 
     def _initial_artists_fill(self):
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         artists_added = []
 
         def _add_to_artists_model(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 self._artists_model.splice(
                     self._artists_model.get_n_items(), 0, artists_added)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             artist = CoreArtist(self._application, media)
@@ -618,7 +618,7 @@ class GrlTrackerWrapper(GObject.GObject):
         :param Grl.Media media: The media with the artist id
         :param Gfm.FilterListModel model: The model to fill
         """
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         artist_id = media.get_id()
 
         query = """
@@ -659,12 +659,12 @@ class GrlTrackerWrapper(GObject.GObject):
         def query_cb(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 model.set_filter_func(albums_filter, albums)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             albums.append(media)
@@ -686,7 +686,7 @@ class GrlTrackerWrapper(GObject.GObject):
         :param Grl.Media media: The media with the album id
         :param Gfm.SortListModel disc_model: The model to fill
         """
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
         album_id = media.get_id()
 
         query = """
@@ -720,11 +720,11 @@ class GrlTrackerWrapper(GObject.GObject):
         def _disc_nr_cb(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             disc_nr = media.get_album_disc_number()
@@ -811,7 +811,7 @@ class GrlTrackerWrapper(GObject.GObject):
                 GLib.utf8_casefold(text, -1), -1, GLib.NormalizeMode.NFKD))
 
         # Artist search
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
 
         query = """
         SELECT
@@ -872,12 +872,12 @@ class GrlTrackerWrapper(GObject.GObject):
         def artist_search_cb(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 self._artist_search_model.set_filter_func(artist_filter)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             artist_filter_ids.append(media.get_id())
@@ -887,7 +887,7 @@ class GrlTrackerWrapper(GObject.GObject):
             query, self.METADATA_KEYS, options, artist_search_cb)
 
         # Album search
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
 
         query = """
         SELECT
@@ -940,12 +940,12 @@ class GrlTrackerWrapper(GObject.GObject):
         def albums_search_cb(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 self._album_search_model.set_filter_func(album_filter)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             album_filter_ids.append(media.get_id())
@@ -955,7 +955,7 @@ class GrlTrackerWrapper(GObject.GObject):
             query, self.METADATA_KEYS, options, albums_search_cb)
 
         # Song search
-        self._window.notifications_popup.push_loading()
+        self._notificationmanager.push_loading()
 
         query = """
         SELECT
@@ -1013,12 +1013,12 @@ class GrlTrackerWrapper(GObject.GObject):
         def songs_search_cb(source, op_id, media, remaining, error):
             if error:
                 self._log.warning("Error: {}".format(error))
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             if not media:
                 self._song_search_tracker.set_filter_func(songs_filter)
-                self._window.notifications_popup.pop_loading()
+                self._notificationmanager.pop_loading()
                 return
 
             filter_ids.append(media.get_id())
diff --git a/gnomemusic/notificationmanager.py b/gnomemusic/notificationmanager.py
new file mode 100644
index 00000000..6ffee620
--- /dev/null
+++ b/gnomemusic/notificationmanager.py
@@ -0,0 +1,61 @@
+# Copyright 2020 The GNOME Music Developers
+#
+# GNOME Music 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 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music 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 GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music.  This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered.  If you
+# modify this code, you may extend this exception to your version of the
+# 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 gi.repository import GObject
+
+
+class NotificationManager(GObject.Object):
+    """Managing wrapper around the notification widgets
+    """
+
+    def __init__(self, application):
+        """Initialize the notification manager
+
+       :param Application application: The Application instance
+       """
+        super().__init__()
+
+        self._application = application
+        self._pushed = False
+        self._window = application.props.window
+
+        if self._window is None:
+            application.connect(
+                "notify::window", self._on_window_changed)
+
+    def _on_window_changed(self, klass, value):
+        self._window = self._application.props.window
+
+    def push_loading(self):
+        """Push a loading notifcation."""
+        if self._window:
+            # This makes sure push/pop are in sync when starting.
+            self._pushed = True
+            self._window.notifications_popup.push_loading()
+
+    def pop_loading(self):
+        """Pop a loading notification."""
+        if (self._window
+                and self._pushed):
+            self._window.notifications_popup.pop_loading()


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