Example #1
0
    def on_searchYouTube(self, text):
#        self.entryYoutubeSearch.set_sensitive(False)
#        self.treeviewYouTubeChooser.set_sensitive(False)
#        self.btnSearchYouTube.set_sensitive(False)
        util.run_in_background(
            lambda: self.thread_func(TabType.Youtube, text)
        )
    def _concatenate_videos(self, episodes):
        episodes = self._get_sorted_episode_list(episodes)

        # TODO: Show file list dialog for reordering

        out_filename = self._get_save_filename()
        if out_filename is None:
            return

        list_filename = os.path.join(os.path.dirname(out_filename),
                '.' + os.path.splitext(os.path.basename(out_filename))[0] + '.txt')

        with open(list_filename, 'w') as fp:
            fp.write('\n'.join("file '%s'\n" % episode.local_filename(create=False)
                for episode in episodes))

        indicator = ProgressIndicator(_('Concatenating video files'),
                _('Writing %(filename)s') % {
                    'filename': os.path.basename(out_filename)
                }, False, self.gpodder.get_dialog_parent())

        def convert():
            ffmpeg = subprocess.Popen(['ffmpeg', '-f', 'concat', '-nostdin', '-y',
                '-i', list_filename, '-c', 'copy', out_filename])
            result = ffmpeg.wait()
            util.delete_file(list_filename)
            util.idle_add(lambda: indicator.on_finished())
            util.idle_add(lambda: self.gpodder.show_message(
                _('Videos successfully converted') if result == 0 else
                _('Error converting videos'),
                _('Concatenation result'), important=True))

        util.run_in_background(convert, True)
Example #3
0
    def updateAllPodcasts(self):
        if not self.request_connection():
            self.setBusy(False)
            return

        self.setBusy(True)

        # Process episode actions received from gpodder.net
        def merge_proc(self):
            self.startProgress.emit(_('Merging episode actions...'))

            def find_episode(podcast_url, episode_url, counter):
                counter['x'] += 1
                self.startProgress.emit(_('Merging episode actions (%d)')
                        % counter['x'])
                return self.find_episode(podcast_url, episode_url)

            try:
                d = {'x': 0} # Used to "remember" the counter inside find_episode
                self.root.mygpo_client.process_episode_actions(lambda x, y:
                        find_episode(x, y, d))
            finally:
                self.endProgress.emit()

        util.run_in_background(lambda: merge_proc(self))

        for podcast in self.root.podcast_model.get_objects():
            if not podcast.pause_subscription and not podcast.qupdating:
                if podcast.qtitle != 'All episodes':
                    self.updating_podcasts += 1
                podcast.qupdate(finished_callback=self.finished_update)

        self.setBusy(self.updating_podcasts > 0)
Example #4
0
    def updateAllPodcasts(self):
        if not self.request_connection():
            return

        # Process episode actions received from gpodder.net
        def merge_proc(self):
            self.root.start_progress(_('Merging episode actions...'))

            def find_episode(podcast_url, episode_url, counter):
                counter['x'] += 1
                self.root.start_progress(_('Merging episode actions (%d)')
                        % counter['x'])
                return self.find_episode(podcast_url, episode_url)

            try:
                d = {'x': 0} # Used to "remember" the counter inside find_episode
                self.root.mygpo_client.process_episode_actions(lambda x, y:
                        find_episode(x, y, d))
            finally:
                self.root.end_progress()

        util.run_in_background(lambda: merge_proc(self))

        for podcast in self.root.podcast_model.get_objects():
            if not podcast.pause_subscription:
                podcast.qupdate(finished_callback=self.update_subset_stats)
Example #5
0
    def addSubscriptions(self, urls):
        def not_yet_subscribed(url):
            for podcast in self.root.podcast_model.get_objects():
                if isinstance(podcast, model.EpisodeSubsetView):
                    continue

                if podcast.url == url:
                    logger.info('Already subscribed: %s', url)
                    return False

            return True

        urls = map(util.normalize_feed_url, urls)
        urls = filter(not_yet_subscribed, urls)

        def subscribe_proc(self, urls):
            self.root.start_progress(_('Adding podcasts...'))
            try:
                for idx, url in enumerate(urls):
                    print idx, url
                    self.root.start_progress(_('Adding podcasts...') + ' (%d/%d)' % (idx, len(urls)))
                    try:
                        podcast = self.root.model.load_podcast(url=url, create=True,
                                max_episodes=self.root.config.max_episodes_per_feed)
                        podcast.save()
                        self.root.insert_podcast(model.QPodcast(podcast))
                    except Exception, e:
                        logger.warn('Cannot add pocast: %s', e)
                        # XXX: Visual feedback in the QML UI
            finally:
                self.root.end_progress()

        util.run_in_background(lambda: subscribe_proc(self, urls))
Example #6
0
    def qdownload(self, config, finished_callback=None):
        # Avoid starting the same download twice
        if self.download_task is not None:
            return

        # Initialize the download task here, so that the
        # UI will be updated as soon as possible
        self._wrapper_manager.add_active_episode(self)
        self._qt_download_progress = 0.
        task = download.DownloadTask(self._episode, config)
        task.status = download.DownloadTask.QUEUED
        self.changed.emit()

        def t(self):
            def cb(progress):
                if progress > self._qt_download_progress + .01 or progress == 1:
                    self._qt_download_progress = progress
                    self.changed.emit()
            task.add_progress_callback(cb)
            task.run()
            task.recycle()
            task.removed_from_list()
            self.changed.emit()
            self.source_url_changed.emit()

            # Make sure the single channel is updated (main view)
            self._podcast.changed.emit()

            # Make sure that "All episodes", etc.. are updated
            if finished_callback is not None:
                finished_callback()

            self._wrapper_manager.remove_active_episode(self)

        util.run_in_background(lambda: t(self), True)
Example #7
0
    def spawn_threads(self, force_start=False):
        """Spawn new worker threads if necessary

        If force_start is True, forcefully spawn a thread and
        let it process at least one episodes, even if a download
        limit is in effect at the moment.
        """
        with self.worker_threads_access:
            if not len(self.tasks):
                return

            if force_start or len(self.worker_threads) == 0 or \
                    len(self.worker_threads) < self._config.max_downloads or \
                    not self._config.max_downloads_enabled:
                # We have to create a new thread here, there's work to do
                logger.info('Starting new worker thread.')

                # The new worker should process at least one task (the one
                # that we want to forcefully start) if force_start is True.
                if force_start:
                    minimum_tasks = 1
                else:
                    minimum_tasks = 0

                worker = DownloadQueueWorker(self.tasks, self.__exit_callback,
                        self.__continue_check_callback, minimum_tasks)
                self.worker_threads.append(worker)
                util.run_in_background(worker.run)
