commit lollypop for openSUSE:Factory
![](https://seccdn.libravatar.org/avatar/e2145bc5cf53dda95c308a3c75e8fef3.jpg?s=120&d=mm&r=g)
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 - antoine.belvire@opensuse.org + +- 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 <zakariakov@gmail.com>\n" +"PO-Revision-Date: 2018-10-25 05:23+0000\n" +"Last-Translator: mohammadA <mohammadAbdulhadi1@gmail.com>\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
participants (1)
-
root