예제 #1
0
    def _on_delete_confirm(self, button):
        """
            Delete tracks after confirmation
            @param button as Gtk.Button
        """
        selection = self.__view.get_selection()
        selected = selection.get_selected_rows()[1]
        rows = []
        for item in selected:
            rows.append(Gtk.TreeRowReference.new(self.__model, item))

        tracks = []
        for row in rows:
            iterator = self.__model.get_iter(row.get_path())
            track = Track(self.__model.get_value(iterator, 3))
            tracks.append(track)
            if self.__playlist_id == Type.LOVED and Lp().lastfm is not None:
                if track.album.artist_id == Type.COMPILATIONS:
                    artist_name = ", ".join(track.artists)
                else:
                    artist_name = ", ".join(track.album.artists)
                helper = TaskHelper()
                helper.run(Lp().lastfm.unlove, artist_name, track.name)
            self.__model.remove(iterator)
        Lp().playlists.remove_tracks(self.__playlist_id, tracks)
        self.__infobar.hide()
        self.__unselectall()
예제 #2
0
 def _on_new_btn_clicked(self, button):
     """
         Create a new playlist based on search
         @param button as Gtk.Button
     """
     helper = TaskHelper()
     helper.run(self.__new_playlist)
예제 #3
0
 def populate(self):
     """
         populate view if needed
     """
     if len(self.__model) == 0:
         helper = TaskHelper()
         helper.run(self.__append_tracks, callback=(self.__append_track,))
예제 #4
0
    def __on_track_moved(self, widget, dst, src, up):
        """
            Move track from src to row
            Recalculate track position
            @param widget as TracksWidget
            @param dst as int
            @param src as int
            @param up as bool
        """
        def update_playlist():
            # Save playlist in db only if one playlist visible
            if len(self.__playlist_ids) == 1 and self.__playlist_ids[0] >= 0:
                Lp().playlists.clear(self.__playlist_ids[0], False)
                tracks = []
                for track_id in self.__tracks_left + self.__tracks_right:
                    tracks.append(Track(track_id))
                Lp().playlists.add_tracks(self.__playlist_ids[0],
                                          tracks,
                                          False)
            if not (set(self.__playlist_ids) -
               set(Lp().player.get_user_playlist_ids())):
                Lp().player.update_user_playlist(self.__tracks_left +
                                                 self.__tracks_right)

        (src_widget, dst_widget, src_index, dst_index) = \
            self.__move_track(dst, src, up)
        self.__update_tracks()
        self.__update_position()
        self.__update_headers()
        self.__tracks_widget_left.update_indexes(1)
        self.__tracks_widget_right.update_indexes(len(self.__tracks_left) + 1)
        helper = TaskHelper()
        helper.run(update_playlist)
예제 #5
0
파일: lastfm.py 프로젝트: lexruee/lollypop
 def __connect(self, full_sync=False):
     """
         Connect service
         @param full_sync as bool
         @thread safe
     """
     if self.__goa is not None or (self.__password != ""
                                   and self.__login != ""):
         self.__is_auth = True
     else:
         self.__is_auth = False
     try:
         self.session_key = ""
         self.__check_for_proxy()
         if self.__goa is not None:
             self.session_key = self.__goa.call_get_access_token_sync(
                 None)[0]
         else:
             skg = SessionKeyGenerator(self)
             self.session_key = skg.get_session_key(username=self.__login,
                                                    password_hash=md5(
                                                        self.__password))
         if full_sync:
             helper = TaskHelper()
             helper.run(self.__populate_loved_tracks)
     except Exception as e:
         debug("LastFM::__connect(): %s" % e)
         self.__is_auth = False
예제 #6
0
파일: pop_menu.py 프로젝트: kublaj/lollypop
 def __remove_from_playlist(self, action, variant, playlist_id):
     """
         Del from playlist
         @param SimpleAction
         @param GVariant
         @param object id as int
         @param is album as bool
         @param playlist id as int
     """
     def remove(playlist_id):
         tracks = []
         if isinstance(self._object, Album):
             track_ids = Lp().albums.get_track_ids(self._object.id,
                                                   self._object.genre_ids,
                                                   self._object.artist_ids)
             for track_id in track_ids:
                 tracks.append(Track(track_id))
         else:
             tracks = [Track(self._object.id)]
         Lp().playlists.remove_tracks(playlist_id, tracks)
         if playlist_id in Lp().player.get_user_playlist_ids():
             Lp().player.update_user_playlist(
                                  Lp().playlists.get_track_ids(playlist_id))
     helper = TaskHelper()
     helper.run(remove, playlist_id)