Example #8
0
 def download_opml_file(self, url):
     self.entryURL.set_text(url)
     self.btnDownloadOpml.set_sensitive(False)
     self.entryURL.set_sensitive(False)
     self.btnOK.set_sensitive(False)
     self.treeviewChannelChooser.set_sensitive(False)
     util.run_in_background(self.thread_func)
     util.run_in_background(lambda: self.thread_func(1))
Example #9
0
 def delete():
     episode.delete_episode()
     self.episodeUpdated.emit(episode.id)
     self.update_subset_stats()
     self.root.mygpo_client.on_delete([episode])
     self.root.mygpo_client.flush()
     self.removeQueuedEpisode.emit(episode)
     util.run_in_background(self.root.episode_model.sort)
Example #10
0
    def getModel(self, tab):
        if tab in self._models:
            if self._models[tab] == None:
                util.run_in_background(
                    lambda: self.thread_func(tab)
                )
                return None

            return self._models[tab]
        else:
            return None
Example #11
0
        def delete():
            for episode in episodes:
                if not episode.qarchive:
                    episode.delete_episode()
                    self.episodeUpdated.emit(episode.id)
            self.update_subset_stats()
            self.root.mygpo_client.on_delete(episodes)
            self.root.mygpo_client.flush()
            for episode in episodes:
                self.removeQueuedEpisode.emit(episode)

            util.run_in_background(self.root.episode_model.sort)
Example #12
0
    def myGpoUploadList(self):
        def upload_proc(self):
            self.root.start_progress(_('Uploading subscriptions...'))

            try:
                try:
                    self.root.mygpo_client.set_subscriptions([podcast.url
                        for podcast in self.root.podcast_model.get_podcasts()])
                except Exception, e:
                    self.root.show_message('\n'.join((_('Error on upload:'), unicode(e))))
            finally:
                self.root.end_progress()

        util.run_in_background(lambda: upload_proc(self))
Example #13
0
    def flattr_url(self, payment_url):
        """Flattr an object given its Flattr payment URL

        Returns a tuple (success, message):

            success ... True if the item was Flattr'd
            message ... The success or error message
        """
        params = {
            'url': payment_url
        }

        content = self.request(self.FLATTR_URL, data=params)

        if '_gpodder_statuscode' in content:
            status_code = content['_gpodder_statuscode']
            if status_code == 401:
                return (False, _('Not enough means to flattr'))
            elif status_code == 404:
                return (False, _('Item does not exist on Flattr'))
            elif status_code == 403:
                return (True, _('Already flattred or own item'))
            else:
                return (False, _('Invalid request'))
                
        if '_gpodder_no_connection' in content:
            if not self._store.get(FlattrAction, url=payment_url):
                flattr_action = FlattrAction(payment_url)
                self._store.save(flattr_action)
            return (False, _('No internet connection'))
        
        if self._worker_thread is None:        
            self._worker_thread = util.run_in_background(lambda: self._worker_proc(), True)

        return (True, content.get('description', _('No description')))
Example #14
0
    def __init__(self, args, gpodder_core, dbus_bus_name):
        QmlCommonQtPodder.__init__(self, args, gpodder_core, dbus_bus_name)

        self.controller = Controller(self)
        self.media_buttons_handler = helper.MediaButtonsHandler()
        self.tracker_miner_config = helper.TrackerMinerConfig()
        self.podcast_model = qml.model.gPodderPodcastListModel()
        self.episode_model = desktopModel.gPodderEpisodeListModel(self.config)
        self.last_episode = None

        # A dictionary of episodes that are currently active
        # in some way (i.e. playing back or downloading)
        self.active_episode_wrappers = {}

        # Add the cover art image provider
        self.cover_provider = images.LocalCachedImageProvider()

        # TODO: clear it, only for debug
        try:
            self._create_qml_gui('MainWindow.qml')

            # Proxy to the "main" QML mainWindow
            # for direct access to Qt Properties
            self.main = QObjectProxy(self.mainWindow.object)

        except AttributeError as ex:
            print "main: ", type(ex)
            print ex.args
            return

        self.main.podcastModel = self._create_model_filter(self.podcast_model)
        self.main.episodeModel = self._create_model_filter(self.episode_model)

        self.mainWindow.setVisible(True)

        self.do_start_progress.connect(self.on_start_progress)
        self.do_end_progress.connect(self.on_end_progress)
        self.do_show_message.connect(self.on_show_message)

        podcasts = self.load_podcasts()

        self.resumable_episodes = None
        self.do_offer_download_resume.connect(self.on_offer_download_resume)
        util.run_in_background(self.find_partial_downloads(podcasts))
Example #15
0
    def __spawn_threads(self):
        """Spawn new worker threads if necessary
        """
        with self.worker_threads_access:
            work_count = self.tasks.available_work_count()
            if self._config.max_downloads_enabled:
                # always allow at least 1 download
                max_downloads = max(int(self._config.max_downloads), 1)
                spawn_limit = max_downloads - len(self.worker_threads)
            else:
                spawn_limit = self._config.limit.downloads.concurrent_max
            logger.info('%r tasks to do, can start at most %r threads', work_count, spawn_limit)
            for i in range(0, min(work_count, spawn_limit)):
                # We have to create a new thread here, there's work to do
                logger.info('Starting new worker thread.')

                worker = DownloadQueueWorker(self.tasks, self.__exit_callback,
                        self.__continue_check_callback)
                self.worker_threads.append(worker)
                util.run_in_background(worker.run)
Example #16
0
    def request_cover(self, channel, custom_url=None, avoid_downloading=False):
        """
        Sends an asynchronous request to download a
        cover for the specific channel.

        After the cover has been downloaded, the
        "cover-available" signal will be sent with
        the channel url and new cover as pixbuf.

        If you specify a custom_url, the cover will
        be downloaded from the specified URL and not
        taken from the channel metadata.

        The optional parameter "avoid_downloading",
        when true, will make sure we return only
        already-downloaded covers and return None
        when we have no cover on the local disk.
        """
        logger.debug("cover download request for %s", channel.url)
        util.run_in_background(lambda: self.__get_cover(channel, custom_url, True, avoid_downloading))
Example #17
0
File: my.py Project: Mortal/gpodder
    def flush(self, now=False):
        if not self.can_access_webservice():
            logger.warn('Flush requested, but sync disabled.')
            return

        if self._worker_thread is None or now:
            if now:
                logger.debug('Flushing NOW.')
            else:
                logger.debug('Flush requested.')
            self._worker_thread = util.run_in_background(lambda: self._worker_proc(now), True)
        else:
            logger.debug('Flush requested, already waiting.')
