Hello community,

here is the log from the commit of package lollypop for openSUSE:Factory 
checked in at 2018-10-29 14:22:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/lollypop (Old)
 and      /work/SRC/openSUSE:Factory/.lollypop.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "lollypop"

Mon Oct 29 14:22:54 2018 rev:54 rq:645113 version:0.9.610

Changes:
--------
--- /work/SRC/openSUSE:Factory/lollypop/lollypop.changes        2018-10-26 
11:10:59.741661169 +0200
+++ /work/SRC/openSUSE:Factory/.lollypop.new/lollypop.changes   2018-10-29 
14:58:59.546026098 +0100
@@ -1,0 +2,9 @@
+Sun Oct 28 17:55:05 UTC 2018 - [email protected]
+
+- Update to version 0.9.610:
+  * Fix broken MTP sync on GNOME 3.30 (glgo#World/lollypop#1522).
+  * Fix a crash about album discs (glgo#World/lollypop#1511).
+  * Fix broken radio view (glgo#World/lollypop#1529).
+- Remove useless runtime dependency on gobject-introspection.
+
+-------------------------------------------------------------------

Old:
----
  lollypop-0.9.609.tar.xz

New:
----
  lollypop-0.9.610.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ lollypop.spec ++++++
--- /var/tmp/diff_new_pack.w3Ln78/_old  2018-10-29 14:59:00.198026875 +0100
+++ /var/tmp/diff_new_pack.w3Ln78/_new  2018-10-29 14:59:00.206026885 +0100
@@ -16,9 +16,8 @@
 #
 
 
-%global gobject_introspection_version 1.35.9
 Name:           lollypop
-Version:        0.9.609
+Version:        0.9.610
 Release:        0
 Summary:        GNOME music playing application
 License:        GPL-3.0-or-later
@@ -31,12 +30,11 @@
 BuildRequires:  meson >= 0.41
 BuildRequires:  pkgconfig
 BuildRequires:  python3-devel
-BuildRequires:  pkgconfig(gobject-introspection-1.0) >= 
%{gobject_introspection_version}
+BuildRequires:  pkgconfig(gobject-introspection-1.0) >= 1.35.9
 BuildRequires:  pkgconfig(gtk+-3.0) >= 3.20
 # Can't migrate to GDBus, the server-side support is not implemented yet:
 #     https://bugzilla.gnome.org/show_bug.cgi?id=656330
 Requires:       dbus-1-python3
-Requires:       gobject-introspection >= %{gobject_introspection_version}
 Requires:       gstreamer-plugins-base
 Requires:       python3-beautifulsoup4
 Requires:       python3-cairo

++++++ _service ++++++
--- /var/tmp/diff_new_pack.w3Ln78/_old  2018-10-29 14:59:00.234026918 +0100
+++ /var/tmp/diff_new_pack.w3Ln78/_new  2018-10-29 14:59:00.234026918 +0100
@@ -1,7 +1,7 @@
 <services>
   <service mode="disabled" name="tar_scm">
     <param name="changesgenerate">enable</param>
-    <param name="revision">0.9.609</param>
+    <param name="revision">0.9.610</param>
     <param name="scm">git</param>
     <param name="url">https://gitlab.gnome.org/World/lollypop.git</param>
     <param name="versionformat">@PARENT_TAG@</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.w3Ln78/_old  2018-10-29 14:59:00.250026937 +0100
+++ /var/tmp/diff_new_pack.w3Ln78/_new  2018-10-29 14:59:00.254026942 +0100
@@ -1,4 +1,4 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://gitlab.gnome.org/World/lollypop.git</param>
-              <param 
name="changesrevision">eee4683f8a1e94bd5ac330855f3372c3f10b7fc1</param></service></servicedata>
\ No newline at end of file
+              <param 
name="changesrevision">76fcc86c22c0df02c644be78368efe6255199cbe</param></service></servicedata>
\ No newline at end of file

++++++ lollypop-0.9.609.tar.xz -> lollypop-0.9.610.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/data/DeviceManagerView.ui 
new/lollypop-0.9.610/data/DeviceManagerView.ui
--- old/lollypop-0.9.609/data/DeviceManagerView.ui      2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/data/DeviceManagerView.ui      2018-10-28 
15:27:23.000000000 +0100
@@ -12,8 +12,9 @@
   <object class="GtkGrid" id="device">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
-    <property name="halign">center</property>
     <property name="margin_top">2</property>
+    <property name="row_spacing">5</property>
+    <property name="column_spacing">5</property>
     <child>
       <object class="GtkButton" id="sync_btn">
         <property name="visible">True</property>
@@ -21,7 +22,6 @@
         <property name="receives_default">True</property>
         <property name="tooltip_text" translatable="yes">Synchronize to your 
device</property>
         <property name="halign">center</property>
-        <property name="valign">end</property>
         <property name="margin_start">5</property>
         <property name="margin_end">5</property>
         <property name="image">image1</property>
@@ -29,8 +29,8 @@
         <signal name="clicked" handler="_on_sync_clicked" swapped="no"/>
       </object>
       <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
+        <property name="left_attach">1</property>
+        <property name="top_attach">1</property>
       </packing>
     </child>
     <child>
@@ -38,13 +38,101 @@
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="halign">center</property>
-        <property name="valign">start</property>
         <property name="margin_start">5</property>
         <signal name="changed" handler="_on_memory_combo_changed" 
swapped="no"/>
       </object>
       <packing>
-        <property name="left_attach">1</property>
+        <property name="left_attach">2</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkInfoBar" id="infobar">
+        <property name="can_focus">False</property>
+        <property name="message_type">error</property>
+        <property name="show_close_button">True</property>
+        <property name="revealed">False</property>
+        <signal name="response" handler="_on_infobar_response" swapped="no"/>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="spacing">6</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child internal-child="content_area">
+          <object class="GtkBox">
+            <property name="can_focus">False</property>
+            <property name="spacing">16</property>
+            <child>
+              <object class="GtkLabel" id="error_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">label</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
         <property name="top_attach">0</property>
+        <property name="width">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+      </object>
+      <packing>
+        <property name="left_attach">3</property>
+        <property name="top_attach">1</property>
       </packing>
     </child>
   </object>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/data/DeviceManagerWidget.ui 
new/lollypop-0.9.610/data/DeviceManagerWidget.ui
--- old/lollypop-0.9.609/data/DeviceManagerWidget.ui    2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/data/DeviceManagerWidget.ui    2018-10-28 
15:27:23.000000000 +0100
@@ -18,64 +18,6 @@
     <property name="row_spacing">5</property>
     <property name="column_spacing">5</property>
     <child>
-      <object class="GtkInfoBar" id="infobar">
-        <property name="app_paintable">True</property>
-        <property name="can_focus">False</property>
-        <property name="hexpand">True</property>
-        <property name="message_type">error</property>
-        <property name="show_close_button">True</property>
-        <signal name="response" handler="_on_response" swapped="no"/>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="infobar-action_area1">
-            <property name="can_focus">False</property>
-            <property name="spacing">6</property>
-            <property name="layout_style">end</property>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child internal-child="content_area">
-          <object class="GtkBox" id="infobar-content_area1">
-            <property name="can_focus">False</property>
-            <property name="spacing">16</property>
-            <child>
-              <object class="GtkLabel" id="error-label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_start">10</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-      </object>
-      <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
-      </packing>
-    </child>
-    <child>
       <object class="GtkOverlay">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
@@ -130,7 +72,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">1</property>
+        <property name="top_attach">0</property>
       </packing>
     </child>
   </object>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/lollypop-0.9.609/data/org.gnome.Lollypop.appdata.xml.in 
new/lollypop-0.9.610/data/org.gnome.Lollypop.appdata.xml.in
--- old/lollypop-0.9.609/data/org.gnome.Lollypop.appdata.xml.in 2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/data/org.gnome.Lollypop.appdata.xml.in 2018-10-28 
15:27:23.000000000 +0100
@@ -27,11 +27,12 @@
     </ul>
   </description>
   <releases>
-    <release version="0.9.608" date="2018-10-24">
+    <release version="0.9.610" date="2018-10-28">
       <description>
         <ul>
-         <li>Fix hang while updating collection</li>
-         <li>Fix an issue with multiple disc albums</li>
+         <li>Fix broken MTP sync on GNOME 3.30</li>
+         <li>Fix a crash about album discs</li>
+         <li>Fix broken radio view</li>
         </ul>
       </description>
     </release>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/container.py 
new/lollypop-0.9.610/lollypop/container.py
--- old/lollypop-0.9.609/lollypop/container.py  2018-10-25 14:37:37.000000000 
+0200
+++ new/lollypop-0.9.610/lollypop/container.py  2018-10-28 15:27:23.000000000 
+0100
@@ -776,11 +776,11 @@
         self.__stop_current_view()
         radios = Radios()
         radio_ids = radios.get_ids()
+        view = RadiosView(radios)
         if radio_ids:
-            view = RadiosView(radios)
             view.populate(radio_ids)
         else:
-            view = MessageView(_("No favorite radios"))
+            view.show_warning()
         view.show()
         return view
 
@@ -822,7 +822,7 @@
                 if child is not None:
                     child.destroy()
                 del self.__devices[dev.id]
-            break
+                break
 
     def __show_donation(self):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/objects.py 
new/lollypop-0.9.610/lollypop/objects.py
--- old/lollypop-0.9.609/lollypop/objects.py    2018-10-25 14:37:37.000000000 
+0200
+++ new/lollypop-0.9.610/lollypop/objects.py    2018-10-28 15:27:23.000000000 
+0100
@@ -234,14 +234,6 @@
         if artist_ids:
             self.artist_ids = artist_ids
 
-    def merge_discs(self):
-        """
-            Merge discs into one
-        """
-        tracks = self.tracks
-        self._discs = [Disc(self, 0)]
-        self._discs[0].set_tracks(tracks)
-
     def move_track(self, track, index):
         """
             Move track to index
@@ -252,6 +244,14 @@
             self._tracks.remove(track)
             self._tracks.insert(index, track)
 
+    def merge_discs(self):
+        """
+            Merge discs into one
+        """
+        tracks = self.tracks
+        self._discs = [Disc(self, 0)]
+        self._discs[0].set_tracks(tracks)
+
     def set_tracks(self, tracks):
         """
             Set album tracks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/progressbar.py 
new/lollypop-0.9.610/lollypop/progressbar.py
--- old/lollypop-0.9.609/lollypop/progressbar.py        2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/progressbar.py        2018-10-28 
15:27:23.000000000 +0100
@@ -10,12 +10,13 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
-from gi.repository import Gtk
+from gi.repository import Gtk, GLib
 
 
 class ProgressBar(Gtk.ProgressBar):
     """
-        A smart progress bar
+        A smart/smooth FIFO progress bar
+        Many objects can register and must call set_fraction(1) to unregister
     """
 
     def __init__(self):
@@ -24,6 +25,8 @@
         """
         Gtk.ProgressBar.__init__(self)
         self.__callers = []
+        self.__fraction = 0.0
+        self.__progress_running = False
 
     def add(self, caller):
         """
@@ -36,13 +39,48 @@
     def set_fraction(self, fraction, caller):
         """
             Set fraction if caller is on top.
+            @param fraction as float
+            @param caller as object
         """
         if not self.__callers:
             return
         if caller == self.__callers[0]:
             self.show()
-            Gtk.ProgressBar.set_fraction(self, fraction)
-            if fraction == 1:
-                self.__callers.remove(caller)
-                self.hide()
-                Gtk.ProgressBar.set_fraction(self, 0.0)
+            self.__fraction = fraction
+            if not self.__progress_running:
+                self.__progress_running = True
+                self.__progress_update(caller)
+
+#######################
+# PRIVATE             #
+#######################
+    def __reset(self, caller):
+        """
+            Reset and hide progressbar
+            @param caller as object
+        """
+        self.hide()
+        self.__fraction = 0.0
+        Gtk.ProgressBar.set_fraction(self, 0.0)
+        self.__progress_running = False
+        self.__callers.remove(caller)
+
+    def __progress_update(self, caller):
+        """
+            Update progressbar smoothly
+            @param caller as object
+        """
+        if caller != self.__callers[0]:
+            self.__progress_running = False
+            return
+        current = self.get_fraction()
+        if self.__fraction < 1:
+            progress = (self.__fraction - current) / 10
+        else:
+            progress = 0.01
+        if current < self.__fraction:
+            Gtk.ProgressBar.set_fraction(self, current + progress)
+        if current < 1.0:
+            GLib.timeout_add(10, self.__progress_update, caller)
+        else:
+            GLib.timeout_add(1000, self.__reset, caller)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/search.py 
new/lollypop-0.9.610/lollypop/search.py
--- old/lollypop-0.9.609/lollypop/search.py     2018-10-25 14:37:37.000000000 
+0200
+++ new/lollypop-0.9.610/lollypop/search.py     2018-10-28 15:27:23.000000000 
+0100
@@ -180,6 +180,7 @@
         for key in album_tracks.keys():
             (album, tracks, score) = album_tracks[key]
             album.set_tracks(tracks)
+            album.merge_discs()
             albums.append((score, album, True))
         albums.sort(key=lambda tup: tup[0], reverse=True)
         return albums
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/sync_mtp.py 
new/lollypop-0.9.610/lollypop/sync_mtp.py
--- old/lollypop-0.9.609/lollypop/sync_mtp.py   2018-10-25 14:37:37.000000000 
+0200
+++ new/lollypop-0.9.610/lollypop/sync_mtp.py   2018-10-28 15:27:23.000000000 
+0100
@@ -10,7 +10,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
-from gi.repository import GLib, Gio, Gst
+from gi.repository import GLib, Gio, Gst, GObject
 
 from time import sleep
 from re import match
@@ -35,21 +35,21 @@
         loaded before entering the scope and saving it when exiting.
     """
 
-    def __init__(self, base_uri):
+    def __init__(self):
         """
             Constructor for MtpSyncDb
-            @param base_uri as str
         """
-        self.__base_uri = base_uri
-        self.__db_uri = self.__base_uri + "/lollypop-sync.db"
         self.__encoder = "convert_none"
         self.__normalize = False
         self.__metadata = {}
 
-    def load_db(self):
+    def load(self, base_uri):
         """
             Loads the metadata db from the MTP device
+            @param base_uri as str
         """
+        self.__base_uri = base_uri
+        self.__db_uri = self.__base_uri + "/lollypop-sync.db"
         Logger.debug("MtpSyncDb::__load_db()")
         try:
             dbfile = Gio.File.new_for_uri(self.__db_uri)
@@ -67,27 +67,28 @@
                     Logger.info("MtpSyncDb::__load_db():"
                                 " unknown sync db version")
         except Exception as e:
-            Logger.error("MtpSyncDb::load_db(): %s" % e)
+            Logger.error("MtpSyncDb::load(): %s" % e)
 
-    def save_db(self):
+    def save(self):
         """
             Saves the metadata db to the MTP device
         """
-        Logger.debug("MtpSyncDb::__save_db()")
-        jsondb = json.dumps({"version": 1,
+        try:
+            Logger.debug("MtpSyncDb::__save()")
+            jsondb = json.dumps(
+                            {"version": 1,
                              "encoder": self.__encoder,
                              "normalize": self.__normalize,
                              "tracks_metadata": [
                                  {"uri": x, "metadata": y}
                                  for x, y in sorted(self.__metadata.items())]})
-        dbfile = Gio.File.new_for_uri(self.__db_uri)
-        ok, _ = dbfile.replace_contents(
-            jsondb.encode("utf-8"),
-            None, False,
-            Gio.FileCreateFlags.REPLACE_DESTINATION,
-            None)
-        if not ok:
-            Logger.error("MtpSyncDb::save_db() failed")
+            dbfile = Gio.File.new_for_uri(self.__db_uri)
+            (tmpfile, stream) = Gio.File.new_tmp()
+            stream.get_output_stream().write_all(jsondb.encode("utf-8"))
+            tmpfile.copy(dbfile, Gio.FileCopyFlags.OVERWRITE, None, None)
+            stream.close()
+        except Exception as e:
+            Logger.error("MtpSyncDb::__save(): %s", e)
 
     def set_encoder(self, encoder):
         """
@@ -160,12 +161,15 @@
         return uri
 
 
-# TODO Rework this code: was designed
-# for playlists and then for albums, it sucks!
-class MtpSync:
+class MtpSync(GObject.Object):
     """
         Synchronisation to MTP devices
     """
+    __gsignals__ = {
+        "sync-progress": (GObject.SignalFlags.RUN_FIRST, None, (float,)),
+        "sync-finished": (GObject.SignalFlags.RUN_FIRST, None, ()),
+        "sync-errors": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
+    }
 
     __ENCODE_START = 'filesrc location="%s" ! decodebin\
                             ! audioconvert\
@@ -195,28 +199,18 @@
         """
             Init MTP synchronisation
         """
-        self._syncing = False
-        self.__errors = False
+        GObject.Object.__init__(self)
+        self.__cancellable = Gio.Cancellable()
+        self.__cancellable.cancel()
         self.__errors_count = 0
-        self._uri = ""
+        self.__last_error = ""
+        self.__uri = None
         self.__total = 0  # Total files to sync
         self.__done = 0   # Handled files on sync
-        self._fraction = 0.0
         self.__copied_art_uris = []
-        self.__mtp_syncdb = MtpSyncDb("")
+        self.__mtp_syncdb = MtpSyncDb()
 
-#######################
-# PROTECTED           #
-#######################
-    def _load_db_uri(self, uri):
-        """
-            Load mtp db at URI
-            @param uri as str
-        """
-        self.__mtp_syncdb = MtpSyncDb(uri)
-        self.__mtp_syncdb.load_db()
-
-    def _check_encoder_status(self, encoder):
+    def check_encoder_status(self, encoder):
         """
             Check encoder status
             @param encoder as str
@@ -226,39 +220,25 @@
             return True
         return False
 
-    def _update_progress(self):
-        """
-            Update progress bar. Do nothing
-        """
-        pass
-
-    def _on_finished(self):
-        """
-            Clean on finished. Do nothing
-        """
-        pass
-
-    def _sync(self):
+    def sync(self, uri):
         """
             Sync playlists with device. If playlists contains Type.NONE,
             sync albums marked as to be synced
+            @param uri as str
         """
         try:
-            self.__in_thread = True
+            self.__uri = uri
+            self.__cancellable.reset()
             self.__convert_bitrate = App().settings.get_value(
                 "convert-bitrate").get_int32()
-            self.__errors = False
             self.__errors_count = 0
             self.__copied_art_uris = []
             # For progress bar
             self.__total = 1
             self.__done = 0
-            self._fraction = 0.0
             playlists = []
 
-            GLib.idle_add(App().window.container.progress.set_fraction,
-                          0, self)
-
+            Logger.debug("Get new tracks before sync")
             # New tracks for synced albums
             album_ids = App().albums.get_synced_ids()
             for album_id in album_ids:
@@ -269,53 +249,71 @@
                 playlists.append(App().playlists.get_name(playlist_id))
                 self.__total += len(App().playlists.get_tracks(playlist_id))
 
+            Logger.debug("Get old tracks")
             # Old tracks
             try:
                 children = self.__get_track_files()
                 self.__total += len(children)
             except:
                 pass
-            GLib.idle_add(self._update_progress)
 
             # Copy new tracks to device
-            if self._syncing:
+            if not self.__cancellable.is_cancelled():
+                Logger.debug("Sync albums")
                 self.__sync_albums()
+                Logger.debug("Sync playlists")
                 self.__sync_playlists(playlist_ids)
 
             # Remove old tracks from device
-            if self._syncing:
+            if not self.__cancellable.is_cancelled():
+                Logger.debug("Remove from device")
                 self.__remove_from_device(playlist_ids)
 
             # Remove empty dirs
-            self.__remove_empty_dirs()
-
-            # Remove old playlists
-            d = Gio.File.new_for_uri(self._uri)
-            infos = d.enumerate_children(
-                "standard::name",
-                Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
-                None)
-            for info in infos:
-                name = info.get_name()
-                if name.endswith(".m3u") and name[:-4] not in playlists:
-                    f = infos.get_child(info)
-                    self.__retry(f.delete, (None,))
+            if not self.__cancellable.is_cancelled():
+                Logger.debug("Remove empty dirs")
+                self.__remove_empty_dirs()
+
+            if not self.__cancellable.is_cancelled():
+                Logger.debug("Remove old playlists")
+                # Remove old playlists
+                d = Gio.File.new_for_uri(self.__uri)
+                infos = d.enumerate_children(
+                    "standard::name",
+                    Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
+                    None)
+                for info in infos:
+                    name = info.get_name()
+                    if name.endswith(".m3u") and name[:-4] not in playlists:
+                        f = infos.get_child(info)
+                        self.__retry(f.delete, (None,))
 
-            d = Gio.File.new_for_uri(self._uri + "/unsync")
+            Logger.debug("Create unsync")
+            d = Gio.File.new_for_uri(self.__uri + "/unsync")
             if not d.query_exists():
                 self.__retry(d.make_directory_with_parents, (None,))
         except Exception as e:
-            Logger.error("DeviceManagerWidget::_sync(): %s" % e)
+            Logger.error("MtpSync::__sync(): %s" % e)
         finally:
-            self.__mtp_syncdb.save_db()
-        self._fraction = 1.0
-        self._syncing = False
-        self.__in_thread = False
-        if self.__errors:
-            GLib.idle_add(self.__on_errors)
+            Logger.debug("Save sync db")
+            self.__mtp_syncdb.save()
+            self.__cancellable.cancel()
+            if self.__errors_count != 0:
+                Logger.debug("Sync errors")
+                GLib.idle_add(self.emit, "sync-errors", self.__last_error)
+            Logger.debug("Sync finished")
+            GLib.idle_add(self.emit, "sync-finished")
 
     @property
-    def mtp_syncdb(self):
+    def cancellable(self):
+        """
+            Get cancellable
+            @return Gio.Cancellable
+        """
+        return self.__cancellable
+
+    @property
+    def db(self):
         """
             Get sync db
         """
@@ -324,39 +322,35 @@
 ############
 # Private  #
 ############
-    def __retry(self, func, args, t=5):
+    def __retry(self, func, args):
         """
-            Try to execute func 5 times
+            Try to execute func and handle errors
             @param func as function
             @param args as tuple
         """
         # Max allowed errors
-        if self.__errors_count > 10:
-            self._syncing = False
-            return
-        if t == 0:
-            self.__errors_count += 1
-            self.__errors = True
+        if self.__errors_count > 5:
+            self.__cancellable.cancel()
             return
         try:
             func(*args)
         except Exception as e:
             Logger.error("MtpSync::_retry(%s, %s): %s" % (func, args, e))
-            for a in args:
-                if isinstance(a, Gio.File):
-                    Logger.info(a.get_uri())
-            sleep(5)
-            self.__retry(func, args, t - 1)
+            self.__last_error = e
+            self.__errors_count += 1
+            sleep(1)
 
     def __remove_empty_dirs(self):
         """
             Delete empty dirs
         """
         to_delete = []
-        dir_uris = [self._uri]
+        dir_uris = [self.__uri]
         try:
             # First get all directories
             while dir_uris:
+                if self.__cancellable.is_cancelled():
+                    break
                 uri = dir_uris.pop(0)
                 d = Gio.File.new_for_uri(uri)
                 infos = d.enumerate_children(
@@ -364,6 +358,8 @@
                     Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
                     None)
                 for info in infos:
+                    if self.__cancellable.is_cancelled():
+                        break
                     if info.get_file_type() == Gio.FileType.DIRECTORY:
                         if info.get_name() != "unsync":
                             f = infos.get_child(info)
@@ -383,6 +379,8 @@
                                 to_delete.append(f.get_uri())
             # Then delete
             for d in to_delete:
+                if self.__cancellable.is_cancelled():
+                    break
                 d = Gio.File.new_for_uri(d)
                 try:
                     d.delete()
@@ -393,15 +391,17 @@
 
     def __get_track_files(self):
         """
-            Return files in self._uri/tracks
+            Return files in self.__uri/tracks
             @return [str]
         """
         children = []
-        dir_uris = [self._uri]
-        d = Gio.File.new_for_uri(self._uri)
+        dir_uris = [self.__uri]
+        d = Gio.File.new_for_uri(self.__uri)
         if not d.query_exists():
             self.__retry(d.make_directory_with_parents, (None,))
         while dir_uris:
+            if self.__cancellable.is_cancelled():
+                break
             try:
                 uri = dir_uris.pop(0)
                 d = Gio.File.new_for_uri(uri)
@@ -410,6 +410,8 @@
                     Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
                     None)
                 for info in infos:
+                    if self.__cancellable.is_cancelled():
+                        break
                     if info.get_file_type() == Gio.FileType.DIRECTORY:
                         if info.get_name() != "unsync":
                             f = infos.get_child(info)
@@ -430,22 +432,18 @@
             @param track as Track
             @return (str, str, str, bool)
         """
-        if not self._syncing:
-            self._fraction = 1.0
-            self.__in_thread = False
-            return
         Logger.debug("MtpSync::__sync_track_id(): %s" % track.uri)
         album_name = escape(track.album_name.lower())
         is_compilation = track.album.artist_ids[0] == Type.COMPILATIONS
         if is_compilation:
             artists = None
             on_device_album_uri = "%s/%s" %\
-                                  (self._uri,
+                                  (self.__uri,
                                    album_name)
         else:
             artists = escape(", ".join(track.album.artists).lower())
             on_device_album_uri = "%s/%s_%s" %\
-                                  (self._uri,
+                                  (self.__uri,
                                    artists,
                                    album_name)
 
@@ -471,7 +469,7 @@
         # Check extension, if not mp3, convert
         m = match(r".*(\.[^.]*)", track.uri)
         ext = m.group(1)
-        convert_ext = self.__EXTENSION[self.mtp_syncdb.encoder]
+        convert_ext = self.__EXTENSION[self.__mtp_syncdb.encoder]
         if convert_ext is not None and ext != convert_ext:
             convertion_needed = True
             track_name = track_name.replace(ext, convert_ext)
@@ -500,7 +498,8 @@
                     bus.add_signal_watch()
                     bus.connect("message::eos", self.__on_bus_eos)
                     self.__encoding = True
-                    while self.__encoding and self._syncing:
+                    while self.__encoding and\
+                            not self.__cancellable.is_cancelled():
                         sleep(1)
                     bus.disconnect_by_func(self.__on_bus_eos)
                     pipeline.set_state(Gst.State.PAUSED)
@@ -523,7 +522,8 @@
         else:
             self.__done += 1
         self.__done += 1
-        self._fraction = self.__done / self.__total
+        GLib.idle_add(self.emit, "sync-progress",
+                      self.__done / self.__total)
         return (track_name, artists, album_name, is_compilation)
 
     def __sync_albums(self):
@@ -533,6 +533,8 @@
         album_ids = App().albums.get_synced_ids()
         for album_id in album_ids:
             for track_id in App().albums.get_track_ids(album_id):
+                if self.__cancellable.is_cancelled():
+                    return
                 self.__sync_track_id(Track(track_id))
 
     def __sync_playlists(self, playlist_ids):
@@ -541,6 +543,8 @@
             @param playlist_ids as [int]
         """
         for playlist_id in playlist_ids:
+            if self.__cancellable.is_cancelled():
+                break
             m3u = None
             stream = None
             playlist = App().playlists.get_name(playlist_id)
@@ -561,6 +565,8 @@
             track_ids = App().playlists.get_track_ids(playlist_id)
             # Start copying
             for track_id in track_ids:
+                if self.__cancellable.is_cancelled():
+                    break
                 if track_id is None:
                     continue
                 track = Track(track_id)
@@ -583,7 +589,7 @@
             if m3u is not None:
                 playlist = escape(playlist)
                 dst = Gio.File.new_for_uri(
-                    self._uri + "/" + playlist + ".m3u")
+                    self.__uri + "/" + playlist + ".m3u")
                 self.__retry(m3u.move,
                              (dst, Gio.FileCopyFlags.OVERWRITE, None, None))
 
@@ -602,18 +608,18 @@
             track_ids += App().playlists.get_track_ids(playlist_id)
         # Get tracks uris
         for track_id in track_ids:
-            if not self._syncing:
-                self._fraction = 1.0
-                self.__in_thread = False
+            if self.__cancellable.is_cancelled():
+                break
+            if self.__cancellable.is_cancelled():
                 return
             track = Track(track_id)
             album_name = escape(track.album_name.lower())
             if track.album.artist_ids[0] == Type.COMPILATIONS:
-                on_device_album_uri = "%s/%s" % (self._uri,
+                on_device_album_uri = "%s/%s" % (self.__uri,
                                                  album_name)
             else:
                 artists = escape(", ".join(track.album.artists).lower())
-                on_device_album_uri = "%s/%s_%s" % (self._uri,
+                on_device_album_uri = "%s/%s_%s" % (self.__uri,
                                                     artists,
                                                     album_name)
             f = Gio.File.new_for_uri(track.uri)
@@ -621,7 +627,7 @@
             # Check extension, if converted, remove
             m = match(r".*(\.[^.]*)", track.uri)
             ext = m.group(1)
-            convert_ext = self.__EXTENSION[self.mtp_syncdb.encoder]
+            convert_ext = self.__EXTENSION[self.__mtp_syncdb.encoder]
             if convert_ext is not None and ext != convert_ext:
                 track_name = track_name.replace(ext, convert_ext)
             dst_uri = "%s/%s" % (on_device_album_uri, track_name)
@@ -633,9 +639,7 @@
 
         # Delete file on device and not in playlists
         for uri in on_mtp_files:
-            if not self._syncing:
-                self._fraction = 1.0
-                self.__in_thread = False
+            if self.__cancellable.is_cancelled():
                 return
             Logger.debug("MtpSync::__remove_from_device(): %s" % uri)
             if uri not in track_uris and uri not in self.__copied_art_uris:
@@ -645,7 +649,8 @@
                 self.__retry(to_delete.delete, (None,))
                 self.__mtp_syncdb.delete_uri(uri)
             self.__done += 1
-            self._fraction = self.__done / self.__total
+            GLib.idle_add(self.emit, "sync-progress",
+                          self.__done / self.__total)
 
     def __convert(self, src, dst):
         """
@@ -659,17 +664,17 @@
             src_path = src.get_path().replace("\\", "\\\\\\")
             dst_path = dst.get_path().replace("\\", "\\\\\\")
             pipeline_str = self.__ENCODE_START % src_path
-            if self.mtp_syncdb.normalize:
+            if self.__mtp_syncdb.normalize:
                 pipeline_str += self.__NORMALIZE
-            if self.mtp_syncdb.encoder in ["convert_vorbis", "convert_aac"]:
+            if self.__mtp_syncdb.encoder in ["convert_vorbis", "convert_aac"]:
                 convert_bitrate = self.__convert_bitrate * 1000
             else:
                 convert_bitrate = self.__convert_bitrate
             try:
-                pipeline_str += self.__ENCODERS[self.mtp_syncdb.encoder] %\
+                pipeline_str += self.__ENCODERS[self.__mtp_syncdb.encoder] %\
                     convert_bitrate
             except:
-                pipeline_str += self.__ENCODERS[self.mtp_syncdb.encoder]
+                pipeline_str += self.__ENCODERS[self.__mtp_syncdb.encoder]
             pipeline_str += self.__ENCODE_END % dst_path
             pipeline = Gst.parse_launch(pipeline_str)
             pipeline.set_state(Gst.State.PLAYING)
@@ -685,9 +690,3 @@
             @param message as Gst.Message
         """
         self.__encoding = False
-
-    def __on_errors(self):
-        """
-            Show something to the user. Do nothing.
-        """
-        pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/view_albums_list.py 
new/lollypop-0.9.610/lollypop/view_albums_list.py
--- old/lollypop-0.9.609/lollypop/view_albums_list.py   2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/view_albums_list.py   2018-10-28 
15:27:23.000000000 +0100
@@ -261,6 +261,12 @@
 #######################
 # PROTECTED           #
 #######################
+    def _on_populated(self):
+        """
+            Populate remaining discs
+        """
+        if len(self.discs) > 0:
+            TracksView.populate(self)
 
 #######################
 # PRIVATE             #
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/view_device.py 
new/lollypop-0.9.610/lollypop/view_device.py
--- old/lollypop-0.9.609/lollypop/view_device.py        2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/view_device.py        2018-10-28 
15:27:23.000000000 +0100
@@ -84,8 +84,14 @@
         self.__syncing_btn.set_label(_("Synchronize %s") % "")
         builder.connect_signals(self)
         self.__device_widget = DeviceManagerWidget(self)
-        self.__device_widget.connect("sync-finished", self.__on_sync_finished)
+        self.__device_widget.mtp_sync.connect("sync-finished",
+                                              self.__on_sync_finished)
+        self.__device_widget.mtp_sync.connect("sync-errors",
+                                              self.__on_sync_errors)
         self.__device_widget.show()
+        self.__infobar = builder.get_object("infobar")
+        self.__error_label = builder.get_object("error_label")
+
         grid = builder.get_object("device")
         self.add(grid)
         self.add(self._scrolled)
@@ -123,7 +129,7 @@
             Check if lollypop is syncing
             @return bool
         """
-        return self.__device_widget.is_syncing()
+        return not self.__device_widget.mtp_sync.cancellable.is_cancelled()
 
     @property
     def device(self):
@@ -136,6 +142,17 @@
 #######################
 # PROTECTED           #
 #######################
+    def _on_infobar_response(self, infobar, response_id):
+        """
+            Hide infobar
+            @param widget as Gtk.Infobar
+            @param reponse id as int
+        """
+        if response_id == Gtk.ResponseType.CLOSE:
+            self.__infobar.set_revealed(False)
+            # WTF?
+            GLib.timeout_add(300, self.__infobar.hide)
+
     def _on_destroy(self, widget):
         """
             Remove running timeout
@@ -151,8 +168,8 @@
             Start synchronisation
             @param widget as Gtk.Button
         """
-        if self.__device_widget.is_syncing():
-            self.__device_widget.cancel_sync()
+        if not self.__device_widget.mtp_sync.cancellable.is_cancelled():
+            self.__device_widget.mtp_sync.cancellable.cancel()
         elif not App().window.container.progress.is_visible():
             self.__memory_combo.hide()
             self.__syncing_btn.set_label(_("Cancel synchronization"))
@@ -191,11 +208,20 @@
         self.__device.uri = uri
 
     def stop(self):
-        """
-            Stop syncing
-        """
         pass
 
+    def __on_sync_errors(self, mtp_sync, error):
+        """
+            Show information bar with error message
+            @param mtp_sync as MtpSync
+            @param error as str
+        """
+        error_text = error or _("Unknown error while syncing,"
+                                " try to reboot your device")
+        self.__error_label.set_text(error_text)
+        self.__infobar.show()
+        self.__infobar.set_revealed(True)
+
     def __on_sync_finished(self, device_widget):
         """
             Restore widgets state
@@ -212,7 +238,7 @@
         """
         # Just update device widget if already populated
         if self.__memory_combo.get_active_text() is not None:
-            if not self.__device_widget.is_syncing():
+            if self.__device_widget.mtp_sync.cancellable.is_cancelled():
                 self.__device_widget.populate(self.__selected_ids)
             return
         for text in text_list:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/view_radios.py 
new/lollypop-0.9.610/lollypop/view_radios.py
--- old/lollypop-0.9.609/lollypop/view_radios.py        2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/view_radios.py        2018-10-28 
15:27:23.000000000 +0100
@@ -12,11 +12,14 @@
 
 from gi.repository import Gtk
 
+from gettext import gettext as _
+
 from lollypop.view_flowbox import FlowBoxView
 from lollypop.widgets_radio import RadioWidget
 from lollypop.pop_radio import RadioPopover
 from lollypop.pop_tunein import TuneinPopover
 from lollypop.controller_view import ViewController
+from lollypop.view import MessageView
 
 
 class RadiosView(FlowBoxView, ViewController):
@@ -44,6 +47,15 @@
         self.connect_current_changed_signal()
         self.connect_artwork_changed_signal("radio")
 
+    def show_warning(self):
+        """
+            Show a message to user
+        """
+        self._scrolled.hide()
+        view = MessageView(_("No favorite radios"))
+        view.show()
+        self.add(view)
+
 #######################
 # PROTECTED           #
 #######################
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/view_tracks.py 
new/lollypop-0.9.610/lollypop/view_tracks.py
--- old/lollypop-0.9.609/lollypop/view_tracks.py        2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/view_tracks.py        2018-10-28 
15:27:23.000000000 +0100
@@ -18,7 +18,7 @@
 from lollypop.widgets_track import TracksWidget, TrackRow
 from lollypop.objects import Album
 from lollypop.logger import Logger
-from lollypop.define import App, Type, ResponsiveType, Shuffle, NextContext
+from lollypop.define import App, Type, Shuffle, NextContext
 
 
 class TracksView:
@@ -58,10 +58,6 @@
         self._tracks_widget_left = {}
         self._tracks_widget_right = {}
 
-        if self._responsive_type in [ResponsiveType.DND,
-                                     ResponsiveType.LIST,
-                                     ResponsiveType.SEARCH]:
-            self._album.merge_discs()
         # Discs to load, will be emptied
         self.__discs = list(self._album.discs)
         for disc in self.__discs:
@@ -274,6 +270,14 @@
         return boxes
 
     @property
+    def discs(self):
+        """
+            Get widget discs
+            @return [Discs]
+        """
+        return self.__discs
+
+    @property
     def is_populated(self):
         """
             Return True if populated
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/widgets_album.py 
new/lollypop-0.9.610/lollypop/widgets_album.py
--- old/lollypop-0.9.609/lollypop/widgets_album.py      2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/widgets_album.py      2018-10-28 
15:27:23.000000000 +0100
@@ -78,7 +78,7 @@
         """
             True if overlayed or going to be
         """
-        return self._show_overlay or self._timeout_id is not None
+        return self._show_overlay
 
 #######################
 # PROTECTED           #
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/lollypop/widgets_device.py 
new/lollypop-0.9.610/lollypop/widgets_device.py
--- old/lollypop-0.9.609/lollypop/widgets_device.py     2018-10-25 
14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/lollypop/widgets_device.py     2018-10-28 
15:27:23.000000000 +0100
@@ -10,7 +10,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 
-from gi.repository import Gtk, GLib, Gio, GObject, Pango
+from gi.repository import Gtk, GLib, Gio, Pango
 
 from gettext import gettext as _
 
@@ -18,19 +18,13 @@
 from lollypop.cellrenderer import CellRendererAlbum
 from lollypop.define import App, Type
 from lollypop.objects import Album
-from lollypop.logger import Logger
 from lollypop.helper_task import TaskHelper
 
 
-# FIXME This class should not inherit MtpSync
-# TODO Rework MtpSync code
-class DeviceManagerWidget(Gtk.Bin, MtpSync):
+class DeviceManagerWidget(Gtk.Bin):
     """
         Widget for synchronize mtp devices
     """
-    __gsignals__ = {
-        "sync-finished": (GObject.SignalFlags.RUN_FIRST, None, ())
-    }
 
     def __init__(self, parent):
         """
@@ -39,10 +33,11 @@
             @param parent as Gtk.Widget
         """
         Gtk.Bin.__init__(self)
-        MtpSync.__init__(self)
+        self.__mtp_sync = MtpSync()
+        self.__mtp_sync.connect("sync-finished", self.__on_sync_finished)
+        self.__mtp_sync.connect("sync-progress", self.__on_sync_progress)
         self.__parent = parent
-        self.__stop = False
-        self._uri = None
+        self.__uri = None
 
         self.__builder = Gtk.Builder()
         self.__builder.add_from_resource(
@@ -50,7 +45,6 @@
         widget = self.__builder.get_object("widget")
         self.connect("size-allocate", self.__on_size_allocate, widget)
 
-        self.__error_label = self.__builder.get_object("error-label")
         self.__switch_albums = self.__builder.get_object("switch_albums")
 
         self.__menu_items = self.__builder.get_object("menu-items")
@@ -65,9 +59,6 @@
 
         self.add(widget)
 
-        self.__infobar = self.__builder.get_object("infobar")
-        self.__infobar_label = self.__builder.get_object("infobarlabel")
-
         renderer0 = Gtk.CellRendererToggle()
         renderer0.set_property("activatable", True)
         renderer0.connect("toggled", self.__on_item_toggled)
@@ -92,10 +83,8 @@
         """
             Populate playlists or albums for selected_ids
             @param selected_ids as [int]
-            @thread safe
         """
         self.__model.clear()
-        self.__stop = False
         if selected_ids[0] == Type.PLAYLISTS:
             playlists = [(Type.LOVED, App().playlists.LOVED)]
             playlists += App().playlists.get()
@@ -124,17 +113,17 @@
             self.__switch_albums.disconnect_by_func(self.__on_albums_state_set)
         except:
             pass
-        self._load_db_uri(uri)
-        encoder = self.mtp_syncdb.encoder
-        normalize = self.mtp_syncdb.normalize
+        self.__mtp_sync.db.load(uri)
+        encoder = self.__mtp_sync.db.encoder
+        normalize = self.__mtp_sync.db.normalize
         self.__switch_normalize = self.__builder.get_object("switch_normalize")
         self.__switch_normalize.set_sensitive(False)
         self.__switch_normalize.set_active(normalize)
         self.__builder.get_object(encoder).set_active(True)
-        for encoder in self._GST_ENCODER.keys():
-            if not self._check_encoder_status(encoder):
+        for encoder in self.__mtp_sync._GST_ENCODER.keys():
+            if not self.__mtp_sync.check_encoder_status(encoder):
                 self.__builder.get_object(encoder).set_sensitive(False)
-        self._uri = uri
+        self.__uri = uri
         d = Gio.File.new_for_uri(uri)
         try:
             if not d.query_exists():
@@ -142,52 +131,35 @@
         except:
             pass
 
-    def is_syncing(self):
-        """
-            @return True if syncing
-        """
-        return self._syncing
-
     def sync(self):
         """
             Start synchronisation
         """
-        self._syncing = True
         App().window.container.progress.add(self)
         self.__menu.set_sensitive(False)
         self.__view.set_sensitive(False)
         helper = TaskHelper()
-        helper.run(self._sync)
+        helper.run(self.__mtp_sync.sync, self.__uri)
 
-    def cancel_sync(self):
+    @property
+    def uri(self):
         """
-            Cancel synchronisation
+            Get device uri
+            @return str
         """
-        self._syncing = False
+        return self.__uri
 
-#######################
-# PROTECTED           #
-#######################
-    def _update_progress(self):
+    @property
+    def mtp_sync(self):
         """
-            Update progress bar smoothly
+            MtpSync object
+            @return MtpSync
         """
-        current = App().window.container.progress.get_fraction()
-        if self._syncing:
-            progress = (self._fraction - current) / 10
-        else:
-            progress = 0.01
-        if current < self._fraction:
-            App().window.container.progress.set_fraction(current + progress,
-                                                         self)
-        if current < 1.0:
-            if progress < 0.0002:
-                GLib.timeout_add(500, self._update_progress)
-            else:
-                GLib.timeout_add(25, self._update_progress)
-        else:
-            GLib.timeout_add(1000, self._on_finished)
+        return self.__mtp_sync
 
+#######################
+# PROTECTED           #
+#######################
     def _pop_menu(self, button):
         """
             Popup menu for album
@@ -202,35 +174,6 @@
         popover.add(self.__menu_items)
         popover.popup()
 
-    def _on_finished(self):
-        """
-            Emit finished signal
-        """
-        MtpSync._on_finished(self)
-        App().window.container.progress.set_fraction(1.0, self)
-        self.__view.set_sensitive(True)
-        self.__menu.set_sensitive(True)
-        self.emit("sync-finished")
-
-    def _on_errors(self):
-        """
-            Show information bar with error message
-        """
-        MtpSync._on_errors(self)
-        error_text = _("Unknown error while syncing,"
-                       " try to reboot your device")
-        try:
-            d = Gio.File.new_for_uri(self._uri)
-            info = d.query_filesystem_info("filesystem::free")
-            free = info.get_attribute_as_string("filesystem::free")
-
-            if free is None or int(free) < 1024:
-                error_text = _("No free space available on device")
-        except Exception as e:
-            Logger.error("DeviceWidget::_on_errors(): %s" % e)
-        self.__error_label.set_text(error_text)
-        self.__infobar.show()
-
     def _on_convert_toggled(self, widget):
         """
             Save option
@@ -240,11 +183,11 @@
             encoder = widget.get_name()
             if encoder == "convert_none":
                 self.__switch_normalize.set_sensitive(False)
-                self.mtp_syncdb.set_normalize(False)
-                self.mtp_syncdb.set_encoder("convert_none")
+                self.__mtp_sync.db.set_normalize(False)
+                self.__mtp_sync.db.set_encoder("convert_none")
             else:
                 self.__switch_normalize.set_sensitive(True)
-                self.mtp_syncdb.set_encoder(encoder)
+                self.__mtp_sync.db.set_encoder(encoder)
 
     def _on_normalize_state_set(self, widget, state):
         """
@@ -252,16 +195,7 @@
             @param widget as Gtk.Switch
             @param state as bool
         """
-        self.mtp_syncdb.set_normalize(state)
-
-    def _on_response(self, infobar, response_id):
-        """
-            Hide infobar
-            @param widget as Gtk.Infobar
-            @param reponse id as int
-        """
-        if response_id == Gtk.ResponseType.CLOSE:
-            self.__infobar.hide()
+        self.__mtp_sync.db.set_normalize(state)
 
 #######################
 # PRIVATE             #
@@ -272,7 +206,7 @@
             @param playlists as [(int, str)]
             @param synced_ids as [int]
         """
-        if playlists and not self.__stop:
+        if playlists:
             playlist = playlists.pop(0)
             selected = playlist[0] in synced_ids
             self.__model.append([selected, playlist[1], playlist[0]])
@@ -283,7 +217,7 @@
             Append albums
             @param albums as [int]
         """
-        if albums and not self.__stop:
+        if albums:
             album = Album(albums.pop(0))
             synced = App().albums.get_synced(album.id)
             # Do not sync youtube albums
@@ -346,3 +280,20 @@
         """
         width = max(400, allocation.width / 2)
         child_widget.set_size_request(width, -1)
+
+    def __on_sync_progress(self, mtp_sync, value):
+        """
+            Update progress bar
+            @param mtp_sync as MtpSync
+            @param value as float
+        """
+        App().window.container.progress.set_fraction(value, self)
+
+    def __on_sync_finished(self, mtp_sync):
+        """
+            Emit finished signal
+            @param mtp_sync as MtpSync
+        """
+        App().window.container.progress.set_fraction(1.0, self)
+        self.__view.set_sensitive(True)
+        self.__menu.set_sensitive(True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/meson.build 
new/lollypop-0.9.610/meson.build
--- old/lollypop-0.9.609/meson.build    2018-10-25 14:37:37.000000000 +0200
+++ new/lollypop-0.9.610/meson.build    2018-10-28 15:27:23.000000000 +0100
@@ -1,5 +1,5 @@
 project('lollypop',
-  version: '0.9.609',
+  version: '0.9.610',
   meson_version: '>= 0.40.0'
 )
 i18n = import('i18n')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lollypop-0.9.609/subprojects/po/ar.po 
new/lollypop-0.9.610/subprojects/po/ar.po
--- old/lollypop-0.9.609/subprojects/po/ar.po   2018-10-25 14:37:37.000000000 
+0200
+++ new/lollypop-0.9.610/subprojects/po/ar.po   2018-10-28 15:27:23.000000000 
+0100
@@ -8,8 +8,8 @@
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2018-10-09 18:44+0200\n"
-"PO-Revision-Date: 2018-10-23 16:22+0000\n"
-"Last-Translator: AbouZakaria <[email protected]>\n"
+"PO-Revision-Date: 2018-10-25 05:23+0000\n"
+"Last-Translator: mohammadA <[email protected]>\n"
 "Language-Team: Arabic <https://hosted.weblate.org/projects/gnumdk/lollypop/";
 "ar/>\n"
 "Language: ar\n"
@@ -30,7 +30,7 @@
 
 #: ../data/org.gnome.Lollypop.gschema.xml:33
 msgid "Window position"
-msgstr "موقع النافذة"
+msgstr "موضع النافذة"
 
 #: ../data/org.gnome.Lollypop.gschema.xml:34
 msgid "Window position (x and y)."
@@ -301,11 +301,12 @@
 
 #: ../data/AboutDialog.ui.in:63 ../lollypop/container.py:834
 msgid "Liberapay"
-msgstr "ليبرابي Liberapay"
+msgstr "ليبرابَي"
 
 #: ../data/AboutDialog.ui.in:73 ../lollypop/container.py:834
+#, fuzzy
 msgid "PayPal"
-msgstr "PayPal"
+msgstr "باي‌بال"
 
 #: ../data/org.gnome.Lollypop.appdata.xml.in:7
 #: ../data/org.gnome.Lollypop.desktop.in:3
@@ -435,15 +436,15 @@
 
 #: ../data/Appmenu.ui:27
 msgid "_Keyboard Shortcuts"
-msgstr "اختصارات _لوحة المفاتيح"
+msgstr "اختصارات لوحة ال_مفاتيح"
 
 #: ../data/Appmenu.ui:31
 msgid "_About"
-msgstr "_حول"
+msgstr "_عن"
 
 #: ../data/Appmenu.ui:35
 msgid "_Quit"
-msgstr "أ_غلق"
+msgstr "_غادر"
 
 #: ../data/ArtistInformation.ui:108 ../lollypop/pop_menu.py:299
 msgid "Show lyrics"
@@ -1326,7 +1327,7 @@
 
 #: ../lollypop/view_playlists.py:205
 msgid "Cancel"
-msgstr "إلغاء"
+msgstr "ألغ"
 
 #: ../lollypop/view_playlists.py:291 ../lollypop/widgets_album_detailed.py:283
 #, python-format


Reply via email to