Esempio n. 1
0
    def add_track(self, episode):
        self.notify('status', _('Adding %s...') % episode.title)
        filename = str(self.convert_track(episode))
        log("sending " + filename + " (" + episode.title + ").", sender=self)

        try:
            # verify free space
            needed = util.calculate_size(filename)
            free = self.get_free_space()
            if needed > free:
                log('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed), sender=self)
                self.cancelled = True
                return False

            # fill metadata
            metadata = pymtp.LIBMTP_Track()
            metadata.title = str(episode.title)
            metadata.artist = str(episode.channel.title)
            metadata.album = str(episode.channel.title)
            metadata.genre = "podcast"
            metadata.date = self.__date_to_mtp(episode.pubDate)
            metadata.duration = get_track_length(str(filename))

            # send the file
            self.__MTPDevice.send_track_from_file(filename,
                    util.sanitize_filename(metadata.title)+episode.extension(),
                    metadata, 0, callback=self.__callback)
        except:
            log('unable to add episode %s', episode.title, sender=self, traceback=True)
            return False

        return True
Esempio n. 2
0
    def get_all_tracks(self):
        tracks = []
        for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist):
            filename = gpod.itdb_filename_on_ipod(track)

            if filename is None:
                # This can happen if the episode is deleted on the device
                logger.info('Episode has no file: %s', track.title)
                self.remove_track_gpod(track)
                continue

            length = util.calculate_size(filename)
            timestamp = util.file_modification_timestamp(filename)
            modified = util.format_date(timestamp)
            try:
                released = gpod.itdb_time_mac_to_host(track.time_released)
                released = util.format_date(released)
            except ValueError, ve:
                # timestamp out of range for platform time_t (bug 418)
                logger.info('Cannot convert track time: %s', ve)
                released = 0

            t = SyncTrack(track.title, length, modified,
                    modified_sort=timestamp,
                    libgpodtrack=track,
                    playcount=track.playcount,
                    released=released,
                    podcast=track.artist)
            tracks.append(t)
Esempio n. 3
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled/paused, skip it
        with self:
            if self.status in (SyncTask.CANCELLING, SyncTask.CANCELLED):
                self.progress = 0.0
                self.speed = 0.0
                self.status = SyncTask.CANCELLED
                return False

            if self.status == SyncTask.PAUSING:
                self.status = SyncTask.PAUSED
                return False

            # We only start this download if its status is downloading
            if self.status != SyncTask.DOWNLOADING:
                return False

            # We are synching this file right now
            self._notification_shown = False

        sync_result = SyncTask.DOWNLOADING
        try:
            logger.info('Starting SyncTask')
            self.device.add_track(self, reporthook=self.status_updated)
        except SyncCancelledException as e:
            sync_result = SyncTask.CANCELLED
        except Exception as e:
            sync_result = SyncTask.FAILED
            logger.error('Sync failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        with self:
            if sync_result == SyncTask.DOWNLOADING:
                # Everything went well - we're done
                self.status = SyncTask.DONE
                if self.total_size <= 0:
                    self.total_size = util.calculate_size(self.filename)
                    logger.info('Total size updated to %d', self.total_size)
                self.progress = 1.0
                gpodder.user_extensions.on_episode_synced(
                    self.device, self.__episode)
                return True

            self.speed = 0.0

            if sync_result == SyncTask.FAILED:
                self.status = SyncTask.FAILED

            # cancelled/paused -- update state to mark it as safe to manipulate this task again
            elif self.status == SyncTask.PAUSING:
                self.status = SyncTask.PAUSED
            elif self.status == SyncTask.CANCELLING:
                self.status = SyncTask.CANCELLED

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 4
0
    def get_all_tracks(self):
        tracks = []

        if self._config.one_folder_per_podcast:
            files = glob.glob(os.path.join(self.destination, '*', '*'))
        else:
            files = glob.glob(os.path.join(self.destination, '*'))

        for filename in files:
            (title, extension) = os.path.splitext(os.path.basename(filename))
            length = util.calculate_size(filename)

            timestamp = util.file_modification_timestamp(filename)
            modified = util.format_date(timestamp)
            if self._config.one_folder_per_podcast:
                podcast_name = os.path.basename(os.path.dirname(filename))
            else:
                podcast_name = None

            t = SyncTrack(title, length, modified,
                    modified_sort=timestamp,
                    filename=filename,
                    podcast=podcast_name)
            tracks.append(t)
        return tracks
Esempio n. 5
0
    def get_all_tracks(self):
        tracks = []
        for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist):
            filename = gpod.itdb_filename_on_ipod(track)

            if filename is None:
                # This can happen if the episode is deleted on the device
                logger.info('Episode has no file: %s', track.title)
                self.remove_track_gpod(track)
                continue

            length = util.calculate_size(filename)
            timestamp = util.file_modification_timestamp(filename)
            modified = util.format_date(timestamp)
            try:
                released = gpod.itdb_time_mac_to_host(track.time_released)
                released = util.format_date(released)
            except ValueError, ve:
                # timestamp out of range for platform time_t (bug 418)
                logger.info('Cannot convert track time: %s', ve)
                released = 0

            t = SyncTrack(track.title,
                          length,
                          modified,
                          modified_sort=timestamp,
                          libgpodtrack=track,
                          playcount=track.playcount,
                          released=released,
                          podcast=track.artist)
            tracks.append(t)
Esempio n. 6
0
    def get_all_tracks(self):
        tracks = []

        if self._config.one_folder_per_podcast:
            files = glob.glob(os.path.join(self.destination, '*', '*'))
        else:
            files = glob.glob(os.path.join(self.destination, '*'))

        for filename in files:
            (title, extension) = os.path.splitext(os.path.basename(filename))
            length = util.calculate_size(filename)

            timestamp = util.file_modification_timestamp(filename)
            modified = util.format_date(timestamp)
            if self._config.one_folder_per_podcast:
                podcast_name = os.path.basename(os.path.dirname(filename))
            else:
                podcast_name = None

            t = SyncTrack(title,
                          length,
                          modified,
                          modified_sort=timestamp,
                          filename=filename,
                          podcast=podcast_name)
            tracks.append(t)
        return tracks
Esempio n. 7
0
    def add_track(self, episode):
        self.notify('status', _('Adding %s...') % episode.title)
        filename = str(self.convert_track(episode))
        logger.info("sending %s (%s).", filename, episode.title)

        try:
            # verify free space
            needed = util.calculate_size(filename)
            free = self.get_free_space()
            if needed > free:
                logger.error('Not enough space on device %s: %s available, but '
                             'need at least %s',
                             self.get_name(),
                             util.format_filesize(free),
                             util.format_filesize(needed))
                self.cancelled = True
                return False

            # fill metadata
            metadata = pymtp.LIBMTP_Track()
            metadata.title = str(episode.title)
            metadata.artist = str(episode.channel.title)
            metadata.album = str(episode.channel.title)
            metadata.genre = "podcast"
            metadata.date = self.__date_to_mtp(episode.published)
            metadata.duration = get_track_length(str(filename))

            folder_name = ''
            if episode.mimetype.startswith('audio/') and self._config.mtp_audio_folder:
                folder_name = self._config.mtp_audio_folder
            if episode.mimetype.startswith('video/') and self._config.mtp_video_folder:
                folder_name = self._config.mtp_video_folder
            if episode.mimetype.startswith('image/') and self._config.mtp_image_folder:
                folder_name = self._config.mtp_image_folder

            if folder_name != '' and self._config.mtp_podcast_folders:
                folder_name += os.path.sep + str(episode.channel.title)

            # log('Target MTP folder: %s' % folder_name)

            if folder_name == '':
                folder_id = 0
            else:
                folder_id = self.__MTPDevice.mkdir(folder_name)

            # send the file
            to_file = util.sanitize_filename(metadata.title) + episode.extension()
            self.__MTPDevice.send_track_from_file(filename, to_file,
                    metadata, folder_id, callback=self.__callback)
            if gpodder.user_hooks is not None:
                gpodder.user_hooks.on_file_copied_to_mtp(self, filename, to_file)
        except:
            logger.error('unable to add episode %s', episode.title)
            return False

        return True