Example #18
0
    def __init__(self, args, gpodder_core, dbus_bus_name):
        QmlCommonQtPodder.__init__(self, args, gpodder_core, dbus_bus_name)

        self.episodeUpdated.connect(self.on_episode_updated)
        self.setEpisodeListModel.connect(self.on_set_episode_list_model)

        self.view = DeclarativeView()
        self.view.closing.connect(self.on_quit)
        self.view.setResizeMode(QDeclarativeView.SizeRootObjectToView)

        self.controller = Controller(self)
        self.media_buttons_handler = helper.MediaButtonsHandler()
        self.tracker_miner_config = helper.TrackerMinerConfig()
        self.podcast_model = qml.model.gPodderPodcastListModel()
        self.episode_model = mobileModel.gPodderEpisodeListModel(self.config, self)
        self.last_episode = None

        # A dictionary of episodes that are currently active
        # in some way (i.e. playing back or downloading)
        self.active_episode_wrappers = {}

        engine = self.view.engine()

        # Add the cover art image provider
        self.cover_provider = images.LocalCachedImageProvider()
        engine.addImageProvider('cover', self.cover_provider)

        root_context = self.view.rootContext()
        root_context.setContextProperty('controller', self.controller)
        root_context.setContextProperty('configProxy', self.config_proxy)
        root_context.setContextProperty('mediaButtonsHandler',
                self.media_buttons_handler)
        root_context.setContextProperty('trackerMinerConfig',
                self.tracker_miner_config)

        # Load the QML UI (this could take a while...)
        self.view.setSource(QUrl.fromLocalFile(QML('main_default.qml')))

        # Proxy to the "main" QML object for direct access to Qt Properties
        self.main = QObjectProxy(self.view.rootObject().property('main'))

        self.main.podcastModel = self.podcast_model
        self.main.episodeModel = self.episode_model

        self.view.setWindowTitle('gPodder')

        if gpodder.ui.harmattan:
            self.view.showFullScreen()
        else:
            # On the Desktop, scale to fit my small laptop screen..
            desktop = self.app.desktop()
            if desktop.height() < 1000:
                FACTOR = .8
                self.view.scale(FACTOR, FACTOR)
                size = self.view.size()
                size *= FACTOR
                self.view.resize(size)
            self.view.show()

        self.do_start_progress.connect(self.on_start_progress)
        self.do_end_progress.connect(self.on_end_progress)
        self.do_show_message.connect(self.on_show_message)

        podcasts = self.load_podcasts()

        self.resumable_episodes = None
        self.do_offer_download_resume.connect(self.on_offer_download_resume)
        util.run_in_background(self.find_partial_downloads(podcasts))
Example #19
0
 def _on_config_changed(self, name, old_value, new_value):
     if name == 'ui.qml.state.episode_list_filter':
         self._filter = new_value
         util.run_in_background(self.sort)
Example #20
0
    def on_synchronize_episodes(self, channels, episodes=None, force_played=True):
        device = sync.open_device(self)

        if device is None:
            return self._show_message_unconfigured()

        if not device.open():
            return self._show_message_cannot_open()
        else:
            # Only set if device is configured and opened successfully
            self.device = device

        if episodes is None:
            force_played = False
            episodes = self._filter_sync_episodes(channels)

        def check_free_space():
            # "Will we add this episode to the device?"
            def will_add(episode):
                # If already on-device, it won't take up any space
                if device.episode_on_device(episode):
                    return False

                # Might not be synced if it's played already
                if (not force_played and
                        self._config.device_sync.skip_played_episodes):
                    return False

                # In all other cases, we expect the episode to be
                # synchronized to the device, so "answer" positive
                return True

            # "What is the file size of this episode?"
            def file_size(episode):
                filename = episode.local_filename(create=False)
                if filename is None:
                    return 0
                return util.calculate_size(str(filename))

            # Calculate total size of sync and free space on device
            total_size = sum(file_size(e) for e in episodes if will_add(e))
            free_space = max(device.get_free_space(), 0)

            if total_size > free_space:
                title = _('Not enough space left on device')
                message = (_('Additional free space required: %(required_space)s\nDo you want to continue?') %
               {'required_space': util.format_filesize(total_size - free_space)})
                if not self.show_confirmation(message, title):
                    device.cancel()
                    device.close()
                    return

            # Finally start the synchronization process
            @util.run_in_background
            def sync_thread_func():
                self.enable_download_list_update()
                device.add_sync_tasks(episodes, force_played=force_played)

        # This function is used to remove files from the device
        def cleanup_episodes():
            # 'skip_played_episodes' must be used or else all the
            # played tracks will be copied then immediately deleted
            if (self._config.device_sync.delete_played_episodes and
                    self._config.device_sync.skip_played_episodes):
                all_episodes = self._filter_sync_episodes(channels,
                        only_downloaded=False)
                episodes_on_device = device.get_all_tracks()
                for local_episode in all_episodes:
                    episode = device.episode_on_device(local_episode)
                    if episode is None:
                        continue

                    if local_episode.state == gpodder.STATE_DELETED:
                        logger.info('Removing episode from device: %s',
                                episode.title)
                        device.remove_track(episode)

            # When this is done, start the callback in the UI code
            util.idle_add(check_free_space)

        # This will run the following chain of actions:
        #  1. Remove old episodes (in worker thread)
        #  2. Check for free space (in UI thread)
        #  3. Sync the device (in UI thread)
        util.run_in_background(cleanup_episodes)