예제 #7
0
 def get(self, search_items, cancellable, callback):
     """
         Get track for name
         @param search_items as [str]
         @param cancellable as Gio.Cancellable
         @param callback as callback
     """
     helper = TaskHelper()
     helper.run(self.__get, search_items, cancellable, callback=callback)
예제 #8
0
 def cache_artists_info(self):
     """
         Cache info for all artists
     """
     if self.__cache_artists_running:
         return
     self.__cache_artists_running = True
     helper = TaskHelper()
     helper.run(self.__cache_artists_info)
예제 #9
0
 def __update_db(self, action=None, param=None):
     """
         Search for new music
         @param action as Gio.SimpleAction
         @param param as GLib.Variant
     """
     if self.window:
         helper = TaskHelper()
         helper.run(self.art.clean_all_cache)
         self.window.update_db()
예제 #10
0
 def populate(self):
     """
         Populate view with tracks from playlist
     """
     Lp().player.set_radios(self.__radios_manager.get())
     if Lp().player.current_track.id == Type.RADIOS:
         Lp().player.set_next()  # We force next update
         Lp().player.set_prev()  # We force prev update
     helper = TaskHelper()
     helper.run(self.__get_radios, callback=(self.__on_get_radios, ))
예제 #11
0
파일: lastfm.py 프로젝트: lexruee/lollypop
 def now_playing(self, artist, album, title, duration):
     """
         Now playing track
         @param artist as str
         @param title as str
         @param album as str
         @param duration as int
     """
     if get_network_available() and\
        self.__is_auth and Secret is not None:
         helper = TaskHelper()
         helper.run(self.__now_playing, artist, album, title, duration)
예제 #12
0
 def cache_album_art(self, album_id):
     """
         Download album artwork
         @param album id as int
     """
     if album_id in self.__albums_history:
         return
     if get_network_available():
         self.__albums_queue.append(album_id)
         if not self.__in_albums_download:
             helper = TaskHelper()
             helper.run(self.__cache_albums_art)
예제 #13
0
 def connect(self, full_sync=False, callback=None, *args):
     """
         Connect service
         @param full_sync as bool
         @param callback as function
     """
     if self.__goa is not None:
         helper = TaskHelper()
         helper.run(self.__connect, (full_sync, ))
     elif get_network_available():
         from lollypop.helper_passwords import PasswordsHelper
         helper = PasswordsHelper()
         helper.get(self.__name, self.__on_get_password, callback, *args)
예제 #14
0
파일: lastfm.py 프로젝트: lexruee/lollypop
 def do_scrobble(self, artist, album, title, timestamp):
     """
         Scrobble track
         @param artist as str
         @param title as str
         @param album as str
         @param timestamp as int
         @param duration as int
     """
     if get_network_available() and\
        self.__is_auth and Secret is not None:
         helper = TaskHelper()
         helper.run(self.__scrobble, artist, album, title, timestamp)
예제 #15
0
    def save_album_artwork(self, data, album_id):
        """
            Save data for album id
            @param data as bytes
            @param album id as int
        """
        try:
            album = Album(album_id)
            arturi = None
            save_to_tags = Lp().settings.get_value("save-to-tags")
            uri_count = Lp().albums.get_uri_count(album.uri)
            filename = self.get_album_cache_name(album) + ".jpg"
            if save_to_tags:
                helper = TaskHelper()
                helper.run(self.__save_artwork_tags, data, album)

            store_path = self._STORE_PATH + "/" + filename
            if album.uri == "" or is_readonly(album.uri):
                arturi = GLib.filename_to_uri(store_path)
            # Many albums with same path, suffix with artist_album name
            elif uri_count > 1:
                arturi = album.uri + "/" + filename
                favorite_uri = album.uri + "/" + self.__favorite
                favorite = Gio.File.new_for_uri(favorite_uri)
                if favorite.query_exists():
                    favorite.trash()
            else:
                arturi = album.uri + "/" + self.__favorite
            # Save cover to uri
            dst = Gio.File.new_for_uri(arturi)
            if not save_to_tags or dst.query_exists():
                bytes = GLib.Bytes(data)
                stream = Gio.MemoryInputStream.new_from_bytes(bytes)
                bytes.unref()
                pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(
                                                               stream,
                                                               ArtSize.MONSTER,
                                                               ArtSize.MONSTER,
                                                               True,
                                                               None)
                stream.close()
                pixbuf.savev(store_path, "jpeg", ["quality"],
                             [str(Lp().settings.get_value(
                                                "cover-quality").get_int32())])
                dst = Gio.File.new_for_uri(arturi)
                src = Gio.File.new_for_path(store_path)
                src.move(dst, Gio.FileCopyFlags.OVERWRITE, None, None)
                self.clean_album_cache(album)
                GLib.idle_add(self.album_artwork_update, album.id)
        except Exception as e:
            print("Art::save_album_artwork(): %s" % e)