Esempio n. 8
0
    def add_track(self, episode):
        self.notify('status', _('Adding %s...') % episode.title)
        filename = str(self.convert_track(episode))
        log("sending %s (%s).", filename, episode.title, sender=self)

        try:
            # verify free space
            needed = util.calculate_size(filename)
            free = self.get_free_space()
            if needed > free:
                log('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed), sender=self)
                self.cancelled = True
                return False

            # fill metadata
            metadata = pymtp.LIBMTP_Track()
            metadata.title = str(episode.title)
            metadata.artist = str(episode.channel.title)
            metadata.album = str(episode.channel.title)
            metadata.genre = "podcast"
            metadata.date = self.__date_to_mtp(episode.pubDate)
            metadata.duration = get_track_length(str(filename))

            folder_name = ''
            if episode.mimetype.startswith('audio/') and self._config.mtp_audio_folder:
                folder_name = self._config.mtp_audio_folder
            if episode.mimetype.startswith('video/') and self._config.mtp_video_folder:
                folder_name = self._config.mtp_video_folder
            if episode.mimetype.startswith('image/') and self._config.mtp_image_folder:
                folder_name = self._config.mtp_image_folder

            if folder_name != '' and self._config.mtp_podcast_folders:
                folder_name += os.path.sep + str(episode.channel.title)

            # log('Target MTP folder: %s' % folder_name)

            if folder_name == '':
                folder_id = 0
            else:
                folder_id = self.__MTPDevice.mkdir(folder_name)

            # send the file
            to_file = util.sanitize_filename(metadata.title) + episode.extension()
            self.__MTPDevice.send_track_from_file(filename, to_file,
                    metadata, folder_id, callback=self.__callback)
            if gpodder.user_hooks is not None:
                gpodder.user_hooks.on_file_copied_to_mtp(self, filename, to_file)
        except:
            log('unable to add episode %s', episode.title, sender=self, traceback=True)
            return False

        return True
Esempio n. 9
0
    def get_all_tracks(self):
        tracks = []
        for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist):
            filename = gpod.itdb_filename_on_ipod(track)
            length = util.calculate_size(filename)

            timestamp = util.file_modification_timestamp(filename)
            modified = util.format_date(timestamp)
            released = gpod.itdb_time_mac_to_host(track.time_released)
            released = util.format_date(released)

            t = SyncTrack(track.title, length, modified, modified_sort=timestamp, libgpodtrack=track, playcount=track.playcount, released=released, podcast=track.artist)
            tracks.append(t)
        return tracks        
Esempio n. 10
0
    def add_track(self, task, reporthook=None):
        episode = task.episode
        self.notify('status', _('Adding %s') % episode.title)

        # get the folder on the device
        folder = self.get_episode_folder_on_device(episode)

        filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert filename is not None

        from_file = filename

        # verify free space
        needed = util.calculate_size(from_file)
        free = self.get_free_space()
        if free == -1:
            logger.warn('Cannot determine free disk space on device')
        elif needed > free:
            d = {'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed)}
            message = _('Not enough space in %(path)s: %(free)s available, but need at least %(need)s')
            raise SyncFailedException(message % d)

        # get the filename that will be used on the device
        to_file = self.get_episode_file_on_device(episode)
        to_file = folder.get_child(to_file)

        util.make_directory(folder)

        if not to_file.query_exists():
            logger.info('Copying %s => %s',
                    os.path.basename(from_file),
                    to_file.get_uri())
            from_file = Gio.File.new_for_path(from_file)
            try:
                def hookconvert(current_bytes, total_bytes, user_data):
                    return reporthook(current_bytes, 1, total_bytes)
                from_file.copy(to_file, Gio.FileCopyFlags.OVERWRITE, task.cancellable, hookconvert, None)
            except GLib.Error as err:
                if err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED):
                    raise SyncCancelledException()
                logger.error('Error copying %s to %s: %s', from_file.get_uri(), to_file.get_uri(), err.message)
                d = {'from_file': from_file.get_uri(), 'to_file': to_file.get_uri(), 'message': err.message}
                self.errors.append(_('Error copying %(from_file)s to %(to_file)s: %(message)s') % d)
                return False

        return True
Esempio n. 11
0
    def add_track(self, episode, reporthook=None):
        self.notify('status',
                    _('Adding %s') % episode.title.decode('utf-8', 'ignore'))

        # get the folder on the device
        folder = self.get_episode_folder_on_device(episode)

        filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert filename is not None

        from_file = util.sanitize_encoding(filename)

        # verify free space
        needed = util.calculate_size(from_file)
        free = self.get_free_space()
        if free == -1:
            logger.warn('Cannot determine free disk space on device')
        elif needed > free:
            d = {
                'path': self.destination,
                'free': util.format_filesize(free),
                'need': util.format_filesize(needed)
            }
            message = _(
                'Not enough space in %(path)s: %(free)s available, but need at least %(need)s'
            )
            raise SyncFailedException(message % d)

        # get the filename that will be used on the device
        to_file = self.get_episode_file_on_device(episode)
        to_file = util.sanitize_encoding(os.path.join(folder, to_file))

        if not os.path.exists(folder):
            try:
                os.makedirs(folder)
            except:
                logger.error('Cannot create folder on MP3 player: %s', folder)
                return False

        if not os.path.exists(to_file):
            logger.info('Copying %s => %s', os.path.basename(from_file),
                        to_file.decode(util.encoding))
            self.copy_file_progress(from_file, to_file, reporthook)

        return True
Esempio n. 12
0
    def add_track(self, task, reporthook=None):
        episode = task.episode
        self.notify('status', _('Adding %s') % episode.title)
        tracklist = self.ipod.get_podcast_tracks()
        episode_urls = [track.podcast_url for track in tracklist]

        if episode.url in episode_urls:
            # Mark as played on iPod if played locally (and set podcast flags)
            self.update_from_episode(
                tracklist[episode_urls.index(episode.url)], episode)
            return True

        local_filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert local_filename is not None

        if util.calculate_size(local_filename) > self.get_free_space():
            logger.error('Not enough space on %s, sync aborted...',
                         self.mountpoint)
            d = {'episode': episode.title, 'mountpoint': self.mountpoint}
            message = _(
                'Error copying %(episode)s: Not enough free space on %(mountpoint)s'
            )
            self.errors.append(message % d)
            self.cancelled = True
            return False

        (fn, extension) = os.path.splitext(local_filename)
        if extension.lower().endswith('ogg'):
            # XXX: Proper file extension/format support check for iPod
            logger.error('Cannot copy .ogg files to iPod.')
            return False

        track = self.ipod.add_track(local_filename, episode.title,
                                    episode.channel.title,
                                    episode._text_description, episode.url,
                                    episode.channel.url, episode.published,
                                    get_track_length(local_filename),
                                    episode.file_type() == 'audio')

        self.update_from_episode(track, episode, initial=True)

        reporthook(episode.file_size, 1, episode.file_size)

        return True