Example #21
0
    def on_synchronize_episodes(self,
                                channels,
                                episodes=None,
                                force_played=True):
        device = sync.open_device(self)

        if device is None:
            return self._show_message_unconfigured()

        if not device.open():
            return self._show_message_cannot_open()
        else:
            # Only set if device is configured and opened successfully
            self.device = device

        if episodes is None:
            force_played = False
            episodes = self._filter_sync_episodes(channels)

        def check_free_space():
            # "Will we add this episode to the device?"
            def will_add(episode):
                # If already on-device, it won't take up any space
                if device.episode_on_device(episode):
                    return False

                # Might not be synced if it's played already
                if (not force_played
                        and self._config.device_sync.skip_played_episodes):
                    return False

                # In all other cases, we expect the episode to be
                # synchronized to the device, so "answer" positive
                return True

            # "What is the file size of this episode?"
            def file_size(episode):
                filename = episode.local_filename(create=False)
                if filename is None:
                    return 0
                return util.calculate_size(str(filename))

            # Calculate total size of sync and free space on device
            total_size = sum(file_size(e) for e in episodes if will_add(e))
            free_space = max(device.get_free_space(), 0)

            if total_size > free_space:
                title = _('Not enough space left on device')
                message = (_(
                    'Additional free space required: %(required_space)s\nDo you want to continue?'
                ) % {
                    'required_space':
                    util.format_filesize(total_size - free_space)
                })
                if not self.show_confirmation(message, title):
                    device.cancel()
                    device.close()
                    return

            #enable updating of UI
            self.enable_download_list_update()

            #Update device playlists
            #General approach is as follows:

            #When a episode is downloaded and synched, it is added to the
            #standard playlist for that podcast which is then written to
            #the device.

            #After the user has played that episode on their device, they
            #can delete that episode from their device.

            #At the next sync, gPodder will then compare the standard
            #podcast-specific playlists on the device (as written by
            #gPodder during the last sync), with the episodes on the
            #device.If there is an episode referenced in the playlist
            #that is no longer on the device, gPodder will assume that
            #the episode has already been synced and subsequently deleted
            #from the device, and will hence mark that episode as deleted
            #in gPodder. If there are no playlists, nothing is deleted.

            #At the next sync, the playlists will be refreshed based on
            #the downloaded, undeleted episodes in gPodder, and the
            #cycle begins again...

            def resume_sync(episode_urls, channel_urls, progress):
                if progress is not None:
                    progress.on_finished()

                #rest of sync process should continue here
                self.commit_changes_to_database()
                for current_channel in self.channels:
                    #only sync those channels marked for syncing
                    if (self._config.device_sync.device_type == 'filesystem'
                            and current_channel.sync_to_mp3_player
                            and self._config.device_sync.playlists.create):

                        #get playlist object
                        playlist = gPodderDevicePlaylist(
                            self._config, current_channel.title)
                        #need to refresh episode list so that
                        #deleted episodes aren't included in playlists
                        episodes_for_playlist = sorted(
                            current_channel.get_episodes(
                                gpodder.STATE_DOWNLOADED),
                            key=lambda ep: ep.published)
                        #don't add played episodes to playlist if skip_played_episodes is True
                        if self._config.device_sync.skip_played_episodes:
                            episodes_for_playlist = [
                                ep for ep in episodes_for_playlist if ep.is_new
                            ]
                        playlist.write_m3u(episodes_for_playlist)

                #enable updating of UI
                self.enable_download_list_update()

                if (self._config.device_sync.device_type == 'filesystem'
                        and self._config.device_sync.playlists.create):
                    title = _('Update successful')
                    message = _(
                        'The playlist on your MP3 player has been updated.')
                    self.notification(message, title)

                # Finally start the synchronization process
                @util.run_in_background
                def sync_thread_func():
                    device.add_sync_tasks(
                        episodes,
                        force_played=force_played,
                        done_callback=self.enable_download_list_update)

                return

            if self._config.device_sync.playlists.create:
                try:
                    episodes_to_delete = []
                    if self._config.device_sync.playlists.two_way_sync:
                        for current_channel in self.channels:
                            #only include channels that are included in the sync
                            if current_channel.sync_to_mp3_player:
                                #get playlist object
                                playlist = gPodderDevicePlaylist(
                                    self._config, current_channel.title)
                                #get episodes to be written to playlist
                                episodes_for_playlist = sorted(
                                    current_channel.get_episodes(
                                        gpodder.STATE_DOWNLOADED),
                                    key=lambda ep: ep.published)
                                episode_keys = list(
                                    map(
                                        playlist.
                                        get_absolute_filename_for_playlist,
                                        episodes_for_playlist))

                                episode_dict = dict(
                                    list(
                                        zip(episode_keys,
                                            episodes_for_playlist)))

                                #then get episodes in playlist (if it exists) already on device
                                episodes_in_playlists = playlist.read_m3u()
                                #if playlist doesn't exist (yet) episodes_in_playlist will be empty
                                if episodes_in_playlists:
                                    for episode_filename in episodes_in_playlists:

                                        if not (os.path.exists(
                                                os.path.join(
                                                    playlist.mountpoint,
                                                    episode_filename))):
                                            #episode was synced but no longer on device
                                            #i.e. must have been deleted by user, so delete from gpodder
                                            try:
                                                episodes_to_delete.append(
                                                    episode_dict[
                                                        episode_filename])
                                            except KeyError as ioe:
                                                logger.warn(
                                                    'Episode %s, removed from device has already been deleted from gpodder',
                                                    episode_filename)

                    #delete all episodes from gpodder (will prompt user)

                    #not using playlists to delete
                    def auto_delete_callback(episodes):

                        if not episodes:
                            #episodes were deleted on device
                            #but user decided not to delete them from gpodder
                            #so jump straight to sync
                            logger.info(
                                'Starting sync - no episodes selected for deletion'
                            )
                            resume_sync([], [], None)
                        else:
                            #episodes need to be deleted from gpodder
                            for episode_to_delete in episodes:
                                logger.info("Deleting episode %s",
                                            episode_to_delete.title)

                            logger.info(
                                'Will start sync - after deleting episodes')
                            self.delete_episode_list(episodes, False, True,
                                                     resume_sync)

                        return

                    if episodes_to_delete:
                        columns = (('markup_delete_episodes', None, None,
                                    _('Episode')), )

                        gPodderEpisodeSelector(
                            self.parent_window,
                            title=_('Episodes have been deleted on device'),
                            instructions=
                            'Select the episodes you want to delete:',
                            episodes=episodes_to_delete,
                            selected=[
                                True,
                            ] * len(episodes_to_delete),
                            columns=columns,
                            callback=auto_delete_callback,
                            _config=self._config)
                    else:
                        logger.warning("Starting sync - no episodes to delete")
                        resume_sync([], [], None)

                except IOError as ioe:
                    title = _('Error writing playlist files')
                    message = _(str(ioe))
                    self.notification(message, title)
            else:
                logger.info('Not creating playlists - starting sync')
                resume_sync([], [], None)

        # This function is used to remove files from the device
        def cleanup_episodes():
            # 'skip_played_episodes' must be used or else all the
            # played tracks will be copied then immediately deleted
            if (self._config.device_sync.delete_played_episodes
                    and self._config.device_sync.skip_played_episodes):
                all_episodes = self._filter_sync_episodes(
                    channels, only_downloaded=False)
                for local_episode in all_episodes:
                    episode = device.episode_on_device(local_episode)
                    if episode is None:
                        continue

                    if local_episode.state == gpodder.STATE_DELETED:
                        logger.info('Removing episode from device: %s',
                                    episode.title)
                        device.remove_track(episode)

            # When this is done, start the callback in the UI code
            util.idle_add(check_free_space)

        # This will run the following chain of actions:
        #  1. Remove old episodes (in worker thread)
        #  2. Check for free space (in UI thread)
        #  3. Sync the device (in UI thread)
        util.run_in_background(cleanup_episodes)