예제 #16
0
 def __on_get_password(self, attributes, password, name, callback, *args):
     """
          Set password label
          @param attributes as {}
          @param password as str
          @param name as str
          @param callback as function
     """
     if attributes is None:
         return
     self.__login = attributes["login"]
     self.__password = password
     if get_network_available():
         helper = TaskHelper()
         helper.run(self.__connect, (), callback, *args)
예제 #17
0
 def __on_drag_data_received(self, widget, context, x, y, data, info, time):
     """
         Import values
         @param widget as Gtk.Widget
         @param context as Gdk.DragContext
         @param x as int
         @param y as int
         @param data as Gtk.SelectionData
         @param info as int
         @param time as int
     """
     from lollypop.collectionimporter import CollectionImporter
     importer = CollectionImporter()
     uris = data.get_text().strip("\n").split("\r")
     task_helper = TaskHelper()
     task_helper.run(importer.add, uris, callback=(self.update_db,))
예제 #18
0
 def _on_map_albums(self, widget):
     """
         Load on map
         @param widget as Gtk.Grid
     """
     self.__jump_button.show()
     if self.__current_track.id is None:
         self.__current_track = Lp().player.current_track
     Lp().settings.set_value("infoswitch", GLib.Variant("s", "albums"))
     view = widget.get_child_at(0, 0)
     if view is None:
         view = CurrentArtistAlbumsView()
         view.set_property("expand", True)
         view.show()
         widget.add(view)
     helper = TaskHelper()
     helper.run(view.populate, self.__current_track)
예제 #19
0
 def reset_history(self):
     """
         Reset history
     """
     # Tracks already played
     self.__history = []
     # Used by shuffle albums to restore playlist before shuffle
     self._albums_backup = []
     # Albums already played
     self.__already_played_albums = []
     # Tracks already played for albums
     self.__already_played_tracks = {}
     # If we have tracks/albums to ignore in party mode, add them
     helper = TaskHelper()
     helper.run(self.__init_party_blacklist)
     # Reset user playlist
     self._user_playlist = []
     self._user_playlist_ids = []
예제 #20
0
    def sync(self):
        """
            Start synchronisation
        """
        self._syncing = True
        Lp().window.progress.add(self)
        self.__menu.set_sensitive(False)
        playlists = []
        if not Lp().settings.get_value("sync-albums"):
            self.__view.set_sensitive(False)
            for item in self.__model:
                if item[0]:
                    playlists.append(item[2])
        else:
            playlists.append(Type.NONE)

        helper = TaskHelper()
        helper.run(self._sync, playlists, self.__switch_mp3.get_active(),
                   self.__switch_normalize.get_active())
예제 #21
0
def set_loved(track_id, loved):
    """
        Add or remove track from loved playlist
        @param track_id
        @param loved Add to loved playlist if `True`; remove if `False`
    """
    if not is_loved(track_id):
        if loved:
            Lp().playlists.add_tracks(Type.LOVED,
                                      [Track(track_id)])
            if Lp().lastfm is not None:
                helper = TaskHelper()
                helper.run(_set_loved_on_lastfm, track_id, True)
    else:
        if not loved:
            Lp().playlists.remove_tracks(Type.LOVED,
                                         [Track(track_id)])
            if Lp().lastfm is not None:
                helper = TaskHelper()
                helper.run(_set_loved_on_lastfm, track_id, False)