Esempio n. 13
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == SyncTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            return False

        # We only start this download if its status is "downloading"
        if self.status != SyncTask.DOWNLOADING:
            return False

        # We are synching this file right now
        self.status = SyncTask.DOWNLOADING
        self._notification_shown = False

        try:
            logger.info('Starting SyncTask')
            self.device.add_track(self.episode, reporthook=self.status_updated)
        except Exception as e:
            self.status = SyncTask.FAILED
            logger.error('Sync failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        if self.status == SyncTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = SyncTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_synced(self.device,
                                                      self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 14
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == SyncTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            return False

        # We only start this download if its status is "downloading"
        if self.status != SyncTask.DOWNLOADING:
            return False

        # We are synching this file right now
        self.status = SyncTask.DOWNLOADING
        self._notification_shown = False

        try:
            logger.info('Starting SyncTask')
            self.device.add_track(self.episode, reporthook=self.status_updated)
        except Exception as e:
            self.status = SyncTask.FAILED
            logger.error('Sync failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e),)

        if self.status == SyncTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = SyncTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_synced(self.device, self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 15
0
    def add_track(self, episode,reporthook=None):
        self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore'))

        # get the folder on the device
        folder = self.get_episode_folder_on_device(episode)

        filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert filename is not None

        from_file = util.sanitize_encoding(filename)

        # verify free space
        needed = util.calculate_size(from_file)
        free = self.get_free_space()
        if free == -1:
            logger.warn('Cannot determine free disk space on device')
        elif needed > free:
            d = {'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed)}
            message =_('Not enough space in %(path)s: %(free)s available, but need at least %(need)s')
            raise SyncFailedException(message % d)

        # get the filename that will be used on the device
        to_file = self.get_episode_file_on_device(episode)
        to_file = util.sanitize_encoding(os.path.join(folder, to_file))

        if not os.path.exists(folder):
            try:
                os.makedirs(folder)
            except:
                logger.error('Cannot create folder on MP3 player: %s', folder)
                return False

        if not os.path.exists(to_file):
            logger.info('Copying %s => %s',
                    os.path.basename(from_file),
                    to_file.decode(util.encoding))
            self.copy_file_progress(from_file, to_file, reporthook)

        return True
Esempio n. 16
0
    def get_all_tracks(self):
        tracks = []
        for track in self.ipod.get_podcast_tracks():
            filename = track.filename_on_ipod

            if filename is None:
                length = 0
                modified = ''
            else:
                length = util.calculate_size(filename)
                timestamp = util.file_modification_timestamp(filename)
                modified = util.format_date(timestamp)

            t = SyncTrack(track.episode_title,
                          length,
                          modified,
                          ipod_track=track,
                          playcount=track.playcount,
                          podcast=track.podcast_title)
            tracks.append(t)
        return tracks
Esempio n. 17
0
    def get_all_tracks(self):
        tracks=[]

        if gl.config.fssync_channel_subfolders:
            files=glob.glob(os.path.join(self.destination, '*', '*'))
        else:
            files=glob.glob(os.path.join(self.destination, '*'))

        for filename in files:
            (title, extension)=os.path.splitext(os.path.basename(filename))
            length=util.calculate_size(filename)
         
            age_in_days=util.file_age_in_days(filename)
            modified=util.file_age_to_string(age_in_days)
            if gl.config.fssync_channel_subfolders:
                podcast_name=os.path.basename(os.path.dirname(filename))
            else:
                podcast_name=None
         
            t=SyncTrack(title, length, modified, filename=filename, podcast=podcast_name)
            tracks.append(t)
        return tracks
Esempio n. 18
0
    def add_track(self, episode, reporthook=None):
        self.notify('status', _('Adding %s') % episode.title)
        tracklist = gpod.sw_get_playlist_tracks(self.podcasts_playlist)
        podcasturls = [track.podcasturl for track in tracklist]

        if episode.url in podcasturls:
            # Mark as played on iPod if played locally (and set podcast flags)
            self.set_podcast_flags(tracklist[podcasturls.index(episode.url)],
                                   episode)
            return True

        original_filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert original_filename is not None
        local_filename = original_filename

        if util.calculate_size(original_filename) > self.get_free_space():
            logger.error('Not enough space on %s, sync aborted...',
                         self.mountpoint)
            d = {'episode': episode.title, 'mountpoint': self.mountpoint}
            message = _(
                'Error copying %(episode)s: Not enough free space on %(mountpoint)s'
            )
            self.errors.append(message % d)
            self.cancelled = True
            return False

        local_filename = episode.local_filename(create=False)

        (fn, extension) = os.path.splitext(local_filename)
        if extension.lower().endswith('ogg'):
            logger.error('Cannot copy .ogg files to iPod.')
            return False

        track = gpod.itdb_track_new()

        # Add release time to track if episode.published has a valid value
        if episode.published > 0:
            try:
                # libgpod>= 0.5.x uses a new timestamp format
                track.time_released = gpod.itdb_time_host_to_mac(
                    int(episode.published))
            except:
                # old (pre-0.5.x) libgpod versions expect mactime, so
                # we're going to manually build a good mactime timestamp here :)
                #
                # + 2082844800 for unixtime => mactime (1970 => 1904)
                track.time_released = int(episode.published + 2082844800)

        track.title = str(episode.title)
        track.album = str(episode.channel.title)
        track.artist = str(episode.channel.title)
        track.description = str(util.remove_html_tags(episode.description))

        track.podcasturl = str(episode.url)
        track.podcastrss = str(episode.channel.url)

        track.tracklen = get_track_length(local_filename)
        track.size = os.path.getsize(local_filename)

        if episode.file_type() == 'audio':
            track.filetype = 'mp3'
            track.mediatype = 0x00000004
        elif episode.file_type() == 'video':
            track.filetype = 'm4v'
            track.mediatype = 0x00000006

        self.set_podcast_flags(track, episode)

        gpod.itdb_track_add(self.itdb, track, -1)
        gpod.itdb_playlist_add_track(self.master_playlist, track, -1)
        gpod.itdb_playlist_add_track(self.podcasts_playlist, track, -1)
        copied = gpod.itdb_cp_track_to_ipod(track, str(local_filename), None)
        reporthook(episode.file_size, 1, episode.file_size)

        # If the file has been converted, delete the temporary file here
        if local_filename != original_filename:
            util.delete_file(local_filename)

        return True
Esempio n. 19
0
 def update_save_dir_size(self):
     self.save_dir_size = util.calculate_size(self._config.download_dir)
Esempio n. 20
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == DownloadTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            self.recycle()
            return False

        # We only start this download if its status is "downloading"
        if self.status != DownloadTask.DOWNLOADING:
            return False

        # We are downloading this file right now
        self.status = DownloadTask.DOWNLOADING
        self._notification_shown = False

        # Restore a reference to this task in the episode
        # when running a recycled task following a pause or failed
        # see #649
        if not self.episode.download_task:
            self.episode.download_task = self

        url = self.__episode.url
        try:
            if url == '':
                raise DownloadNoURLException()

            if self.downloader:
                downloader = self.downloader.custom_downloader(
                    self._config, self.episode)
            else:
                downloader = registry.custom_downloader.resolve(
                    self._config, None, self.episode)

            if downloader:
                logger.info('Downloading %s with %s', url, downloader)
            else:
                downloader = DefaultDownloader.custom_downloader(
                    self._config, self.episode)

            headers, real_url = downloader.retrieve_resume(
                self.tempname, self.status_updated)

            new_mimetype = headers.get('content-type',
                                       self.__episode.mime_type)
            old_mimetype = self.__episode.mime_type
            _basename, ext = os.path.splitext(self.filename)
            if new_mimetype != old_mimetype or util.wrong_extension(ext):
                logger.info('Updating mime type: %s => %s', old_mimetype,
                            new_mimetype)
                old_extension = self.__episode.extension()
                self.__episode.mime_type = new_mimetype
                # don't call local_filename because we'll get the old download name
                new_extension = self.__episode.extension(
                    may_call_local_filename=False)

                # If the desired filename extension changed due to the new
                # mimetype, we force an update of the local filename to fix the
                # extension.
                if old_extension != new_extension or util.wrong_extension(ext):
                    self.filename = self.__episode.local_filename(
                        create=True, force_update=True)

            # In some cases, the redirect of a URL causes the real filename to
            # be revealed in the final URL (e.g. http://gpodder.org/bug/1423)
            if real_url != url and not util.is_known_redirecter(real_url):
                realname, realext = util.filename_from_url(real_url)

                # Only update from redirect if the redirected-to filename has
                # a proper extension (this is needed for e.g. YouTube)
                if not util.wrong_extension(realext):
                    real_filename = ''.join((realname, realext))
                    self.filename = self.__episode.local_filename(
                        create=True, force_update=True, template=real_filename)
                    logger.info(
                        'Download was redirected (%s). New filename: %s',
                        real_url, os.path.basename(self.filename))

            # Look at the Content-disposition header; use if if available
            disposition_filename = util.get_header_param(
                headers, 'filename', 'content-disposition')

            # Some servers do send the content-disposition header, but provide
            # an empty filename, resulting in an empty string here (bug 1440)
            if disposition_filename is not None and disposition_filename != '':
                # The server specifies a download filename - try to use it
                # filename_from_url to remove query string; see #591
                fn, ext = util.filename_from_url(disposition_filename)
                logger.debug(
                    "converting disposition filename '%s' to local filename '%s%s'",
                    disposition_filename, fn, ext)
                disposition_filename = fn + ext
                self.filename = self.__episode.local_filename(
                    create=True,
                    force_update=True,
                    template=disposition_filename)
                new_mimetype, encoding = mimetypes.guess_type(self.filename)
                if new_mimetype is not None:
                    logger.info('Using content-disposition mimetype: %s',
                                new_mimetype)
                    self.__episode.mime_type = new_mimetype

            # Re-evaluate filename and tempname to take care of podcast renames
            # while downloads are running (which will change both file names)
            self.filename = self.__episode.local_filename(create=False)
            self.tempname = os.path.join(os.path.dirname(self.filename),
                                         os.path.basename(self.tempname))
            shutil.move(self.tempname, self.filename)

            # Model- and database-related updates after a download has finished
            self.__episode.on_downloaded(self.filename)
        except DownloadCancelledException:
            logger.info('Download has been cancelled/paused: %s', self)
            if self.status == DownloadTask.CANCELLED:
                util.delete_file(self.tempname)
                self.progress = 0.0
                self.speed = 0.0
        except DownloadNoURLException:
            self.status = DownloadTask.FAILED
            self.error_message = _('Episode has no URL to download')
        except urllib.error.ContentTooShortError as ctse:
            self.status = DownloadTask.FAILED
            self.error_message = _('Missing content from server')
        except ConnectionError as ce:
            # special case request exception
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(ce), exc_info=True)
            d = {'host': ce.args[0].pool.host, 'port': ce.args[0].pool.port}
            self.error_message = _(
                "Couldn't connect to server %(host)s:%(port)s" % d)
        except RequestException as re:
            # extract MaxRetryError to shorten the exception message
            if isinstance(re.args[0], MaxRetryError):
                re = re.args[0]
            logger.error('%s while downloading "%s"',
                         str(re),
                         self.__episode.title,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'error': str(re)}
            self.error_message = _('Request Error: %(error)s') % d
        except IOError as ioe:
            logger.error('%s while downloading "%s": %s',
                         ioe.strerror,
                         self.__episode.title,
                         ioe.filename,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'error': ioe.strerror, 'filename': ioe.filename}
            self.error_message = _('I/O Error: %(error)s: %(filename)s') % d
        except gPodderDownloadHTTPError as gdhe:
            logger.error('HTTP %s while downloading "%s": %s',
                         gdhe.error_code,
                         self.__episode.title,
                         gdhe.error_message,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'code': gdhe.error_code, 'message': gdhe.error_message}
            self.error_message = _('HTTP Error %(code)s: %(message)s') % d
        except Exception as e:
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        if self.status == DownloadTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = DownloadTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_downloaded(self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 21
0
 def update_save_dir_size(self):
     self.save_dir_size = util.calculate_size(self._config.download_dir)
Esempio n. 22
0
            d = {'error': ioe.strerror, 'filename': ioe.filename}
            self.error_message = _('I/O Error: %(error)s: %(filename)s') % d
        except gPodderDownloadHTTPError, gdhe:
            logger.error('HTTP %s while downloading "%s": %s',
                         gdhe.error_code,
                         self.__episode.title,
                         gdhe.error_message,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'code': gdhe.error_code, 'message': gdhe.error_message}
            self.error_message = _('HTTP Error %(code)s: %(message)s') % d
        except Exception, e:
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        if self.status == DownloadTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = DownloadTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_downloaded(self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 23
0
 def file_size(episode):
     filename = episode.local_filename(create=False)
     if filename is None:
         return 0
     return util.calculate_size(str(filename))
Esempio n. 24
0
    def add_track(self, episode, reporthook=None):
        self.notify('status', _('Adding %s') % episode.title)
        # episode.description
        # episode.description_html
        # file = episode.local_filename(create=False)
        # episode.link
        # episode.title
        # cover
        #

        #bb =io.BytesIO(episode.channel.cover)

        folder = self.get_episode_folder_on_device(episode)
        filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert filename is not None

        from_file = filename

        # verify free space
        needed = util.calculate_size(from_file)
        free = self.get_free_space()
        if free == -1:
            logger.warn('Cannot determine free disk space on device')
        elif needed > free:
            d = {
                'path': self.destination,
                'free': util.format_filesize(free),
                'need': util.format_filesize(needed)
            }
            message = _(
                'Not enough space in %(path)s: %(free)s available, but need at least %(need)s'
            )
            raise SyncFailedException(message % d)

        # get the filename that will be used on the device
        to_file = self.get_episode_file_on_device(episode)
        to_file_name = to_file
        to_file = os.path.join(folder, to_file)

        video_file = to_file.replace(".mp3", "").replace(".m4a", "") + ".avi"
        cmd_file = to_file.replace(".mp3", "").replace(".m4a", "") + ".ps1"

        image_file = to_file.replace(".mp3", "").replace(".m4a", "") + ".png"

        jpg_file = episode.channel.cover_file + ".jpg"
        if not os.path.exists(image_file):
            img = Image.open(jpg_file)
            draw = ImageDraw.Draw(img)
            font = ImageFont.truetype(
                "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 72)
            draw.text((0, 0), episode.title, (255, 255, 255), font=font)
            font = ImageFont.truetype(
                "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 42)
            yy = draw.multiline_text((0, 120),
                                     episode.description, (255, 255, 255),
                                     font=font)
            img.save(image_file)  # write the image

        if not os.path.exists(folder):
            try:
                os.makedirs(folder)
            except:
                logger.error('Cannot create folder on MP3 player: %s', folder)
                return False

        if not os.path.exists(to_file):
            logger.info('Copying %s => %s', os.path.basename(from_file),
                        to_file)
            self.copy_file_progress(from_file, to_file, reporthook)

            #ffmpeg -i "$1" -acodec libfaac -ac 2 -ab 128k -s 480x320
            #-vcodec libx264 -vpre libx264-hq -vpre libx264-ipod640 -b 768k -bt 512k -aspect 3:2 -threads 0 -f mp4 $1.mp4
            cmd = [
                'ffmpeg', '-framerate', '2', '-loop', '1', '-i', image_file,
                "-i", to_file, "-qmin", "3", "-qmax", "5", "-ac", "2", "-ab",
                "128k", "-b:v", "512k", "-aspect", "3:2", "-threads", "4",
                "-s", "480x320", "-ar", "16000", "-b:a", "44K", "-vbr",
                "constrained", "-vf", "scale=iw/4:ih/4", "-profile:v",
                "baseline", "-preset", "ultrafast", "-movflags", "+faststart",
                "-crf", "16", "-c:v", "libx264", "-c:a", "libmp3lame",
                "-reset_timestamps", "1", "-tune", "stillimage", "-pix_fmt",
                "yuv420p", "-shortest", video_file
                #"'{}'".format(video_file)
            ]
            #ffmpeg -i test.mpg -c:v libx264  test.avi
            cmd = subprocess.list2cmdline(cmd)

        if not os.path.exists(cmd_file):
            with open(cmd_file, "w") as fo:
                fo.write(cmd.replace(folder + "/", ""))

        if not os.path.exists(video_file):
            check_call(shlex.split(cmd))

            # now upload
            #loop = asyncio.get_event_loop()
            #loop.run_until_complete(run_bcupload(video_file, image_file, episode))
            #loop.close()

        return True
Esempio n. 25
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == DownloadTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            return False

        # We only start this download if its status is "downloading"
        if self.status != DownloadTask.DOWNLOADING:
            return False

        # We are downloading this file right now
        self.status = DownloadTask.DOWNLOADING
        self._notification_shown = False

        try:
            # Resolve URL and start downloading the episode
            fmt_ids = youtube.get_fmt_ids(self._config.youtube)
            url = youtube.get_real_download_url(self.__episode.url, fmt_ids)
            url = vimeo.get_real_download_url(url,
                                              self._config.vimeo.fileformat)
            url = escapist_videos.get_real_download_url(url)
            url = url.strip()

            # Properly escapes Unicode characters in the URL path section
            # TODO: Explore if this should also handle the domain
            # Based on: http://stackoverflow.com/a/18269491/1072626
            # In response to issue: https://github.com/gpodder/gpodder/issues/232
            def iri_to_url(url):
                url = urllib.parse.urlsplit(url)
                url = list(url)
                # First unquote to avoid escaping quoted content
                url[2] = urllib.parse.unquote(url[2])
                url[2] = urllib.parse.quote(url[2])
                url = urllib.parse.urlunsplit(url)
                return url

            url = iri_to_url(url)

            downloader = DownloadURLOpener(self.__episode.channel)

            # HTTP Status codes for which we retry the download
            retry_codes = (408, 418, 504, 598, 599)
            max_retries = max(0, self._config.auto.retries)

            # Retry the download on timeout (bug 1013)
            for retry in range(max_retries + 1):
                if retry > 0:
                    logger.info('Retrying download of %s (%d)', url, retry)
                    time.sleep(1)

                try:
                    headers, real_url = downloader.retrieve_resume(
                        url, self.tempname, reporthook=self.status_updated)
                    # If we arrive here, the download was successful
                    break
                except urllib.error.ContentTooShortError as ctse:
                    if retry < max_retries:
                        logger.info('Content too short: %s - will retry.', url)
                        continue
                    raise
                except socket.timeout as tmout:
                    if retry < max_retries:
                        logger.info('Socket timeout: %s - will retry.', url)
                        continue
                    raise
                except gPodderDownloadHTTPError as http:
                    if retry < max_retries and http.error_code in retry_codes:
                        logger.info('HTTP error %d: %s - will retry.',
                                    http.error_code, url)
                        continue
                    raise

            new_mimetype = headers.get('content-type',
                                       self.__episode.mime_type)
            old_mimetype = self.__episode.mime_type
            _basename, ext = os.path.splitext(self.filename)
            if new_mimetype != old_mimetype or util.wrong_extension(ext):
                logger.info('Updating mime type: %s => %s', old_mimetype,
                            new_mimetype)
                old_extension = self.__episode.extension()
                self.__episode.mime_type = new_mimetype
                new_extension = self.__episode.extension()

                # If the desired filename extension changed due to the new
                # mimetype, we force an update of the local filename to fix the
                # extension.
                if old_extension != new_extension or util.wrong_extension(ext):
                    self.filename = self.__episode.local_filename(
                        create=True, force_update=True)

            # In some cases, the redirect of a URL causes the real filename to
            # be revealed in the final URL (e.g. http://gpodder.org/bug/1423)
            if real_url != url and not util.is_known_redirecter(real_url):
                realname, realext = util.filename_from_url(real_url)

                # Only update from redirect if the redirected-to filename has
                # a proper extension (this is needed for e.g. YouTube)
                if not util.wrong_extension(realext):
                    real_filename = ''.join((realname, realext))
                    self.filename = self.__episode.local_filename(
                        create=True, force_update=True, template=real_filename)
                    logger.info(
                        'Download was redirected (%s). New filename: %s',
                        real_url, os.path.basename(self.filename))

            # Look at the Content-disposition header; use if if available
            disposition_filename = get_header_param(headers, 'filename',
                                                    'content-disposition')

            if disposition_filename is not None:
                try:
                    disposition_filename.decode('ascii')
                except:
                    logger.warn(
                        'Content-disposition header contains non-ASCII characters - ignoring'
                    )
                    disposition_filename = None

            # Some servers do send the content-disposition header, but provide
            # an empty filename, resulting in an empty string here (bug 1440)
            if disposition_filename is not None and disposition_filename != '':
                # The server specifies a download filename - try to use it
                disposition_filename = os.path.basename(disposition_filename)
                self.filename = self.__episode.local_filename(create=True, \
                        force_update=True, template=disposition_filename)
                new_mimetype, encoding = mimetypes.guess_type(self.filename)
                if new_mimetype is not None:
                    logger.info('Using content-disposition mimetype: %s',
                                new_mimetype)
                    self.__episode.mime_type = new_mimetype

            # Re-evaluate filename and tempname to take care of podcast renames
            # while downloads are running (which will change both file names)
            self.filename = self.__episode.local_filename(create=False)
            self.tempname = os.path.join(os.path.dirname(self.filename),
                                         os.path.basename(self.tempname))
            shutil.move(self.tempname, self.filename)

            # Model- and database-related updates after a download has finished
            self.__episode.on_downloaded(self.filename)
        except DownloadCancelledException:
            logger.info('Download has been cancelled/paused: %s', self)
            if self.status == DownloadTask.CANCELLED:
                util.delete_file(self.tempname)
                self.progress = 0.0
                self.speed = 0.0
        except urllib.error.ContentTooShortError as ctse:
            self.status = DownloadTask.FAILED
            self.error_message = _('Missing content from server')
        except IOError as ioe:
            logger.error('%s while downloading "%s": %s',
                         ioe.strerror,
                         self.__episode.title,
                         ioe.filename,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'error': ioe.strerror, 'filename': ioe.filename}
            self.error_message = _('I/O Error: %(error)s: %(filename)s') % d
        except gPodderDownloadHTTPError as gdhe:
            logger.error('HTTP %s while downloading "%s": %s',
                         gdhe.error_code,
                         self.__episode.title,
                         gdhe.error_message,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'code': gdhe.error_code, 'message': gdhe.error_message}
            self.error_message = _('HTTP Error %(code)s: %(message)s') % d
        except Exception as e:
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        if self.status == DownloadTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = DownloadTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_downloaded(self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 26
0
    def add_track(self, episode,reporthook=None):
        self.notify('status', _('Adding %s') % episode.title)
        tracklist = gpod.sw_get_playlist_tracks(self.podcasts_playlist)
        podcasturls=[track.podcasturl for track in tracklist]

        if episode.url in podcasturls:
            # Mark as played on iPod if played locally (and set podcast flags)
            self.set_podcast_flags(tracklist[podcasturls.index(episode.url)], episode)
            return True

        original_filename = episode.local_filename(create=False)
        # The file has to exist, if we ought to transfer it, and therefore,
        # local_filename(create=False) must never return None as filename
        assert original_filename is not None
        local_filename = original_filename

        if util.calculate_size(original_filename) > self.get_free_space():
            logger.error('Not enough space on %s, sync aborted...', self.mountpoint)
            d = {'episode': episode.title, 'mountpoint': self.mountpoint}
            message =_('Error copying %(episode)s: Not enough free space on %(mountpoint)s')
            self.errors.append(message % d)
            self.cancelled = True
            return False

        local_filename = episode.local_filename(create=False)

        (fn, extension) = os.path.splitext(local_filename)
        if extension.lower().endswith('ogg'):
            logger.error('Cannot copy .ogg files to iPod.')
            return False

        track = gpod.itdb_track_new()

        # Add release time to track if episode.published has a valid value
        if episode.published > 0:
            try:
                # libgpod>= 0.5.x uses a new timestamp format
                track.time_released = gpod.itdb_time_host_to_mac(int(episode.published))
            except:
                # old (pre-0.5.x) libgpod versions expect mactime, so
                # we're going to manually build a good mactime timestamp here :)
                #
                # + 2082844800 for unixtime => mactime (1970 => 1904)
                track.time_released = int(episode.published + 2082844800)

        track.title = str(episode.title)
        track.album = str(episode.channel.title)
        track.artist = str(episode.channel.title)
        track.description = str(util.remove_html_tags(episode.description))

        track.podcasturl = str(episode.url)
        track.podcastrss = str(episode.channel.url)

        track.tracklen = get_track_length(local_filename)
        track.size = os.path.getsize(local_filename)

        if episode.file_type() == 'audio':
            track.filetype = 'mp3'
            track.mediatype = 0x00000004
        elif episode.file_type() == 'video':
            track.filetype = 'm4v'
            track.mediatype = 0x00000006

        self.set_podcast_flags(track, episode)

        gpod.itdb_track_add(self.itdb, track, -1)
        gpod.itdb_playlist_add_track(self.master_playlist, track, -1)
        gpod.itdb_playlist_add_track(self.podcasts_playlist, track, -1)
        copied = gpod.itdb_cp_track_to_ipod(track, str(local_filename), None)
        reporthook(episode.file_size, 1, episode.file_size)

        # If the file has been converted, delete the temporary file here
        if local_filename != original_filename:
            util.delete_file(local_filename)

        return True
Esempio n. 27
0
    def add_track(self, episode):
        self.notify('status', _('Adding %s') % episode.title)
        for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist):
            if episode.url == track.podcasturl:
                if track.playcount > 0:
                    gl.history_mark_played(track.podcasturl)
                # Mark as played on iPod if played locally (and set podcast flags)
                self.set_podcast_flags(track)
                return True

        original_filename=str(episode.local_filename())
        local_filename=original_filename

        # Reserve 10 MiB for iTunesDB writing (to be on the safe side)
        RESERVED_FOR_ITDB=1024*1024*10
        space_for_track=util.get_free_disk_space(self.mountpoint) - RESERVED_FOR_ITDB
        needed=util.calculate_size(local_filename)

        if needed > space_for_track:
            log('Not enough space on %s: %s available, but need at least %s', self.mountpoint, util.format_filesize(space_for_track), util.format_filesize(needed), sender=self)
            self.errors.append( _('Error copying %s: Not enough free disk space on %s') % (episode.title, self.mountpoint))
            self.cancelled=True
            return False

        (fn, extension)=os.path.splitext(original_filename)
        if libconverter.converters.has_converter(extension):
            log('Converting: %s', original_filename, sender=self)
            callback_status=lambda percentage: self.notify('sub-progress', int(percentage))
            local_filename=libconverter.converters.convert(original_filename, callback=callback_status)

            if not libtagupdate.update_metadata_on_file(local_filename, title=episode.title, artist=episode.channel.title):
                log('Could not set metadata on converted file %s', local_filename, sender=self)

            if local_filename is None:
                log('Cannot convert %s', original_filename, sender=self)
                return False
            else:
                local_filename=str(local_filename)

        (fn, extension)=os.path.splitext(local_filename)
        if extension.lower().endswith('ogg'):
            log('Cannot copy .ogg files to iPod.', sender=self)
            return False

        track=gpod.itdb_track_new()
        
        # Add release time to track if pubDate is parseable
        ipod_date=email.Utils.parsedate(episode.pubDate)
        if ipod_date is not None:
            try:
                # libgpod>= 0.5.x uses a new timestamp format
                track.time_released=gpod.itdb_time_host_to_mac(int(time.mktime(ipod_date)))
            except:
                # old (pre-0.5.x) libgpod versions expect mactime, so
                # we're going to manually build a good mactime timestamp here :)
                #
                # + 2082844800 for unixtime => mactime (1970 => 1904)
                track.time_released=int(time.mktime(ipod_date) + 2082844800)
        
        track.title=str(episode.title)
        track.album=str(episode.channel.title)
        track.artist=str(episode.channel.title)
        track.description=str(episode.description)

        track.podcasturl=str(episode.url)
        track.podcastrss=str(episode.channel.url)

        track.tracklen=get_track_length(local_filename)
        track.size=os.path.getsize(local_filename)

        if episode.file_type() == 'audio':
            track.filetype='mp3'
            track.mediatype=0x00000004
        elif episode.file_type() == 'video':
            track.filetype='m4v'
            track.mediatype=0x00000006

        self.set_podcast_flags(track)
        self.set_cover_art(track, local_filename)

        gpod.itdb_track_add(self.itdb, track, -1)
        gpod.itdb_playlist_add_track(self.podcasts_playlist, track, -1)
        gpod.itdb_cp_track_to_ipod( track, local_filename, None)

        # If the file has been converted, delete the temporary file here
        if local_filename != original_filename:
            util.delete_file(local_filename)

        return True
Esempio n. 28
0
 def file_size(episode):
     filename = episode.local_filename(create=False)
     if filename is None:
         return 0
     return util.calculate_size(str(filename))
Esempio n. 29
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == DownloadTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            return False

        # We only start this download if its status is "downloading"
        if self.status != DownloadTask.DOWNLOADING:
            return False

        # We are downloading this file right now
        self.status = DownloadTask.DOWNLOADING
        self._notification_shown = False

        # Restore a reference to this task in the episode
        # when running a recycled task following a pause or failed
        # see #649
        if not self.episode.download_task:
            self.episode.download_task = self

        try:

            custom_downloader = registry.custom_downloader.resolve(
                self._config, None, self.episode)

            url = self.__episode.url
            if custom_downloader:
                logger.info('Downloading %s with %s', url, custom_downloader)
                headers, real_url = custom_downloader.retrieve_resume(
                    self.tempname, reporthook=self.status_updated)
            else:
                # Resolve URL and start downloading the episode
                res = registry.download_url.resolve(self._config, None,
                                                    self.episode)
                if res:
                    url = res
                if url == self.__episode.url:
                    # don't modify custom urls (#635 - vimeo breaks if * is unescaped)
                    url = url.strip()
                    url = util.iri_to_url(url)

                logger.info("Downloading %s", url)
                downloader = DownloadURLOpener(self.__episode.channel)

                # HTTP Status codes for which we retry the download
                retry_codes = (408, 418, 504, 598, 599)
                max_retries = max(0, self._config.auto.retries)

                # Retry the download on timeout (bug 1013)
                for retry in range(max_retries + 1):
                    if retry > 0:
                        logger.info('Retrying download of %s (%d)', url, retry)
                        time.sleep(1)

                    try:
                        headers, real_url = downloader.retrieve_resume(
                            url, self.tempname, reporthook=self.status_updated)
                        # If we arrive here, the download was successful
                        break
                    except urllib.error.ContentTooShortError as ctse:
                        if retry < max_retries:
                            logger.info('Content too short: %s - will retry.',
                                        url)
                            continue
                        raise
                    except socket.timeout as tmout:
                        if retry < max_retries:
                            logger.info('Socket timeout: %s - will retry.',
                                        url)
                            continue
                        raise
                    except gPodderDownloadHTTPError as http:
                        if retry < max_retries and http.error_code in retry_codes:
                            logger.info('HTTP error %d: %s - will retry.',
                                        http.error_code, url)
                            continue
                        raise

            new_mimetype = headers.get('content-type',
                                       self.__episode.mime_type)
            old_mimetype = self.__episode.mime_type
            _basename, ext = os.path.splitext(self.filename)
            if new_mimetype != old_mimetype or util.wrong_extension(ext):
                logger.info('Updating mime type: %s => %s', old_mimetype,
                            new_mimetype)
                old_extension = self.__episode.extension()
                self.__episode.mime_type = new_mimetype
                new_extension = self.__episode.extension()

                # If the desired filename extension changed due to the new
                # mimetype, we force an update of the local filename to fix the
                # extension.
                if old_extension != new_extension or util.wrong_extension(ext):
                    self.filename = self.__episode.local_filename(
                        create=True, force_update=True)

            # In some cases, the redirect of a URL causes the real filename to
            # be revealed in the final URL (e.g. http://gpodder.org/bug/1423)
            if real_url != url and not util.is_known_redirecter(real_url):
                realname, realext = util.filename_from_url(real_url)

                # Only update from redirect if the redirected-to filename has
                # a proper extension (this is needed for e.g. YouTube)
                if not util.wrong_extension(realext):
                    real_filename = ''.join((realname, realext))
                    self.filename = self.__episode.local_filename(
                        create=True, force_update=True, template=real_filename)
                    logger.info(
                        'Download was redirected (%s). New filename: %s',
                        real_url, os.path.basename(self.filename))

            # Look at the Content-disposition header; use if if available
            disposition_filename = get_header_param(headers, 'filename',
                                                    'content-disposition')

            # Some servers do send the content-disposition header, but provide
            # an empty filename, resulting in an empty string here (bug 1440)
            if disposition_filename is not None and disposition_filename != '':
                # The server specifies a download filename - try to use it
                # filename_from_url to remove query string; see #591
                fn, ext = util.filename_from_url(disposition_filename)
                logger.debug(
                    "converting disposition filename '%s' to local filename '%s%s'",
                    disposition_filename, fn, ext)
                disposition_filename = fn + ext
                self.filename = self.__episode.local_filename(
                    create=True,
                    force_update=True,
                    template=disposition_filename)
                new_mimetype, encoding = mimetypes.guess_type(self.filename)
                if new_mimetype is not None:
                    logger.info('Using content-disposition mimetype: %s',
                                new_mimetype)
                    self.__episode.mime_type = new_mimetype

            # Re-evaluate filename and tempname to take care of podcast renames
            # while downloads are running (which will change both file names)
            self.filename = self.__episode.local_filename(create=False)
            self.tempname = os.path.join(os.path.dirname(self.filename),
                                         os.path.basename(self.tempname))
            shutil.move(self.tempname, self.filename)

            # Model- and database-related updates after a download has finished
            self.__episode.on_downloaded(self.filename)
        except DownloadCancelledException:
            logger.info('Download has been cancelled/paused: %s', self)
            if self.status == DownloadTask.CANCELLED:
                util.delete_file(self.tempname)
                self.progress = 0.0
                self.speed = 0.0
        except urllib.error.ContentTooShortError as ctse:
            self.status = DownloadTask.FAILED
            self.error_message = _('Missing content from server')
        except IOError as ioe:
            logger.error('%s while downloading "%s": %s',
                         ioe.strerror,
                         self.__episode.title,
                         ioe.filename,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'error': ioe.strerror, 'filename': ioe.filename}
            self.error_message = _('I/O Error: %(error)s: %(filename)s') % d
        except gPodderDownloadHTTPError as gdhe:
            logger.error('HTTP %s while downloading "%s": %s',
                         gdhe.error_code,
                         self.__episode.title,
                         gdhe.error_message,
                         exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'code': gdhe.error_code, 'message': gdhe.error_message}
            self.error_message = _('HTTP Error %(code)s: %(message)s') % d
        except Exception as e:
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        if self.status == DownloadTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = DownloadTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_downloaded(self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 30
0
    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == DownloadTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            return False

        # We only start this download if its status is "queued"
        if self.status != DownloadTask.QUEUED:
            return False

        # We are downloading this file right now
        self.status = DownloadTask.DOWNLOADING
        self._notification_shown = False

        try:
            # Resolve URL and start downloading the episode
            url = registry.download_url.resolve(self.__episode, self.url, self._config)

            downloader = DownloadURLOpener(self.__episode.podcast)

            # HTTP Status codes for which we retry the download
            retry_codes = (408, 418, 504, 598, 599)
            max_retries = max(0, self._config.auto.retries)

            # Retry the download on timeout (bug 1013)
            for retry in range(max_retries + 1):
                if retry > 0:
                    logger.info('Retrying download of %s (%d)', url, retry)
                    time.sleep(1)

                try:
                    headers, real_url = downloader.retrieve_resume(url, self.tempname,
                                                                   reporthook=self.status_updated)
                    # If we arrive here, the download was successful
                    break
                except urllib.error.ContentTooShortError as ctse:
                    if retry < max_retries:
                        logger.info('Content too short: %s - will retry.', url)
                        continue
                    raise
                except socket.timeout as tmout:
                    if retry < max_retries:
                        logger.info('Socket timeout: %s - will retry.', url)
                        continue
                    raise
                except gPodderDownloadHTTPError as http:
                    if retry < max_retries and http.error_code in retry_codes:
                        logger.info('HTTP error %d: %s - will retry.', http.error_code, url)
                        continue
                    raise

            new_mimetype = headers.get('content-type', self.__episode.mime_type)
            old_mimetype = self.__episode.mime_type
            _basename, ext = os.path.splitext(self.filename)
            if new_mimetype != old_mimetype or util.wrong_extension(ext):
                logger.info('Updating mime type: %s => %s', old_mimetype, new_mimetype)
                old_extension = self.__episode.extension()
                self.__episode.mime_type = new_mimetype
                new_extension = self.__episode.extension()

                # If the desired filename extension changed due to the new
                # mimetype, we force an update of the local filename to fix the
                # extension.
                if old_extension != new_extension or util.wrong_extension(ext):
                    self.filename = self.__episode.local_filename(create=True, force_update=True)

            # In some cases, the redirect of a URL causes the real filename to
            # be revealed in the final URL (e.g. http://gpodder.org/bug/1423)
            if real_url != url:
                realname, realext = util.filename_from_url(real_url)

                # Only update from redirect if the redirected-to filename has
                # a proper extension (this is needed for e.g. YouTube)
                if not util.wrong_extension(realext):
                    real_filename = ''.join((realname, realext))
                    self.filename = self.__episode.local_filename(create=True, force_update=True,
                                                                  template=real_filename)
                    logger.info('Download was redirected (%s). New filename: %s', real_url,
                                os.path.basename(self.filename))

            # Look at the Content-disposition header; use if if available
            disposition_filename = get_header_param(headers, 'filename', 'content-disposition')

            # Some servers do send the content-disposition header, but provide
            # an empty filename, resulting in an empty string here (bug 1440)
            if disposition_filename is not None and disposition_filename != '':
                # The server specifies a download filename - try to use it
                disposition_filename = os.path.basename(disposition_filename)
                self.filename = self.__episode.local_filename(create=True, force_update=True,
                                                              template=disposition_filename)
                new_mimetype, encoding = mimetypes.guess_type(self.filename)
                if new_mimetype is not None:
                    logger.info('Using content-disposition mimetype: %s', new_mimetype)
                    self.__episode.mime_type = new_mimetype

            # Re-evaluate filename and tempname to take care of podcast renames
            # while downloads are running (which will change both file names)
            self.filename = self.__episode.local_filename(create=False)
            self.tempname = os.path.join(os.path.dirname(self.filename),
                                         os.path.basename(self.tempname))
            shutil.move(self.tempname, self.filename)

            # Model- and database-related updates after a download has finished
            self.__episode.on_downloaded(self.filename)
        except DownloadCancelledException:
            logger.info('Download has been cancelled/paused: %s', self)
            if self.status == DownloadTask.CANCELLED:
                util.delete_file(self.tempname)
                self.progress = 0.0
                self.speed = 0.0
        except urllib.error.ContentTooShortError as ctse:
            self.status = DownloadTask.FAILED
            self.error_message = 'Missing content from server'
        except IOError as ioe:
            logger.error('%s while downloading "%s": %s', ioe.strerror,
                         self.__episode.title, ioe.filename, exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'error': ioe.strerror, 'filename': ioe.filename}
            self.error_message = 'I/O Error: %(error)s: %(filename)s' % d
        except gPodderDownloadHTTPError as gdhe:
            logger.error('HTTP %s while downloading "%s": %s',
                         gdhe.error_code, self.__episode.title, gdhe.error_message, exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'code': gdhe.error_code, 'message': gdhe.error_message}
            self.error_message = 'HTTP Error %(code)s: %(message)s' % d
        except Exception as e:
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = 'Error: %s' % (str(e),)

        if self.status == DownloadTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = DownloadTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            registry.after_download.call_each(self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 31
0
class SyncTask(download.DownloadTask):
    # An object representing the synchronization task of an episode

    # Possible states this sync task can be in
    STATUS_MESSAGE = (_('Added'), _('Queued'), _('Synchronizing'),
                      _('Finished'), _('Failed'), _('Cancelled'), _('Paused'))
    (INIT, QUEUED, DOWNLOADING, DONE, FAILED, CANCELLED, PAUSED) = range(7)

    def __str__(self):
        return self.__episode.title

    def __get_status(self):
        return self.__status

    def __set_status(self, status):
        if status != self.__status:
            self.__status_changed = True
            self.__status = status

    status = property(fget=__get_status, fset=__set_status)

    def __get_device(self):
        return self.__device

    def __set_device(self, device):
        self.__device = device

    device = property(fget=__get_device, fset=__set_device)

    def __get_status_changed(self):
        if self.__status_changed:
            self.__status_changed = False
            return True
        else:
            return False

    status_changed = property(fget=__get_status_changed)

    def __get_activity(self):
        return self.__activity

    def __set_activity(self, activity):
        self.__activity = activity

    activity = property(fget=__get_activity, fset=__set_activity)

    def __get_empty_string(self):
        return ''

    url = property(fget=__get_empty_string)
    podcast_url = property(fget=__get_empty_string)

    def __get_episode(self):
        return self.__episode

    episode = property(fget=__get_episode)

    def cancel(self):
        if self.status in (self.DOWNLOADING, self.QUEUED):
            self.status = self.CANCELLED

    def removed_from_list(self):
        # XXX: Should we delete temporary/incomplete files here?
        pass

    def __init__(self, episode):
        self.__status = SyncTask.INIT
        self.__activity = SyncTask.ACTIVITY_SYNCHRONIZE
        self.__status_changed = True
        self.__episode = episode

        # Create the target filename and save it in the database
        self.filename = self.__episode.local_filename(create=False)
        self.tempname = self.filename + '.partial'

        self.total_size = self.__episode.file_size
        self.speed = 0.0
        self.progress = 0.0
        self.error_message = None

        # Have we already shown this task in a notification?
        self._notification_shown = False

        # Variables for speed limit and speed calculation
        self.__start_time = 0
        self.__start_blocks = 0
        self.__limit_rate_value = 999
        self.__limit_rate = 999

        # Callbacks
        self._progress_updated = lambda x: None

    def notify_as_finished(self):
        if self.status == SyncTask.DONE:
            if self._notification_shown:
                return False
            else:
                self._notification_shown = True
                return True

        return False

    def notify_as_failed(self):
        if self.status == SyncTask.FAILED:
            if self._notification_shown:
                return False
            else:
                self._notification_shown = True
                return True

        return False

    def add_progress_callback(self, callback):
        self._progress_updated = callback

    def status_updated(self, count, blockSize, totalSize):
        # We see a different "total size" while downloading,
        # so correct the total size variable in the thread
        if totalSize != self.total_size and totalSize > 0:
            self.total_size = float(totalSize)

        if self.total_size > 0:
            self.progress = max(
                0.0, min(1.0,
                         float(count * blockSize) / self.total_size))
            self._progress_updated(self.progress)

        if self.status == SyncTask.CANCELLED:
            raise SyncCancelledException()

        if self.status == SyncTask.PAUSED:
            raise SyncCancelledException()

    def recycle(self):
        self.episode.download_task = None

    def run(self):
        # Speed calculation (re-)starts here
        self.__start_time = 0
        self.__start_blocks = 0

        # If the download has already been cancelled, skip it
        if self.status == SyncTask.CANCELLED:
            util.delete_file(self.tempname)
            self.progress = 0.0
            self.speed = 0.0
            return False

        # We only start this download if its status is "queued"
        if self.status != SyncTask.QUEUED:
            return False

        # We are synching this file right now
        self.status = SyncTask.DOWNLOADING
        self._notification_shown = False

        try:
            logger.info('Starting SyncTask')
            self.device.add_track(self.episode, reporthook=self.status_updated)
        except Exception, e:
            self.status = SyncTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e), )

        if self.status == SyncTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = SyncTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_downloaded(self.__episode)
            return True

        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False
Esempio n. 32
0
            self.status = DownloadTask.FAILED
            d = {'error': ioe.strerror, 'filename': ioe.filename}
            self.error_message = _('I/O Error: %(error)s: %(filename)s') % d
        except gPodderDownloadHTTPError, gdhe:
            logger.error('HTTP %s while downloading "%s": %s',
                    gdhe.error_code, self.__episode.title, gdhe.error_message,
                    exc_info=True)
            self.status = DownloadTask.FAILED
            d = {'code': gdhe.error_code, 'message': gdhe.error_message}
            self.error_message = _('HTTP Error %(code)s: %(message)s') % d
        except Exception, e:
            self.status = DownloadTask.FAILED
            logger.error('Download failed: %s', str(e), exc_info=True)
            self.error_message = _('Error: %s') % (str(e),)

        if self.status == DownloadTask.DOWNLOADING:
            # Everything went well - we're done
            self.status = DownloadTask.DONE
            if self.total_size <= 0:
                self.total_size = util.calculate_size(self.filename)
                logger.info('Total size updated to %d', self.total_size)
            self.progress = 1.0
            gpodder.user_extensions.on_episode_downloaded(self.__episode)
            return True
        
        self.speed = 0.0

        # We finished, but not successfully (at least not really)
        return False

Esempio n. 33
0
 def update_save_dir_size(self):
     self.save_dir_size = util.calculate_size(self.save_dir)