Example #22
0
 def schedule_save(self):
     if self.__save_thread is None:
         self.__save_thread = util.run_in_background(self.save_thread_proc, True)
Example #23
0
                        GObject.idle_add(launcher_entry.set_progress,
                                float(value))
                except:
                    pass

    class LauncherEntry:
        FILENAME = 'gpodder.desktop'

        def __init__(self):
            self.launcher = Unity.LauncherEntry.get_for_desktop_id(
                    self.FILENAME)

        def set_count(self, count):
            self.launcher.set_property('count', count)
            self.launcher.set_property('count_visible', count > 0)

        def set_progress(self, progress):
            self.launcher.set_property('progress', progress)
            self.launcher.set_property('progress_visible', 0. <= progress < 1.)

    GObject.threads_init()
    loop = GObject.MainLoop()
    util.run_in_background(loop.run)

    launcher_entry = LauncherEntry()
    reader = InputReader(sys.stdin, launcher_entry)
    reader.read()

    loop.quit()

Example #24
0
 def on_btnSearchYouTube_clicked(self, widget, *args):
     self.entryYoutubeSearch.set_sensitive(False)
     self.treeviewYouTubeChooser.set_sensitive(False)
     self.btnSearchYouTube.set_sensitive(False)
     util.run_in_background(lambda: self.thread_func(2))
Example #25
0
    def __init__(self, args, gpodder_core, dbus_bus_name):
        QObject.__init__(self)

        self.dbus_bus_name = dbus_bus_name
        # TODO: Expose the same D-Bus API as the Gtk UI D-Bus object (/gui)
        # TODO: Create a gpodder.dbusproxy.DBusPodcastsProxy object (/podcasts)

        # Enable OpenGL rendering without requiring QtOpenGL
        # On Harmattan we let the system choose the best graphicssystem
        if "-graphicssystem" not in args and not gpodder.ui.harmattan:
            if gpodder.ui.fremantle:
                args += ["-graphicssystem", "opengl"]
            elif not gpodder.win32:
                args += ["-graphicssystem", "raster"]

        self.app = QApplication(args)
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        self.quit.connect(self.on_quit)

        self.core = gpodder_core
        self.config = self.core.config
        self.db = self.core.db
        self.model = self.core.model

        self.config_proxy = ConfigProxy(self.config)

        # Initialize the gpodder.net client
        self.mygpo_client = my.MygPoClient(self.config)

        gpodder.user_extensions.on_ui_initialized(
            self.model, self.extensions_podcast_update_cb, self.extensions_episode_download_cb
        )

        self.view = DeclarativeView()
        self.view.closing.connect(self.on_quit)
        self.view.setResizeMode(QDeclarativeView.SizeRootObjectToView)

        self.controller = Controller(self)
        self.media_buttons_handler = helper.MediaButtonsHandler()
        self.tracker_miner_config = helper.TrackerMinerConfig()
        self.podcast_model = gPodderPodcastListModel()
        self.episode_model = gPodderEpisodeListModel(self.config)
        self.last_episode = None

        # A dictionary of episodes that are currently active
        # in some way (i.e. playing back or downloading)
        self.active_episode_wrappers = {}

        engine = self.view.engine()

        # Maemo 5: Experimental Qt Mobility packages are installed in /opt
        if gpodder.ui.fremantle:
            for path in ("/opt/qtm11/imports", "/opt/qtm12/imports"):
                engine.addImportPath(path)
        elif gpodder.win32:
            for path in (r"C:\QtSDK\Desktop\Qt\4.7.4\msvc2008\imports",):
                engine.addImportPath(path)

        # Add the cover art image provider
        self.cover_provider = images.LocalCachedImageProvider()
        engine.addImageProvider("cover", self.cover_provider)

        root_context = self.view.rootContext()
        root_context.setContextProperty("controller", self.controller)
        root_context.setContextProperty("configProxy", self.config_proxy)
        root_context.setContextProperty("mediaButtonsHandler", self.media_buttons_handler)
        root_context.setContextProperty("trackerMinerConfig", self.tracker_miner_config)

        # Load the QML UI (this could take a while...)
        self.view.setSource(QUrl.fromLocalFile(QML("main_default.qml")))

        # Proxy to the "main" QML object for direct access to Qt Properties
        self.main = helper.QObjectProxy(self.view.rootObject().property("main"))

        self.main.podcastModel = self.podcast_model
        self.main.episodeModel = self.episode_model

        self.view.setWindowTitle("gPodder")

        if gpodder.ui.harmattan:
            self.view.showFullScreen()
        elif gpodder.ui.fremantle:
            self.view.setAttribute(Qt.WA_Maemo5AutoOrientation, True)
            self.view.showFullScreen()
        else:
            # On the Desktop, scale to fit my small laptop screen..
            desktop = self.app.desktop()
            if desktop.height() < 1000:
                FACTOR = 0.8
                self.view.scale(FACTOR, FACTOR)
                size = self.view.size()
                size *= FACTOR
                self.view.resize(size)
            self.view.show()

        self.do_start_progress.connect(self.on_start_progress)
        self.do_end_progress.connect(self.on_end_progress)
        self.do_show_message.connect(self.on_show_message)

        podcasts = self.load_podcasts()

        self.resumable_episodes = None
        self.do_offer_download_resume.connect(self.on_offer_download_resume)
        util.run_in_background(self.find_partial_downloads(podcasts))
Example #26
0
                


        # This function is used to remove files from the device
        def cleanup_episodes():
            # 'skip_played_episodes' must be used or else all the
            # played tracks will be copied then immediately deleted
            if (self._config.device_sync.delete_played_episodes and
                    self._config.device_sync.skip_played_episodes):
                all_episodes = self._filter_sync_episodes(channels,
                        only_downloaded=False)
                for local_episode in all_episodes:
                    episode = device.episode_on_device(local_episode)
                    if episode is None:
                        continue

                    if local_episode.state == gpodder.STATE_DELETED:
                        logger.info('Removing episode from device: %s',
                                episode.title)
                        device.remove_track(episode)

            # When this is done, start the callback in the UI code
            util.idle_add(check_free_space)

        # This will run the following chain of actions:
        #  1. Remove old episodes (in worker thread)
        #  2. Check for free space (in UI thread)
        #  3. Sync the device (in UI thread)
        util.run_in_background(cleanup_episodes)