예제 #22
0
 def __set_current_object(self, playlist_id, add):
     """
         Add/Remove current object to playlist
         @param playlist id as int
         @param add as bool
     """
     def set(playlist_id, add):
         tracks = []
         if self.__is_album:
             track_ids = Lp().albums.get_track_ids(self.__object_id,
                                                   self.__genre_ids,
                                                   self.__artist_ids)
             for track_id in track_ids:
                 tracks.append(Track(track_id))
         else:
             tracks = [Track(self.__object_id)]
         if add:
             Lp().playlists.add_tracks(playlist_id, tracks)
         else:
             Lp().playlists.remove_tracks(playlist_id, tracks)
     helper = TaskHelper()
     helper.run(set, playlist_id, add)
예제 #23
0
    def __save_album_artwork(self, album, data):
        """
            Save artwork for an album
            @param album as Album
            @param data as bytes
        """
        store_path = "%s/%s.jpg" % (ALBUMS_PATH, album.lp_album_id)
        save_to_tags = App().settings.get_value("save-to-tags")
        # Multiple albums at same path
        uri_count = App().albums.get_uri_count(album.uri)
        art_uri = album.uri + "/" + self.__favorite

        # Save cover to tags
        if save_to_tags:
            helper = TaskHelper()
            helper.run(self.__save_album_artwork_to_tags, album, data)

        # We need to remove favorite if exists
        if uri_count > 1 or save_to_tags:
            f = Gio.File.new_for_uri(art_uri)
            if f.query_exists():
                f.trash()

        # Name file with album information
        if uri_count > 1:
            art_uri = "%s/%s.jpg" % (album.uri, album.lp_album_id)

        if data is None:
            f = Gio.File.new_for_path(store_path)
            fstream = f.replace(None, False,
                                Gio.FileCreateFlags.REPLACE_DESTINATION, None)
            fstream.close()
        else:
            self.save_pixbuf_from_data(store_path, data)
        dst = Gio.File.new_for_uri(art_uri)
        src = Gio.File.new_for_path(store_path)
        src.move(dst, Gio.FileCopyFlags.OVERWRITE, None, None)
        self.clean_album_cache(album)
        self.album_artwork_update(album.id)
예제 #24
0
파일: pop_menu.py 프로젝트: kublaj/lollypop
 def __add_to_playlist(self, action, variant, playlist_id):
     """
         Add to playlist
         @param SimpleAction
         @param GVariant
         @param playlist id as int
     """
     def add(playlist_id):
         tracks = []
         if isinstance(self._object, Album):
             track_ids = Lp().albums.get_track_ids(self._object.id,
                                                   self._object.genre_ids,
                                                   self._object.artist_ids)
             for track_id in track_ids:
                 tracks.append(Track(track_id))
         else:
             tracks = [Track(self._object.id)]
         Lp().playlists.add_tracks(playlist_id, tracks)
         if playlist_id in Lp().player.get_user_playlist_ids():
             Lp().player.update_user_playlist(
                                  Lp().playlists.get_track_ids(playlist_id))
     helper = TaskHelper()
     helper.run(add, playlist_id)
예제 #25
0
 def __on_activate_link(self, link, item):
     """
         Open new uri or just play stream
         @param link as Gtk.LinkButton
         @param item as TuneIn Item
     """
     if item.TYPE == "link":
         self.__scrolled.get_vadjustment().set_value(0.0)
         self.populate(item.URL)
     elif item.TYPE == "audio":
         if get_network_available():
             helper = TaskHelper()
             # Cache for toolbar
             helper.run(Lp().art.copy_uri_to_cache, item.LOGO, item.TEXT,
                        Lp().window.toolbar.artsize)
             # Cache for MPRIS
             helper.run(Lp().art.copy_uri_to_cache, item.LOGO, item.TEXT,
                        ArtSize.BIG)
             # Cache for miniplayer
             helper.run(Lp().art.copy_uri_to_cache, item.LOGO, item.TEXT,
                        WindowSize.SMALL)
         Lp().player.load_external(item.URL, item.TEXT)
         Lp().player.play_this_external(item.URL)
     return True
예제 #26
0
    def __upgrade_46(self, db):
        """
            Populate lp_album_id/lp_track_id
        """
        queue = LOLLYPOP_DATA_PATH + "/queue.bin"
        try:
            f = Gio.File.new_for_path(queue)
            f.delete(None)
        except:
            pass
        from lollypop.database_albums import AlbumsDatabase
        from lollypop.database_tracks import TracksDatabase
        from lollypop.utils import get_lollypop_album_id, get_lollypop_track_id
        albums = AlbumsDatabase(db)
        tracks = TracksDatabase(db)

        def do_migration(dialog, label, progress):
            GLib.idle_add(label.set_text,
                          _("Please wait while Lollypop is updating albums"))
            album_ids = albums.get_ids([], [], StorageType.ALL, True)
            album_ids += albums.get_compilation_ids([], StorageType.ALL, True)
            count = len(album_ids)
            i = 0
            for album_id in album_ids:
                if i % 10 == 0:
                    GLib.idle_add(progress.set_fraction, i / count)
                name = albums.get_name(album_id)
                artists = ";".join(albums.get_artists(album_id))
                lp_album_id = get_lollypop_album_id(name, artists)
                albums.set_lp_album_id(album_id, lp_album_id)
                i += 1

            track_ids = tracks.get_ids(StorageType.ALL, True)
            count = len(track_ids)
            i = 0
            GLib.idle_add(label.set_text,
                          _("Please wait while Lollypop is updating tracks"))
            for track_id in track_ids:
                if i % 10 == 0:
                    GLib.idle_add(progress.set_fraction, i / count)
                name = tracks.get_name(track_id)
                artists = ";".join(tracks.get_artists(track_id))
                album_name = tracks.get_album_name(track_id)
                lp_track_id = get_lollypop_track_id(name, artists, album_name)
                tracks.set_lp_track_id(track_id, lp_track_id)
                i += 1
            GLib.idle_add(dialog.destroy)

        dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.NONE)
        progress = Gtk.ProgressBar.new()
        progress.show()
        label = Gtk.Label.new()
        label.show()
        grid = Gtk.Grid.new()
        grid.set_orientation(Gtk.Orientation.VERTICAL)
        grid.set_row_spacing(10)
        grid.show()
        grid.add(label)
        grid.add(progress)
        dialog.set_image(grid)
        helper = TaskHelper()
        helper.run(do_migration, dialog, label, progress)
        dialog.run()