Example #27
0
                    if command == 'progress':
                        GObject.idle_add(launcher_entry.set_progress,
                                float(value))
                except:
                    pass

    class LauncherEntry:
        FILENAME = 'gpodder.desktop'

        def __init__(self):
            self.launcher = Unity.LauncherEntry.get_for_desktop_id(
                self.FILENAME)

        def set_count(self, count):
            self.launcher.set_property('count', count)
            self.launcher.set_property('count_visible', count > 0)

        def set_progress(self, progress):
            self.launcher.set_property('progress', progress)
            self.launcher.set_property('progress_visible', 0. <= progress < 1.)

    GObject.threads_init()
    loop = GObject.MainLoop()
    util.run_in_background(loop.run)

    launcher_entry = LauncherEntry()
    reader = InputReader(sys.stdin, launcher_entry)
    reader.read()

    loop.quit()
Example #28
0
 def force_start_task(self, task):
     if self.tasks.set_downloading(task):
         worker = ForceDownloadWorker(task)
         util.run_in_background(worker.run)
Example #29
0
                


        # This function is used to remove files from the device
        def cleanup_episodes():
            # 'skip_played_episodes' must be used or else all the
            # played tracks will be copied then immediately deleted
            if (self._config.device_sync.delete_played_episodes and
                    self._config.device_sync.skip_played_episodes):
                all_episodes = self._filter_sync_episodes(channels,
                        only_downloaded=False)
                for local_episode in all_episodes:
                    episode = device.episode_on_device(local_episode)
                    if episode is None:
                        continue

                    if local_episode.state == gpodder.STATE_DELETED:
                        logger.info('Removing episode from device: %s',
                                episode.title)
                        device.remove_track(episode)

            # When this is done, start the callback in the UI code
            util.idle_add(check_free_space)

        # This will run the following chain of actions:
        #  1. Remove old episodes (in worker thread)
        #  2. Check for free space (in UI thread)
        #  3. Sync the device (in UI thread)
        util.run_in_background(cleanup_episodes)

Example #30
0
    def __init__(self, args, gpodder_core, dbus_bus_name):
        QObject.__init__(self)

        self.dbus_bus_name = dbus_bus_name
        # TODO: Expose the same D-Bus API as the Gtk UI D-Bus object (/gui)
        # TODO: Create a gpodder.dbusproxy.DBusPodcastsProxy object (/podcasts)

        self.app = QApplication(args)
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        self.quit.connect(self.on_quit)
        self.episodeUpdated.connect(self.on_episode_updated)
        self.setEpisodeListModel.connect(self.on_set_episode_list_model)

        self.core = gpodder_core
        self.config = self.core.config
        self.db = self.core.db
        self.model = self.core.model

        self.config_proxy = ConfigProxy(self.config)

        # Initialize the gpodder.net client
        self.mygpo_client = my.MygPoClient(self.config)

        gpodder.user_extensions.on_ui_initialized(self.model,
                self.extensions_podcast_update_cb,
                self.extensions_episode_download_cb)

        self.view = DeclarativeView()
        self.view.closing.connect(self.on_quit)
        self.view.setResizeMode(QDeclarativeView.SizeRootObjectToView)

        self.controller = Controller(self)
        self.media_buttons_handler = helper.MediaButtonsHandler()
        self.tracker_miner_config = helper.TrackerMinerConfig()
        self.podcast_model = gPodderPodcastListModel()
        self.episode_model = gPodderEpisodeListModel(self.config, self)
        self.last_episode = None

        # A dictionary of episodes that are currently active
        # in some way (i.e. playing back or downloading)
        self.active_episode_wrappers = {}

        engine = self.view.engine()

        # Add the cover art image provider
        self.cover_provider = images.LocalCachedImageProvider()
        engine.addImageProvider('cover', self.cover_provider)

        root_context = self.view.rootContext()
        root_context.setContextProperty('controller', self.controller)
        root_context.setContextProperty('configProxy', self.config_proxy)
        root_context.setContextProperty('mediaButtonsHandler',
                self.media_buttons_handler)
        root_context.setContextProperty('trackerMinerConfig',
                self.tracker_miner_config)

        # Load the QML UI (this could take a while...)
        self.view.setSource(QUrl.fromLocalFile(QML('main_default.qml')))

        # Proxy to the "main" QML object for direct access to Qt Properties
        self.main = helper.QObjectProxy(self.view.rootObject().property('main'))

        self.main.podcastModel = self.podcast_model
        self.main.episodeModel = self.episode_model

        self.view.setWindowTitle('gPodder')

        if gpodder.ui.harmattan:
            self.view.showFullScreen()
        else:
            # On the Desktop, scale to fit my small laptop screen..
            desktop = self.app.desktop()
            if desktop.height() < 1000:
                FACTOR = .8
                self.view.scale(FACTOR, FACTOR)
                size = self.view.size()
                size *= FACTOR
                self.view.resize(size)
            self.view.show()

        self.do_start_progress.connect(self.on_start_progress)
        self.do_end_progress.connect(self.on_end_progress)
        self.do_show_message.connect(self.on_show_message)

        podcasts = self.load_podcasts()

        self.resumable_episodes = None
        self.do_offer_download_resume.connect(self.on_offer_download_resume)
        util.run_in_background(self.find_partial_downloads(podcasts))
Example #31
0
 def force_start_task(self, task):
     if self.tasks.set_downloading(task):
         worker = ForceDownloadWorker(task)
         util.run_in_background(worker.run)
Example #32
0
    def on_synchronize_episodes(self,
                                channels,
                                episodes=None,
                                force_played=True):
        device = sync.open_device(self)

        if device is None:
            return self._show_message_unconfigured()

        if not device.open():
            return self._show_message_cannot_open()
        else:
            # Only set if device is configured and opened successfully
            self.device = device

        if episodes is None:
            force_played = False
            episodes = self._filter_sync_episodes(channels)

        def check_free_space():
            # "Will we add this episode to the device?"
            def will_add(episode):
                # If already on-device, it won't take up any space
                if device.episode_on_device(episode):
                    return False

                # Might not be synced if it's played already
                if (not force_played
                        and self._config.device_sync.skip_played_episodes):
                    return False

                # In all other cases, we expect the episode to be
                # synchronized to the device, so "answer" positive
                return True

            # "What is the file size of this episode?"
            def file_size(episode):
                filename = episode.local_filename(create=False)
                if filename is None:
                    return 0
                return util.calculate_size(str(filename))

            # Calculate total size of sync and free space on device
            total_size = sum(file_size(e) for e in episodes if will_add(e))
            free_space = max(device.get_free_space(), 0)

            if total_size > free_space:
                title = _('Not enough space left on device')
                message = (_(
                    'Additional free space required: %(required_space)s\nDo you want to continue?'
                ) % {
                    'required_space':
                    util.format_filesize(total_size - free_space)
                })
                if not self.show_confirmation(message, title):
                    device.cancel()
                    device.close()
                    return

            # Finally start the synchronization process
            @util.run_in_background
            def sync_thread_func():
                self.enable_download_list_update()
                device.add_sync_tasks(episodes, force_played=force_played)

        # This function is used to remove files from the device
        def cleanup_episodes():
            # 'skip_played_episodes' must be used or else all the
            # played tracks will be copied then immediately deleted
            if (self._config.device_sync.delete_played_episodes
                    and self._config.device_sync.skip_played_episodes):
                all_episodes = self._filter_sync_episodes(
                    channels, only_downloaded=False)
                episodes_on_device = device.get_all_tracks()
                for local_episode in all_episodes:
                    episode = device.episode_on_device(local_episode)
                    if episode is None:
                        continue

                    if local_episode.state == gpodder.STATE_DELETED:
                        logger.info('Removing episode from device: %s',
                                    episode.title)
                        device.remove_track(episode)

            # When this is done, start the callback in the UI code
            util.idle_add(check_free_space)

        # This will run the following chain of actions:
        #  1. Remove old episodes (in worker thread)
        #  2. Check for free space (in UI thread)
        #  3. Sync the device (in UI thread)
        util.run_in_background(cleanup_episodes)
Example #33
0
 def podcastSelected(self, podcast):
     self.setEpisodeListTitle(podcast.qtitle)
     util.run_in_background(lambda: self.root.select_podcast(podcast))
Example #34
0
    def __init__(self, args, gpodder_core, dbus_bus_name):
        QObject.__init__(self)

        self.dbus_bus_name = dbus_bus_name
        # TODO: Expose the same D-Bus API as the Gtk UI D-Bus object (/gui)
        # TODO: Create a gpodder.dbusproxy.DBusPodcastsProxy object (/podcasts)

        self.app = QApplication(args)
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        self.quit.connect(self.on_quit)

        self.core = gpodder_core
        self.config = self.core.config
        self.db = self.core.db
        self.model = self.core.model

        self.config_proxy = ConfigProxy(self.config)

        # Initialize the gpodder.net client
        self.mygpo_client = my.MygPoClient(self.config)

        gpodder.user_extensions.on_ui_initialized(
            self.model, self.extensions_podcast_update_cb,
            self.extensions_episode_download_cb)

        self.view = DeclarativeView()
        self.view.closing.connect(self.on_quit)
        self.view.setResizeMode(QDeclarativeView.SizeRootObjectToView)

        self.controller = Controller(self)
        self.media_buttons_handler = helper.MediaButtonsHandler()
        self.tracker_miner_config = helper.TrackerMinerConfig()
        self.podcast_model = gPodderPodcastListModel()
        self.episode_model = gPodderEpisodeListModel(self.config)
        self.last_episode = None

        # A dictionary of episodes that are currently active
        # in some way (i.e. playing back or downloading)
        self.active_episode_wrappers = {}

        engine = self.view.engine()

        # Add the cover art image provider
        self.cover_provider = images.LocalCachedImageProvider()
        engine.addImageProvider('cover', self.cover_provider)

        root_context = self.view.rootContext()
        root_context.setContextProperty('controller', self.controller)
        root_context.setContextProperty('configProxy', self.config_proxy)
        root_context.setContextProperty('mediaButtonsHandler',
                                        self.media_buttons_handler)
        root_context.setContextProperty('trackerMinerConfig',
                                        self.tracker_miner_config)

        # Load the QML UI (this could take a while...)
        self.view.setSource(QUrl.fromLocalFile(QML('main_default.qml')))

        # Proxy to the "main" QML object for direct access to Qt Properties
        self.main = helper.QObjectProxy(
            self.view.rootObject().property('main'))

        self.main.podcastModel = self.podcast_model
        self.main.episodeModel = self.episode_model

        self.view.setWindowTitle('gPodder')

        if gpodder.ui.harmattan:
            self.view.showFullScreen()
        else:
            # On the Desktop, scale to fit my small laptop screen..
            desktop = self.app.desktop()
            if desktop.height() < 1000:
                FACTOR = .8
                self.view.scale(FACTOR, FACTOR)
                size = self.view.size()
                size *= FACTOR
                self.view.resize(size)
            self.view.show()

        self.do_start_progress.connect(self.on_start_progress)
        self.do_end_progress.connect(self.on_end_progress)
        self.do_show_message.connect(self.on_show_message)

        podcasts = self.load_podcasts()

        self.resumable_episodes = None
        self.do_offer_download_resume.connect(self.on_offer_download_resume)
        util.run_in_background(self.find_partial_downloads(podcasts))