예제 #27
0
class Application(Gtk.Application, ApplicationActions):
    """
        Lollypop application:
            - Handle appmenu
            - Handle command line
            - Create main window
    """
    def __init__(self, version, data_dir):
        """
            Create application
            @param version as str
            @param data_dir as str
        """
        Gtk.Application.__init__(
            self,
            application_id="org.gnome.Lollypop",
            flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.__version = version
        self.__data_dir = data_dir
        self.set_property("register-session", True)
        signal(SIGINT, lambda a, b: self.quit())
        signal(SIGTERM, lambda a, b: self.quit())
        # Set main thread name
        # We force it to current python 3.6 name, to be sure in case of
        # change in python
        current_thread().setName("MainThread")
        (self.__proxy_host, self.__proxy_port) = init_proxy_from_gnome()
        GLib.setenv("PULSE_PROP_media.role", "music", True)
        GLib.setenv("PULSE_PROP_application.icon_name", "org.gnome.Lollypop",
                    True)
        # Ideally, we will be able to delete this once Flatpak has a solution
        # for SSL certificate management inside of applications.
        if GLib.file_test("/app", GLib.FileTest.EXISTS):
            paths = [
                "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/cert.pem",
                "/etc/ssl/cert.pem"
            ]
            for path in paths:
                if GLib.file_test(path, GLib.FileTest.EXISTS):
                    GLib.setenv("SSL_CERT_FILE", path, True)
                    break
        self.cursors = {}
        self.debug = False
        self.shown_sidebar_tooltip = False
        self.__window = None
        self.__fs_window = None
        settings = Gio.Settings.new("org.gnome.desktop.interface")
        self.animations = settings.get_value("enable-animations").get_boolean()
        GLib.set_application_name("Lollypop")
        GLib.set_prgname("lollypop")
        self.add_main_option("play-ids", b"a", GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING, "Play ids", None)
        self.add_main_option("debug", b"d", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Debug Lollypop", None)
        self.add_main_option("set-rating", b"r", GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING, "Rate the current track",
                             None)
        self.add_main_option("play-pause", b"t", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Toggle playback", None)
        self.add_main_option("stop", b"s", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Stop playback", None)
        self.add_main_option("next", b"n", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Go to next track", None)
        self.add_main_option("prev", b"p", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Go to prev track", None)
        self.add_main_option("emulate-phone", b"e", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Emulate a Librem phone",
                             None)
        self.add_main_option("version", b"v", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Lollypop version", None)
        self.connect("command-line", self.__on_command_line)
        self.connect("handle-local-options", self.__on_handle_local_options)
        self.connect("activate", self.__on_activate)
        self.connect("shutdown", lambda a: self.__save_state())
        self.register(None)
        if self.get_is_remote():
            Gdk.notify_startup_complete()
        if GLib.environ_getenv(GLib.get_environ(), "DEBUG_LEAK") is not None:
            import gc
            gc.set_debug(gc.DEBUG_LEAK)

    def init(self):
        """
            Init main application
        """
        self.settings = Settings.new()
        # Mount enclosing volume as soon as possible
        uris = self.settings.get_music_uris()
        try:
            for uri in uris:
                if uri.startswith("file:/"):
                    continue
                f = Gio.File.new_for_uri(uri)
                f.mount_enclosing_volume(Gio.MountMountFlags.NONE, None, None,
                                         None)
        except Exception as e:
            Logger.error("Application::init(): %s" % e)

        cssProviderFile = Gio.File.new_for_uri(
            "resource:///org/gnome/Lollypop/application.css")
        cssProvider = Gtk.CssProvider()
        cssProvider.load_from_file(cssProviderFile)
        screen = Gdk.Screen.get_default()
        styleContext = Gtk.StyleContext()
        styleContext.add_provider_for_screen(screen, cssProvider,
                                             Gtk.STYLE_PROVIDER_PRIORITY_USER)
        self.db = Database()
        self.cache = CacheDatabase()
        self.playlists = Playlists()
        self.albums = AlbumsDatabase(self.db)
        self.artists = ArtistsDatabase(self.db)
        self.genres = GenresDatabase(self.db)
        self.tracks = TracksDatabase(self.db)
        self.player = Player()
        self.inhibitor = Inhibitor()
        self.scanner = CollectionScanner()
        self.notify = NotificationManager()
        self.task_helper = TaskHelper()
        self.art_helper = ArtHelper()
        self.art = Art()
        self.art.update_art_size()
        self.ws_director = DirectorWebService()
        self.ws_director.start()
        if not self.settings.get_value("disable-mpris"):
            from lollypop.mpris import MPRIS
            MPRIS(self)

        settings = Gtk.Settings.get_default()
        self.__gtk_dark = settings.get_property(
            "gtk-application-prefer-dark-theme")
        if not self.__gtk_dark:
            dark = self.settings.get_value("dark-ui")
            settings.set_property("gtk-application-prefer-dark-theme", dark)
        ApplicationActions.__init__(self)
        monitor = Gio.NetworkMonitor.get_default()
        if monitor.get_network_available() and\
                not monitor.get_network_metered() and\
                self.settings.get_value("recent-youtube-dl"):
            self.task_helper.run(install_youtube_dl)

    def do_startup(self):
        """
            Init application
        """
        Gtk.Application.do_startup(self)
        Handy.init()
        if self.__window is None:
            from lollypop.window import Window
            self.init()
            self.__window = Window()
            self.__window.connect("delete-event", self.__hide_on_delete)
            self.__window.setup()
            self.__window.show()
            self.player.restore_state()

    def quit(self, vacuum=False):
        """
            Quit Lollypop
            @param vacuum as bool
        """
        self.__window.container.stop()
        self.__window.hide()
        if not self.ws_director.stop():
            GLib.timeout_add(100, self.quit, vacuum)
            return
        if self.settings.get_value("save-state"):
            self.__window.container.stack.save_history()
        # Then vacuum db
        if vacuum:
            self.__vacuum()
            self.art.clean_artwork()
        Gio.Application.quit(self)
        if GLib.environ_getenv(GLib.get_environ(), "DEBUG_LEAK") is not None:
            import gc
            gc.collect()
            for x in gc.garbage:
                s = str(x)
                print(type(x), "\n  ", s)

    def fullscreen(self):
        """
            Go fullscreen
        """
        def on_destroy(window):
            self.__fs_window = None
            self.__window.show()

        if self.__fs_window is None:
            from lollypop.fullscreen import FullScreen
            self.__fs_window = FullScreen()
            self.__fs_window.delayed_init()
            self.__fs_window.show()
            self.__fs_window.connect("destroy", on_destroy)
            self.__window.hide()
        else:
            self.__fs_window.destroy()

    @property
    def proxy_host(self):
        """
            Get proxy host
            @return str
        """
        return self.__proxy_host

    @property
    def proxy_port(self):
        """
            Get proxy port
            @return int
        """
        return self.__proxy_port

    @property
    def devices(self):
        """
            Get available devices
            Merge connected and known
            @return [str]
        """
        devices = self.__window.toolbar.end.devices_popover.devices
        devices += list(self.settings.get_value("devices"))
        result = []
        # Do not use set() + filter() because we want to keep order
        for device in devices:
            if device not in result and device != "":
                result.append(device)
        return result

    @property
    def is_fullscreen(self):
        """
            Return True if application is fullscreen
        """
        return self.__fs_window is not None

    @property
    def main_window(self):
        """
            Get main window
        """
        return self.__window

    @property
    def window(self):
        """
            Get current application window
            @return Gtk.Window
        """
        if self.__fs_window is not None:
            return self.__fs_window
        else:
            return self.__window

    @property
    def data_dir(self):
        """
            Get data dir
            @return str
        """
        return self.__data_dir

    @property
    def gtk_application_prefer_dark_theme(self):
        """
            Return default gtk value
            @return bool
        """
        return self.__gtk_dark

    @property
    def version(self):
        """
            Get Lollypop version
            @return srt
        """
        return self.__version

#######################
# PRIVATE             #
#######################

    def __save_state(self):
        """
            Save player state
        """
        if self.settings.get_value("save-state"):
            if self.player.current_track.id is None or\
                    self.player.current_track.storage_type &\
                    StorageType.EPHEMERAL:
                track_id = None
            else:
                track_id = self.player.current_track.id
                # Save albums context
                try:
                    with open(LOLLYPOP_DATA_PATH + "/Albums.bin", "wb") as f:
                        dump(self.player.albums, f)
                except Exception as e:
                    Logger.error("Application::__save_state(): %s" % e)
            dump(track_id, open(LOLLYPOP_DATA_PATH + "/track_id.bin", "wb"))
            dump([self.player.is_playing, self.player.is_party],
                 open(LOLLYPOP_DATA_PATH + "/player.bin", "wb"))
            dump(self.player.queue,
                 open(LOLLYPOP_DATA_PATH + "/queue.bin", "wb"))
            if self.player.current_track.id is not None:
                position = self.player.position
            else:
                position = 0
            dump(position, open(LOLLYPOP_DATA_PATH + "/position.bin", "wb"))
        self.player.stop_all()

    def __vacuum(self):
        """
            VACUUM DB
        """
        try:
            if self.scanner.is_locked():
                self.scanner.stop()
                GLib.idle_add(self.__vacuum)
                return
            SqlCursor.add(self.db)
            self.tracks.del_non_persistent(False)
            self.tracks.clean(False)
            self.albums.clean(False)
            self.artists.clean(False)
            self.genres.clean(False)
            SqlCursor.remove(self.db)
            self.cache.clean(True)

            with SqlCursor(self.db) as sql:
                sql.isolation_level = None
                sql.execute("VACUUM")
                sql.isolation_level = ""
            with SqlCursor(self.playlists) as sql:
                sql.isolation_level = None
                sql.execute("VACUUM")
                sql.isolation_level = ""
        except Exception as e:
            Logger.error("Application::__vacuum(): %s" % e)

    def __parse_uris(self, playlist_uris, audio_uris):
        """
            Parse playlist uris
            @param playlist_uris as [str]
            @param audio_uris as [str]
        """
        from gi.repository import TotemPlParser
        playlist_uri = playlist_uris.pop(0)
        parser = TotemPlParser.Parser.new()
        parser.connect("entry-parsed", self.__on_entry_parsed, audio_uris)
        parser.parse_async(playlist_uri, True, None, self.__on_parse_finished,
                           playlist_uris, audio_uris)

    def __on_handle_local_options(self, app, options):
        """
            Handle local options
            @param app as Gio.Application
            @param options as GLib.VariantDict
        """
        if options.contains("version"):
            print("Lollypop %s" % self.__version)
            exit(0)
        return -1

    def __on_command_line(self, app, app_cmd_line):
        """
            Handle command line
            @param app as Gio.Application
            @param options as Gio.ApplicationCommandLine
        """
        try:
            args = app_cmd_line.get_arguments()
            options = app_cmd_line.get_options_dict()
            if options.contains("debug"):
                self.debug = True
            if options.contains("set-rating"):
                value = options.lookup_value("set-rating").get_string()
                try:
                    value = min(max(0, int(value)), 5)
                    if self.player.current_track.id is not None:
                        self.player.current_track.set_rate(value)
                except Exception as e:
                    Logger.error("Application::__on_command_line(): %s", e)
                    pass
            elif options.contains("play-pause"):
                self.player.play_pause()
            elif options.contains("stop"):
                self.player.stop()
            elif options.contains("play-ids"):
                try:
                    value = options.lookup_value("play-ids").get_string()
                    ids = value.split(";")
                    albums = []
                    for id in ids:
                        if id[0:2] == "a:":
                            album = Album(int(id[2:]))
                            self.player.add_album(album)
                            albums.append(album)
                        else:
                            track = Track(int(id[2:]))
                            self.player.add_album(track.album)
                            albums.append(track.album)
                    if albums and albums[0].tracks:
                        self.player.load(albums[0].tracks[0])
                except Exception as e:
                    Logger.error("Application::__on_command_line(): %s", e)
                    pass
            elif options.contains("next"):
                self.player.next()
            elif options.contains("prev"):
                self.player.prev()
            elif options.contains("emulate-phone"):
                self.__window.toolbar.end.devices_popover.add_fake_phone()
            elif len(args) > 1:
                audio_uris = []
                playlist_uris = []
                for uri in args[1:]:
                    parsed = urlparse(uri)
                    if parsed.scheme not in ["http", "https"]:
                        try:
                            uri = GLib.filename_to_uri(uri)
                        except:
                            pass
                        f = Gio.File.new_for_uri(uri)
                        # Try ./filename
                        if not f.query_exists():
                            uri = GLib.filename_to_uri(
                                "%s/%s" % (GLib.get_current_dir(), uri))
                            print(uri)
                            f = Gio.File.new_for_uri(uri)
                    file_type = get_file_type(uri)
                    if file_type == FileType.PLS:
                        playlist_uris.append(uri)
                    else:
                        audio_uris.append(uri)
                if playlist_uris:
                    self.__parse_uris(playlist_uris, audio_uris)
                else:
                    self.__on_parse_finished(None, None, [], audio_uris)
            elif self.__window is not None:
                if not self.__window.is_visible():
                    self.__window.present()
                    emit_signal(self.player, "status-changed")
                    emit_signal(self.player, "current-changed")
            Gdk.notify_startup_complete()
        except Exception as e:
            Logger.error("Application::__on_command_line(): %s", e)
        return 0

    def __on_parse_finished(self, source, result, playlist_uris, audio_uris):
        """
            Play stream
            @param source as None
            @param result as Gio.AsyncResult
            @param uris as ([str], [str])
        """
        if playlist_uris:
            self.__parse_uris(playlist_uris, audio_uris)
        else:
            self.scanner.update(ScanType.EXTERNAL, audio_uris)

    def __on_entry_parsed(self, parser, uri, metadata, audio_uris):
        """
            Add playlist entry to external files
            @param parser as TotemPlParser.Parser
            @param uri as str
            @param metadata as GLib.HastTable
            @param audio_uris as str
        """
        audio_uris.append(uri)

    def __hide_on_delete(self, widget, event):
        """
            Hide window
            @param widget as Gtk.Widget
            @param event as Gdk.Event
        """
        # Quit if background mode is on but player is off
        if not self.settings.get_value("background-mode") or\
                not self.player.is_playing:
            GLib.idle_add(self.quit, True)
        return widget.hide_on_delete()

    def __on_activate(self, application):
        """
            Call default handler
            @param application as Gio.Application
        """
        self.__window.present()