Example #35
0
    def on_synchronize_episodes(self, channels, episodes=None, force_played=True):
        device = sync.open_device(self)

        if device is None:
            return self._show_message_unconfigured()

        if not device.open():
            return self._show_message_cannot_open()
        else:
            # Only set if device is configured and opened successfully
            self.device = device

        if episodes is None:
            force_played = False
            episodes = self._filter_sync_episodes(channels)

        def check_free_space():
            # "Will we add this episode to the device?"
            def will_add(episode):
                # If already on-device, it won't take up any space
                if device.episode_on_device(episode):
                    return False

                # Might not be synced if it's played already
                if (not force_played and
                        self._config.device_sync.skip_played_episodes):
                    return False

                # In all other cases, we expect the episode to be
                # synchronized to the device, so "answer" positive
                return True

            # "What is the file size of this episode?"
            def file_size(episode):
                filename = episode.local_filename(create=False)
                if filename is None:
                    return 0
                return util.calculate_size(str(filename))

            # Calculate total size of sync and free space on device
            total_size = sum(file_size(e) for e in episodes if will_add(e))
            free_space = max(device.get_free_space(), 0)

            if total_size > free_space:
                title = _('Not enough space left on device')
                message = (_('Additional free space required: %(required_space)s\nDo you want to continue?') %
               {'required_space': util.format_filesize(total_size - free_space)})
                if not self.show_confirmation(message, title):
                    device.cancel()
                    device.close()
                    return

            # enable updating of UI
            self.enable_download_list_update()

            """Update device playlists
            General approach is as follows:

            When a episode is downloaded and synched, it is added to the
            standard playlist for that podcast which is then written to
            the device.

            After the user has played that episode on their device, they
            can delete that episode from their device.

            At the next sync, gPodder will then compare the standard
            podcast-specific playlists on the device (as written by
            gPodder during the last sync), with the episodes on the
            device.If there is an episode referenced in the playlist
            that is no longer on the device, gPodder will assume that
            the episode has already been synced and subsequently deleted
            from the device, and will hence mark that episode as deleted
            in gPodder. If there are no playlists, nothing is deleted.

            At the next sync, the playlists will be refreshed based on
            the downloaded, undeleted episodes in gPodder, and the
            cycle begins again...
            """

            def resume_sync(episode_urls, channel_urls, progress):
                if progress is not None:
                    progress.on_finished()

                # rest of sync process should continue here
                self.commit_changes_to_database()
                for current_channel in self.channels:
                    # only sync those channels marked for syncing
                    if (self._config.device_sync.device_type == 'filesystem'
                            and current_channel.sync_to_mp3_player
                            and self._config.device_sync.playlists.create):

                        # get playlist object
                        playlist = gPodderDevicePlaylist(self._config,
                                                         current_channel.title)
                        # need to refresh episode list so that
                        # deleted episodes aren't included in playlists
                        episodes_for_playlist = sorted(current_channel.get_episodes(gpodder.STATE_DOWNLOADED),
                                                       key=lambda ep: ep.published)
                        # don't add played episodes to playlist if skip_played_episodes is True
                        if self._config.device_sync.skip_played_episodes:
                            episodes_for_playlist = [ep for ep in episodes_for_playlist if ep.is_new]
                        playlist.write_m3u(episodes_for_playlist)

                # enable updating of UI
                self.enable_download_list_update()

                if (self._config.device_sync.device_type == 'filesystem' and self._config.device_sync.playlists.create):
                    title = _('Update successful')
                    message = _('The playlist on your MP3 player has been updated.')
                    self.notification(message, title)

                # Finally start the synchronization process
                @util.run_in_background
                def sync_thread_func():
                    device.add_sync_tasks(episodes, force_played=force_played,
                                          done_callback=self.enable_download_list_update)

                return

            if self._config.device_sync.playlists.create:
                try:
                    episodes_to_delete = []
                    if self._config.device_sync.playlists.two_way_sync:
                        for current_channel in self.channels:
                            # only include channels that are included in the sync
                            if current_channel.sync_to_mp3_player:
                                # get playlist object
                                playlist = gPodderDevicePlaylist(self._config, current_channel.title)
                                # get episodes to be written to playlist
                                episodes_for_playlist = sorted(current_channel.get_episodes(gpodder.STATE_DOWNLOADED),
                                                               key=lambda ep: ep.published)
                                episode_keys = list(map(playlist.get_absolute_filename_for_playlist,
                                                        episodes_for_playlist))

                                episode_dict = dict(list(zip(episode_keys, episodes_for_playlist)))

                                # then get episodes in playlist (if it exists) already on device
                                episodes_in_playlists = playlist.read_m3u()
                                # if playlist doesn't exist (yet) episodes_in_playlist will be empty
                                if episodes_in_playlists:
                                    for episode_filename in episodes_in_playlists:

                                        if not(os.path.exists(os.path.join(playlist.mountpoint,
                                                                           episode_filename))):
                                            # episode was synced but no longer on device
                                            # i.e. must have been deleted by user, so delete from gpodder
                                            try:
                                                episodes_to_delete.append(episode_dict[episode_filename])
                                            except KeyError as ioe:
                                                logger.warn('Episode %s, removed from device has already been deleted from gpodder',
                                                            episode_filename)
                    # delete all episodes from gpodder (will prompt user)

                    # not using playlists to delete
                    def auto_delete_callback(episodes):

                        if not episodes:
                            # episodes were deleted on device
                            # but user decided not to delete them from gpodder
                            # so jump straight to sync
                            logger.info('Starting sync - no episodes selected for deletion')
                            resume_sync([], [], None)
                        else:
                            # episodes need to be deleted from gpodder
                            for episode_to_delete in episodes:
                                logger.info("Deleting episode %s",
                                            episode_to_delete.title)

                            logger.info('Will start sync - after deleting episodes')
                            self.delete_episode_list(episodes, False,
                                                     True, resume_sync)

                        return

                    if episodes_to_delete:
                        columns = (
                            ('markup_delete_episodes', None, None, _('Episode')),
                        )

                        gPodderEpisodeSelector(
                            self.parent_window,
                            title=_('Episodes have been deleted on device'),
                            instructions='Select the episodes you want to delete:',
                            episodes=episodes_to_delete,
                            selected=[True, ] * len(episodes_to_delete),
                            columns=columns,
                            callback=auto_delete_callback,
                            _config=self._config)
                    else:
                        logger.warning("Starting sync - no episodes to delete")
                        resume_sync([], [], None)

                except IOError as ioe:
                    title = _('Error writing playlist files')
                    message = _(str(ioe))
                    self.notification(message, title)
            else:
                logger.info('Not creating playlists - starting sync')
                resume_sync([], [], None)

        # This function is used to remove files from the device
        def cleanup_episodes():
            # 'skip_played_episodes' must be used or else all the
            # played tracks will be copied then immediately deleted
            if (self._config.device_sync.delete_played_episodes and
                    self._config.device_sync.skip_played_episodes):
                all_episodes = self._filter_sync_episodes(
                    channels, only_downloaded=False)
                for local_episode in all_episodes:
                    episode = device.episode_on_device(local_episode)
                    if episode is None:
                        continue

                    if local_episode.state == gpodder.STATE_DELETED:
                        logger.info('Removing episode from device: %s',
                                    episode.title)
                        device.remove_track(episode)

            # When this is done, start the callback in the UI code
            util.idle_add(check_free_space)

        # This will run the following chain of actions:
        #  1. Remove old episodes (in worker thread)
        #  2. Check for free space (in UI thread)
        #  3. Sync the device (in UI thread)
        util.run_in_background(cleanup_episodes)
Example #36
0
 def force_start_task(self, task):
     with task:
         if task.status in (task.QUEUED, task.PAUSED, task.CANCELLED, task.FAILED):
             task.status = task.DOWNLOADING
             worker = ForceDownloadWorker(task)
             util.run_in_background(worker.run)
Example #37
0
 def schedule_save(self):
     if self.__save_thread is None:
         self.__save_thread = util.run_in_background(
             self.save_thread_proc, True)