Exemple #1
0
def bluetooth_send_file(filename, device=None, callback_finished=None):
    """
    Sends a file via bluetooth using gnome-obex send.
    Optional parameter device is the bluetooth address
    of the device; optional parameter callback_finished
    is a callback function that will be called when the
    sending process has finished - it gets one parameter
    that is either True (when sending succeeded) or False
    when there was some error.

    This function tries to use "bluetooth-sendto", and if
    it is not available, it also tries "gnome-obex-send".
    """
    command_line=None

    if find_command('bluetooth-sendto'):
        command_line=['bluetooth-sendto']
        if device is not None:
            command_line.append('--device=%s' % device)
    elif find_command('gnome-obex-send'):
        command_line=['gnome-obex-send']
        if device is not None:
            command_line += ['--dest', device]

    if command_line is not None:
        command_line.append(filename)
        result=(subprocess.Popen(command_line).wait() == 0)
        if callback_finished is not None:
            callback_finished(result)
        return result
    else:
        log('Cannot send file. Please install "bluetooth-sendto" or "gnome-obex-send".')
        if callback_finished is not None:
            callback_finished(False)
        return False
Exemple #2
0
def calculate_size( path):
    """
    Tries to calculate the size of a directory, including any 
    subdirectories found. The returned value might not be 
    correct if the user doesn't have appropriate permissions 
    to list all subdirectories of the given path.
    """
    if path is None:
        return 0L

    if os.path.dirname( path) == '/':
        return 0L

    if os.path.isfile( path):
        return os.path.getsize( path)

    if os.path.isdir( path) and not os.path.islink( path):
        sum = os.path.getsize( path)

        try:
            for item in os.listdir(path):
                try:
                    sum += calculate_size(os.path.join(path, item))
                except:
                    log('Cannot get size for %s', path)
        except:
            log('Cannot access: %s', path)

        return sum

    return 0L
Exemple #3
0
def object_string_formatter( s, **kwargs):
    """
    Makes attributes of object passed in as keyword 
    arguments available as {OBJECTNAME.ATTRNAME} in 
    the passed-in string and returns a string with 
    the above arguments replaced with the attribute 
    values of the corresponding object.

    Example:

    e = Episode()
    e.title = 'Hello'
    s = '{episode.title} World'
    
    print object_string_formatter( s, episode = e)
          => 'Hello World'
    """
    result = s
    for ( key, o ) in kwargs.items():
        matches = re.findall( r'\{%s\.([^\}]+)\}' % key, s)
        for attr in matches:
            if hasattr( o, attr):
                try:
                    from_s = '{%s.%s}' % ( key, attr )
                    to_s = getattr( o, attr)
                    result = result.replace( from_s, to_s)
                except:
                    log( 'Could not replace attribute "%s" in string "%s".', attr, s)

    return result
    def _save_object(self, o, table, schema):
        self.lock.acquire()
        try:
            cur = self.cursor()
            columns = [
                name for name, typ, required, default in schema if name != 'id'
            ]
            values = [getattr(o, name) for name in columns]

            if o.id is None:
                qmarks = ', '.join('?' * len(columns))
                sql = 'INSERT INTO %s (%s) VALUES (%s)' % (
                    table, ', '.join(columns), qmarks)
                cur.execute(sql, values)
                o.id = cur.lastrowid
            else:
                qmarks = ', '.join('%s = ?' % name for name in columns)
                values.append(o.id)
                sql = 'UPDATE %s SET %s WHERE id = ?' % (table, qmarks)
                cur.execute(sql, values)
        except Exception, e:
            log('Cannot save %s to %s: %s',
                o,
                table,
                e,
                sender=self,
                traceback=True)
Exemple #5
0
def get_episode_info_from_url(url):
    """
    Try to get information about a podcast episode by sending
    a HEAD request to the HTTP server and parsing the result.

    The return value is a dict containing all fields that 
    could be parsed from the URL. This currently contains:
    
      "length": The size of the file in bytes
      "pubdate": The unix timestamp for the pubdate

    If there is an error, this function returns {}. This will
    only function with http:// and https:// URLs.
    """
    if not (url.startswith('http://') or url.startswith('https://')):
        return {}

    r = http_request(url)
    result = {}

    log('Trying to get metainfo for %s', url)

    if 'content-length' in r.msg:
        try:
            length = int(r.msg['content-length'])
            result['length'] = length
        except ValueError, e:
            log('Error converting content-length header.')
Exemple #6
0
    def read_device(self):
        """
        read all files from the device
        """
        log('Reading files from %s',
            self._config.mp3_player_folder,
            sender=self)
        tracks = []
        for root, dirs, files in os.walk(self._config.mp3_player_folder):
            for file in files:
                filename = os.path.join(root, file)

                if filename == self.playlist_file or fnmatch.fnmatch(
                        filename, '*.dat') or fnmatch.fnmatch(
                            filename, '*.DAT'):
                    # We don't want to have our playlist file as
                    # an entry in our file list, so skip it!
                    # We also don't want to include dat files
                    continue

                if self._config.mp3_player_playlist_absolute_path:
                    filename = filename[len(self.mountpoint):]
                else:
                    filename = util.relpath(os.path.dirname(self.playlist_file),
                                            os.path.dirname(filename)) + \
                               os.sep + os.path.basename(filename)

                if self._config.mp3_player_playlist_win_path:
                    filename = filename.replace(os.sep, '\\')

                tracks.append(filename)
        return tracks
 def commit(self):
     self.lock.acquire()
     try:
         self.log("COMMIT")
         self.db.commit()
     except Exception, e:
         log('Error commiting changes: %s', e, sender=self, traceback=True)
Exemple #8
0
def get_free_disk_space(path):
    """
    Calculates the free disk space available to the current user
    on the file system that contains the given path.

    If the path (or its parent folder) does not yet exist, this
    function returns zero.
    """

    if not os.path.exists(path):
        return 0

    if gpodder.win32:
        return get_free_disk_space_win32(path)

    s = os.statvfs(path)

    free_space = s.f_bavail * s.f_bsize

    if free_space == 0:
        # Try to fallback to using GIO to determine free space
        # This fixes issues with GVFS-mounted iPods (bug 1361)
        try:
            import gio
            file = gio.File(path)
            info = file.query_filesystem_info(
                gio.FILE_ATTRIBUTE_FILESYSTEM_FREE)
            return info.get_attribute_uint64(
                gio.FILE_ATTRIBUTE_FILESYSTEM_FREE)
        except Exception, e:
            log('Free space is zero. Fallback using "gio" failed.',
                traceback=True)
            return free_space
Exemple #9
0
 def write_m3u(self):
     """
     write the list into the playlist on the device
     """
     log('Writing playlist file: %s', self.playlist_file, sender=self)
     playlist_folder = os.path.split(self.playlist_file)[0]
     if not util.make_directory(playlist_folder):
         self.show_message(
             _('Folder %s could not be created.') % playlist_folder,
             _('Error writing playlist'))
     else:
         try:
             fp = open(self.playlist_file, 'w')
             fp.write('#EXTM3U%s' % self.linebreak)
             for icon, checked, filename in self.playlist:
                 fp.write(self.build_extinf(filename))
                 if not checked:
                     fp.write('#')
                 fp.write(filename)
                 fp.write(self.linebreak)
             fp.close()
             self.show_message(
                 _('The playlist on your MP3 player has been updated.'),
                 _('Update successful'))
         except IOError, ioe:
             self.show_message(str(ioe), _('Error writing playlist file'))
Exemple #10
0
def sanitize_filename(filename, max_length=0, use_ascii=False):
    """
    Generate a sanitized version of a filename that can
    be written on disk (i.e. remove/replace invalid
    characters and encode in the native language) and
    trim filename if greater than max_length (0 = no limit).

    If use_ascii is True, don't encode in the native language,
    but use only characters from the ASCII character set.
    """
    global encoding
    if use_ascii:
        e = 'ascii'
    else:
        e = encoding

    if not isinstance(filename, unicode):
        filename = filename.decode(encoding, 'ignore')

    if max_length > 0 and len(filename) > max_length:
        log('Limiting file/folder name "%s" to %d characters.', filename,
            max_length)
        filename = filename[:max_length]

    return re.sub('[/|?*<>:+\[\]\"\\\]', '_',
                  filename.strip().encode(e, 'ignore'))
Exemple #11
0
    def calculate_total_size(self):
        if self.size_attribute is not None:
            (total_size, count) = (0, 0)
            for episode in self.get_selected_episodes():
                try:
                    total_size += int(getattr(episode, self.size_attribute))
                    count += 1
                except:
                    log('Cannot get size for %s', episode.title, sender=self)

            text = []
            if count == 0:
                text.append(_('Nothing selected'))
            text.append(
                N_('%(count)d episode', '%(count)d episodes', count) %
                {'count': count})
            if total_size > 0:
                text.append(_('size: %s') % util.format_filesize(total_size))
            self.labelTotalSize.set_text(', '.join(text))
            self.btnOK.set_sensitive(count > 0)
            self.btnRemoveAction.set_sensitive(count > 0)
            if count > 0:
                self.btnCancel.set_label(gtk.STOCK_CANCEL)
            else:
                self.btnCancel.set_label(gtk.STOCK_CLOSE)
        else:
            self.btnOK.set_sensitive(False)
            self.btnRemoveAction.set_sensitive(False)
            for index, row in enumerate(self.model):
                if self.model.get_value(row.iter, self.COLUMN_TOGGLE) == True:
                    self.btnOK.set_sensitive(True)
                    self.btnRemoveAction.set_sensitive(True)
                    break
            self.labelTotalSize.set_text('')
Exemple #12
0
 def __setattr__(self, name, value):
     if name in self.Settings:
         fieldtype, default = self.Settings[name][:2]
         try:
             if self[name] != fieldtype(value):
                 old_value = self[name]
                 log('Update %s: %s => %s',
                     name,
                     old_value,
                     value,
                     sender=self)
                 self[name] = fieldtype(value)
                 for observer in self.__observers:
                     try:
                         # Notify observer about config change
                         observer(name, old_value, self[name])
                     except:
                         log('Error while calling observer: %s',
                             repr(observer),
                             sender=self,
                             traceback=True)
                 self.schedule_save()
         except:
             raise ValueError('%s has to be of type %s' %
                              (name, fieldtype.__name__))
     else:
         object.__setattr__(self, name, value)
Exemple #13
0
def create_cmml(html, ogg_file):
    soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES)
    startzeit = soup.findAll(text='Startzeit')
    if len(startzeit) == 1:
        m = re.match('(.*)\\.[^\\.]+$', ogg_file)
        if m is not None:
            to_file = m.group(1) + ".cmml"
            cmml = ET.Element('cmml', attrib={'lang': 'en'})
            remove_ws = re.compile('\s+')
            for s in startzeit:
                tr = s.parent.parent.parent
                for row in tr.findNextSiblings(name='tr'):
                    txt = ''
                    tds = row.findAll(name='td')
                    t = remove_ws.sub('', tds[1].string)
                    for c in tds[0].findAll(text=True):
                        txt += c
                    txt = remove_ws.sub(' ', txt)
                    txt = txt.strip()
                    log("found chapter %s at %s" % (txt, t))
                    # totem want's escaped html in the title attribute (not &amp; but &amp;amp;)
                    txt = txt.replace('&', '&amp;')
                    clip = ET.Element('clip')
                    clip.set('id', t)
                    clip.set('start', ('npt:' + t))
                    clip.set('title', txt)
                    cmml.append(clip)
            ET.ElementTree(cmml).write(to_file, encoding='utf-8')
Exemple #14
0
    def _get_icon_from_image(self,image_path, icon_size):
        """
        Load an local image file and transform it into an icon.

        Return a pixbuf scaled to the desired size and may return None
        if the icon creation is impossible (file not found etc).
        """
        if not os.path.exists(image_path):
            return None
        # load image from disc (code adapted from CoverDownloader
        # except that no download is needed here)
        loader = gtk.gdk.PixbufLoader()
        pixbuf = None
        try:
            loader.write(open(image_path, 'rb').read())
            loader.close()
            pixbuf = loader.get_pixbuf()
        except:
            log('Data error while loading image %s', image_path, sender=self)
            return None
        # Now scale the image with ratio (copied from _resize_pixbuf_keep_ratio)
        # Resize if too wide
        if pixbuf.get_width() > icon_size:
            f = float(icon_size)/pixbuf.get_width()
            (width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
            pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
        # Resize if too high
        if pixbuf.get_height() > icon_size:
            f = float(icon_size)/pixbuf.get_height()
            (width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
            pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
        return pixbuf
Exemple #15
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
                log('Episode has no file: %s', track.title, sender=self)
                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)
                log('Cannot convert track time: %s', ve, sender=self)
                released = 0

            t = SyncTrack(track.title,
                          length,
                          modified,
                          modified_sort=timestamp,
                          libgpodtrack=track,
                          playcount=track.playcount,
                          released=released,
                          podcast=track.artist)
            tracks.append(t)
    def addDownloadedItem( self, item):
        # no multithreaded access
        global_lock.acquire()

        downloaded_episodes=self.load_downloaded_episodes()
        already_in_list=item.url in [ episode.url for episode in downloaded_episodes ]
        
        # only append if not already in list
        if not already_in_list:
            downloaded_episodes.append( item)
            self.save_downloaded_episodes( downloaded_episodes)

            # Update metadata on file (if possible and wanted)
            if gl.config.update_tags and libtagupdate.tagging_supported():
                filename=item.local_filename()
                try:
                    libtagupdate.update_metadata_on_file(filename, title=item.title, artist=self.title)
                except:
                    log('Error while calling update_metadata_on_file() :(')

        gl.history_mark_downloaded(item.url)
        self.update_m3u_playlist(downloaded_episodes)
        
        if item.file_type() == 'torrent':
            torrent_filename=item.local_filename()
            destination_filename=util.torrent_filename( torrent_filename)
            gl.invoke_torrent(item.url, torrent_filename, destination_filename)
            
        global_lock.release()
        return not already_in_list
    def write( self, channel):
        doc=xml.dom.minidom.Document()

        rss=doc.createElement( 'rss')
        rss.setAttribute( 'version', '1.0')
        doc.appendChild( rss)

        channele=doc.createElement( 'channel')
        channele.appendChild( self.create_node( doc, 'title', channel.title))
        channele.appendChild( self.create_node( doc, 'description', channel.description))
        channele.appendChild( self.create_node( doc, 'link', channel.link))
        rss.appendChild( channele)

        for episode in channel:
            if episode.is_downloaded():
                rss.appendChild( self.create_item( doc, episode))

        try:
            fp=open( self.filename, 'w')
            fp.write( doc.toxml( encoding='utf-8'))
            fp.close()
        except:
            log( 'Could not open file for writing: %s', self.filename, sender=self)
            return False
        
        return True
    def read_device(self):
        """
        read all files from the device
        """
        log('Reading files from %s', self._config.mp3_player_folder, sender=self)
        tracks = []
        for root, dirs, files in os.walk(self._config.mp3_player_folder):
            for file in files:
                filename = os.path.join(root, file)

                if filename == self.playlist_file or fnmatch.fnmatch(filename, '*.dat') or fnmatch.fnmatch(filename, '*.DAT'):
                    # We don't want to have our playlist file as
                    # an entry in our file list, so skip it!
                    # We also don't want to include dat files
                    continue

                if self._config.mp3_player_playlist_absolute_path:
                    filename = filename[len(self.mountpoint):]
                else:
                    filename = util.relpath(os.path.dirname(self.playlist_file),
                                            os.path.dirname(filename)) + \
                               os.sep + os.path.basename(filename)

                if self._config.mp3_player_playlist_win_path:
                    filename = filename.replace(os.sep, '\\')

                tracks.append(filename)
        return tracks
    def calculate_total_size( self):
        if self.size_attribute is not None:
            (total_size, count) = (0, 0)
            for episode in self.get_selected_episodes():
                try:
                    total_size += int(getattr( episode, self.size_attribute))
                    count += 1
                except:
                    log( 'Cannot get size for %s', episode.title, sender = self)

            text = []
            if count == 0:
                text.append(_('Nothing selected'))
            else:
                text.append(N_('%(count)d episode', '%(count)d episodes', count) % {'count':count})

            if total_size > 0:
                text.append(_('size: %s') % util.format_filesize(total_size))
            self.labelTotalSize.set_text(', '.join(text))
            self.btnOK.set_sensitive(count>0)
            self.btnRemoveAction.set_sensitive(count>0)
            if count > 0:
                self.btnCancel.set_label(gtk.STOCK_CANCEL)
            else:
                self.btnCancel.set_label(gtk.STOCK_CLOSE)
        else:
            self.btnOK.set_sensitive(False)
            self.btnRemoveAction.set_sensitive(False)
            for index, row in enumerate(self.model):
                if self.model.get_value(row.iter, self.COLUMN_TOGGLE) == True:
                    self.btnOK.set_sensitive(True)
                    self.btnRemoveAction.set_sensitive(True)
                    break
            self.labelTotalSize.set_text('')
Exemple #20
0
def get_header_param(headers, param, header_name):
    """Extract a HTTP header parameter from a dict

    Uses the "email" module to retrieve parameters
    from HTTP headers. This can be used to get the
    "filename" parameter of the "content-disposition"
    header for downloads to pick a good filename.

    Returns None if the filename cannot be retrieved.
    """
    try:
        headers_string = ['%s:%s' % (k, v) for k, v in headers.items()]
        msg = email.message_from_string('\n'.join(headers_string))
        if header_name in msg:
            value = msg.get_param(param, header=header_name)
            if value is None:
                return None
            decoded_list = email.Header.decode_header(value)
            value = []
            for part, encoding in decoded_list:
                if encoding:
                    value.append(part.decode(encoding))
                else:
                    value.append(unicode(part))
            return u''.join(value)
    except Exception, e:
        log('Error trying to get %s from %s: %s', \
                param, header_name, str(e), traceback=True)
Exemple #21
0
def create_cmml(html, ogg_file):
    soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES)
    time_re = text = re.compile("\\d{1,2}(:\\d{2}){2}")
    times = soup.findAll(text=time_re)
    if len(times) > 0:
        m = re.match('(.*)\\.[^\\.]+$', ogg_file)
        if m is not None:
            to_file = m.group(1) + ".cmml"
            cmml = ET.Element('cmml', attrib={'lang': 'en'})
            remove_ws = re.compile('\s+')
            for t in times:
                txt = ''
                for c in t.parent.findAll(text=True):
                    if c is not t: txt += c
                txt = remove_ws.sub(' ', txt)
                txt = txt.strip()
                log("found chapter %s at %s" % (txt, t))
                # totem want's escaped html in the title attribute (not &amp; but &amp;amp;)
                txt = txt.replace('&', '&amp;')
                clip = ET.Element('clip')
                clip.set('id', t)
                clip.set('start', ('npt:' + t))
                clip.set('title', txt)
                cmml.append(clip)
            ET.ElementTree(cmml).write(to_file, encoding='utf-8')
Exemple #22
0
    def run( self):
        self.download_id=services.download_status_manager.reserve_download_id()
        services.download_status_manager.register_download_id( self.download_id, self)

        # Initial status update
        services.download_status_manager.update_status( self.download_id, episode=self.episode.title, url=self.episode.url, speed=self.speed, progress=self.progress)

        acquired=services.download_status_manager.s_acquire()
        try:
            try:
                if self.cancelled:
                    return
         
                util.delete_file( self.tempname)
                self.downloader.retrieve( self.episode.url, self.tempname, reporthook=self.status_updated)
                shutil.move( self.tempname, self.filename)
                self.channel.addDownloadedItem( self.episode)
                services.download_status_manager.download_completed(self.download_id)
            finally:
                services.download_status_manager.remove_download_id( self.download_id)
                services.download_status_manager.s_release( acquired)
        except DownloadCancelledException:
            log('Download has been cancelled: %s', self.episode.title, traceback=None, sender=self)
        except IOError, ioe:
            if self.notification is not None:
                title=ioe.strerror
                message=_('An error happened while trying to download <b>%s</b>.') % ( saxutils.escape( self.episode.title), )
                self.notification( message, title)
            log( 'Error "%s" while downloading "%s": %s', ioe.strerror, self.episode.title, ioe.filename, sender=self)
Exemple #23
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
                log('I am going to spawn a new worker thread.', sender=self)

                # 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)
                worker.start()
Exemple #24
0
    def add_tracks(self, tracklist=[], force_played=False):
        for id, track in enumerate(tracklist):
            if self.cancelled:
                return False

            self.notify('progress', id+1, len(tracklist))

            if not track.is_downloaded():
                continue

            if track.is_played() and gl.config.only_sync_not_played and not force_played:
                continue

            if track.file_type() not in self.allowed_types:
                continue

            added=self.add_track(track)

            if gl.config.on_sync_mark_played:
                log('Marking as played on transfer: %s', track.url, sender=self)
                gl.history_mark_played(track.url)

            if added and gl.config.on_sync_delete:
                log('Removing episode after transfer: %s', track.url, sender=self)
                track.delete_from_disk()
        return True
Exemple #25
0
    def calculate_total_size( self):
        if self.size_attribute is not None:
            (total_size, count) = (0, 0)
            for episode in self.get_selected_episodes():
                try:
                    total_size += int(getattr( episode, self.size_attribute))
                    count += 1
                except:
                    log( 'Cannot get size for %s', episode.title, sender = self)

            text = []
            if count == 0: 
                text.append(_('Nothing selected'))
            else:
                text.append(N_('%d episode', '%d episodes', count) % count)
            if total_size > 0: 
                text.append(_('size: %s') % util.format_filesize(total_size))
            self.labelTotalSize.set_text(', '.join(text))
            self.btnOK.set_sensitive(count>0)
            self.btnRemoveAction.set_sensitive(count>0)
        else:
            selection = self.treeviewEpisodes.get_selection()
            selected_rows = selection.count_selected_rows()
            self.btnOK.set_sensitive(selected_rows > 0)
            self.btnRemoveAction.set_sensitive(selected_rows > 0)
            self.labelTotalSize.set_text('')
Exemple #26
0
    def calculate_total_size( self):
        if self.size_attribute is not None:
            (total_size, count) = (0, 0)
            for episode in self.get_selected_episodes():
                try:
                    total_size += int(getattr( episode, self.size_attribute))
                    count += 1
                except:
                    log( 'Cannot get size for %s', episode.title, sender = self)

            text = []
            if count == 0: 
                text.append(_('Nothing selected'))
            else:
                text.append(N_('%(count)d episode', '%(count)d episodes', count) % {'count':count})
            if total_size > 0: 
                text.append(_('size: %s') % util.format_filesize(total_size))
            self.labelTotalSize.set_text(', '.join(text))
            self.btnOK.set_sensitive(count>0)
            self.btnRemoveAction.set_sensitive(count>0)
        else:
            selection = self.treeviewEpisodes.get_selection()
            selected_rows = selection.count_selected_rows()
            self.btnOK.set_sensitive(selected_rows > 0)
            self.btnRemoveAction.set_sensitive(selected_rows > 0)
            self.labelTotalSize.set_text('')
Exemple #27
0
def create_cmml(html, ogg_file):
    soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES)
    time_re  = text=re.compile("\\d{1,2}(:\\d{2}){2}")
    times = soup.findAll(text=time_re)
    if len(times) > 0:
        m = re.match('(.*)\\.[^\\.]+$',ogg_file)
        if m is not None:
            to_file = m.group(1) + ".cmml"
            cmml = ET.Element('cmml',attrib={'lang':'en'})
            remove_ws = re.compile('\s+')
            for t in times:
                txt = ''
                for c in t.parent.findAll(text=True):
                    if c is not t: txt += c
                txt = remove_ws.sub(' ', txt)
                txt = txt.strip()
                log("found chapter %s at %s"%(txt,t))
                # totem want's escaped html in the title attribute (not &amp; but &amp;amp;)
                txt = txt.replace('&','&amp;')
                clip = ET.Element('clip')
                clip.set('id',t)
                clip.set( 'start', ('npt:'+t))
                clip.set('title',txt)
                cmml.append(clip)
            ET.ElementTree(cmml).write(to_file,encoding='utf-8')
Exemple #28
0
    def __mtp_to_date(self, mtp):
        """
        this parse the mtp's string representation for date
        according to specifications (YYYYMMDDThhmmss.s) to
        a python time object

        """
        if not mtp:
            return None

        try:
            mtp = mtp.replace(" ", "0") # replace blank with 0 to fix some invalid string
            d = time.strptime(mtp[:8] + mtp[9:13],"%Y%m%d%H%M%S")
            _date = calendar.timegm(d)
            if len(mtp)==20:
                # TIME ZONE SHIFTING: the string contains a hour/min shift relative to a time zone
                try:
                    shift_direction=mtp[15]
                    hour_shift = int(mtp[16:18])
                    minute_shift = int(mtp[18:20])
                    shift_in_sec = hour_shift * 3600 + minute_shift * 60
                    if shift_direction == "+":
                        _date += shift_in_sec
                    elif shift_direction == "-":
                        _date -= shift_in_sec
                    else:
                        raise ValueError("Expected + or -")
                except Exception, exc:
                    log('WARNING: ignoring invalid time zone information for %s (%s)', mtp, exc, sender=self)
            return max( 0, _date )
Exemple #29
0
 def commit(self):
     self.lock.acquire()
     try:
         self.log("COMMIT")
         self.db.commit()
     except ProgrammingError, e:
         log('Error commiting changes: %s', e, sender=self, traceback=True)
Exemple #30
0
def get_free_disk_space(path):
    """
    Calculates the free disk space available to the current user
    on the file system that contains the given path.

    If the path (or its parent folder) does not yet exist, this
    function returns zero.
    """

    if not os.path.exists(path):
        return 0

    if gpodder.win32:
        return get_free_disk_space_win32(path)

    s = os.statvfs(path)

    free_space = s.f_bavail * s.f_bsize

    if free_space == 0:
        # Try to fallback to using GIO to determine free space
        # This fixes issues with GVFS-mounted iPods (bug 1361)
        try:
            import gio

            file = gio.File(path)
            info = file.query_filesystem_info(gio.FILE_ATTRIBUTE_FILESYSTEM_FREE)
            return info.get_attribute_uint64(gio.FILE_ATTRIBUTE_FILESYSTEM_FREE)
        except Exception, e:
            log('Free space is zero. Fallback using "gio" failed.', traceback=True)
            return free_space
Exemple #31
0
def object_string_formatter(s, **kwargs):
    """
    Makes attributes of object passed in as keyword 
    arguments available as {OBJECTNAME.ATTRNAME} in 
    the passed-in string and returns a string with 
    the above arguments replaced with the attribute 
    values of the corresponding object.

    Example:

    e = Episode()
    e.title = 'Hello'
    s = '{episode.title} World'
    
    print object_string_formatter( s, episode = e)
          => 'Hello World'
    """
    result = s
    for (key, o) in kwargs.items():
        matches = re.findall(r"\{%s\.([^\}]+)\}" % key, s)
        for attr in matches:
            if hasattr(o, attr):
                try:
                    from_s = "{%s.%s}" % (key, attr)
                    to_s = getattr(o, attr)
                    result = result.replace(from_s, to_s)
                except:
                    log('Could not replace attribute "%s" in string "%s".', attr, s)

    return result
Exemple #32
0
def get_episode_info_from_url(url):
    """
    Try to get information about a podcast episode by sending
    a HEAD request to the HTTP server and parsing the result.

    The return value is a dict containing all fields that 
    could be parsed from the URL. This currently contains:
    
      "length": The size of the file in bytes
      "pubdate": The unix timestamp for the pubdate

    If there is an error, this function returns {}. This will
    only function with http:// and https:// URLs.
    """
    if not (url.startswith("http://") or url.startswith("https://")):
        return {}

    r = http_request(url)
    result = {}

    log("Trying to get metainfo for %s", url)

    if "content-length" in r.msg:
        try:
            length = int(r.msg["content-length"])
            result["length"] = length
        except ValueError, e:
            log("Error converting content-length header.")
Exemple #33
0
def patch_feedparser():
    """Fix a bug in feedparser 4.1
    This replaces the mapContentType method of the
    _FeedParserMixin class to correctly detect the
    "plain" content type as "text/plain".

    See also:
    http://code.google.com/p/feedparser/issues/detail?id=80

    Added by Thomas Perl for gPodder 2007-12-29
    """
    def mapContentType2(self, contentType):
        contentType=contentType.lower()
        if contentType == 'text' or contentType == 'plain':
            contentType='text/plain'
        elif contentType == 'html':
            contentType='text/html'
        elif contentType == 'xhtml':
            contentType='application/xhtml+xml'
        return contentType

    try:
        if feedparser._FeedParserMixin().mapContentType('plain') == 'plain':
            log('Patching feedparser module... (mapContentType bugfix)')
            feedparser._FeedParserMixin.mapContentType=mapContentType2
    except:
        log('Warning: feedparser unpatched - might be broken!')
Exemple #34
0
    def upgrade_table(self, table_name, fields, index_list):
        """
        Creates a table or adds fields to it.
        """
        cur = self.cursor(lock=True)

        cur.execute("PRAGMA table_info(%s)" % table_name)
        available = cur.fetchall()

        if not available:
            log('Creating table %s', table_name, sender=self)
            columns = ', '.join(' '.join(f) for f in fields)
            sql = "CREATE TABLE %s (%s)" % (table_name, columns)
            cur.execute(sql)
        else:
            # Table info columns, as returned by SQLite
            ID, NAME, TYPE, NULL, DEFAULT = range(5)
            existing = set(column[NAME] for column in available)

            for field_name, field_type in fields:
                if field_name not in existing:
                    log('Adding column: %s.%s (%s)', table_name, field_name, field_type, sender=self)
                    cur.execute("ALTER TABLE %s ADD COLUMN %s %s" % (table_name, field_name, field_type))

        for column, typ in index_list:
            cur.execute('CREATE %s IF NOT EXISTS idx_%s ON %s (%s)' % (typ, column, table_name, column))

        self.lock.release()
    def __extract_shownotes(self, imagefile):
        """
        extract shownotes from the FRONT_COVER.jpeg
        """
        shownotes = None
        password = "******"
        shownotes_file = "/tmp/shownotes.txt"

        myprocess = subprocess.Popen(
            ["steghide", "extract", "-f", "-p", password, "-sf", imagefile, "-xf", shownotes_file],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        (stdout, stderr) = myprocess.communicate()

        os.remove(imagefile)

        if stderr.startswith("wrote extracted data to"):
            # read shownote file
            f = open(shownotes_file)
            shownotes = f.read()
            f.close()
        else:
            log(u"Error extracting shownotes from the image file %s" % imagefile)

        return shownotes
Exemple #36
0
 def __setattr__( self, name, value):
     if name in self.Settings:
         ( fieldtype, default )=self.Settings[name]
         try:
             if self[name] != fieldtype(value):
                 log( 'Update: %s=%s', name, value, sender=self)
                 old_value=self[name]
                 self[name]=fieldtype(value)
                 for observer in self.__observers:
                     try:
                         # Notify observer about config change
                         observer(name, old_value, self[name])
                     except:
                         log('Error while calling observer: %s', repr(observer), sender=self)
                 for row in self.__model:
                     if row[0] == name:
                         row[2]=str(fieldtype(value))
                         if self[name] == default:
                             weight=pango.WEIGHT_NORMAL
                         else:
                             weight=pango.WEIGHT_BOLD
                         row[5]=weight
                 self.schedule_save()
         except:
             raise ValueError( '%s has to be of type %s' % ( name, fieldtype.__name__ ))
     else:
         object.__setattr__( self, name, value)
Exemple #37
0
    def copy_player_cover_art(self, destination, local_filename, \
                                  cover_dst_name, cover_dst_format, \
                                  cover_dst_size):
        """
        Try to copy the channel cover to the podcast folder on the MP3
        player. This makes the player, e.g. Rockbox (rockbox.org), display the
        cover art in its interface.

        You need the Python Imaging Library (PIL) installed to be able to
        convert the cover file to a Bitmap file, which Rockbox needs.
        """
        try:
            cover_loc = os.path.join(os.path.dirname(local_filename), 'folder.jpg')
            cover_dst = os.path.join(destination, cover_dst_name)
            if os.path.isfile(cover_loc):
                log('Creating cover art file on player', sender=self)
                log('Cover art size is %s', cover_dst_size, sender=self)
                size = (cover_dst_size, cover_dst_size)
                try:
                    cover = Image.open(cover_loc)
                    cover.thumbnail(size)
                    cover.save(cover_dst, cover_dst_format)
                except IOError:
                    log('Cannot create %s (PIL?)', cover_dst, traceback=True, sender=self)
                return True
            else:
                log('No cover available to set as player cover', sender=self)
                return True
        except:
            log('Error getting cover using channel cover', sender=self)
        return False
Exemple #38
0
    def set_cover_art(self, track, local_filename):
        try:
            tag = eyeD3.Tag()
            if tag.link(local_filename):
                if 'APIC' in tag.frames and len(tag.frames['APIC']) > 0:
                    apic = tag.frames['APIC'][0]

                    extension = 'jpg'
                    if apic.mimeType == 'image/png':
                        extension = 'png'
                    cover_filename = '%s.cover.%s' (local_filename, extension)

                    cover_file = open(cover_filename, 'w')
                    cover_file.write(apic.imageData)
                    cover_file.close()

                    gpod.itdb_track_set_thumbnails(track, cover_filename)
                    return True
        except:
            log('Error getting cover using eyeD3', sender=self)

        try:
            cover_filename = os.path.join(os.path.dirname(local_filename), 'folder.jpg')

            if os.path.isfile(cover_filename):
                gpod.itdb_track_set_thumbnails(track, cover_filename)
                return True
        except:
            log('Error getting cover using channel cover', sender=self)

        return False
Exemple #39
0
def get_track_length(filename):
    length = gstreamer.get_track_length(filename)
    if length is not None:
        return length

    if util.find_command('mplayer') is not None:
        try:
            mplayer_output = os.popen('mplayer -msglevel all=-1 -identify -vo null -ao null -frames 0 "%s" 2>/dev/null' % filename).read()
            return int(float(mplayer_output[mplayer_output.index('ID_LENGTH'):].splitlines()[0][10:])*1000)
        except:
            pass
    else:
        log('Please install MPlayer for track length detection.')

    try:
        mad_info = mad.MadFile(filename)
        return int(mad_info.total_time())
    except:
        pass
    
    try:
        eyed3_info = eyeD3.Mp3AudioFile(filename)
        return int(eyed3_info.getPlayTime()*1000)
    except:
        pass

    return int(60*60*1000*3) # Default is three hours (to be on the safe side)
Exemple #40
0
    def set_cover_art(self, track, local_filename):
        try:
            tag = eyeD3.Tag()
            if tag.link(local_filename):
                if 'APIC' in tag.frames and len(tag.frames['APIC']) > 0:
                    apic = tag.frames['APIC'][0]

                    extension = 'jpg'
                    if apic.mimeType == 'image/png':
                        extension = 'png'
                    cover_filename = '%s.cover.%s' (local_filename, extension)

                    cover_file = open(cover_filename, 'w')
                    cover_file.write(apic.imageData)
                    cover_file.close()

                    gpod.itdb_track_set_thumbnails(track, cover_filename)
                    return True
        except:
            log('Error getting cover using eyeD3', sender=self)

        try:
            cover_filename = os.path.join(os.path.dirname(local_filename), 'folder.jpg')

            if os.path.isfile(cover_filename):
                gpod.itdb_track_set_thumbnails(track, cover_filename)
                return True
        except:
            log('Error getting cover using channel cover', sender=self)

        return False
Exemple #41
0
def calculate_size(path):
    """
    Tries to calculate the size of a directory, including any 
    subdirectories found. The returned value might not be 
    correct if the user doesn't have appropriate permissions 
    to list all subdirectories of the given path.
    """
    if path is None:
        return 0L

    if os.path.dirname(path) == "/":
        return 0L

    if os.path.isfile(path):
        return os.path.getsize(path)

    if os.path.isdir(path) and not os.path.islink(path):
        sum = os.path.getsize(path)

        try:
            for item in os.listdir(path):
                try:
                    sum += calculate_size(os.path.join(path, item))
                except:
                    log("Cannot get size for %s", path)
        except:
            log("Cannot access: %s", path)

        return sum

    return 0L
Exemple #42
0
 def log(self, message, *args, **kwargs):
     if False:
         try:
             message = message % args
             log('%s', message, sender=self)
         except TypeError, e:
             log('Exception in log(): %s: %s', e, message, sender=self)
Exemple #43
0
    def load(self, filename=None):
        if filename is not None:
            self.__filename = filename

        parser = ConfigParser.RawConfigParser()

        if os.path.exists(self.__filename):
            try:
                parser.read(self.__filename)
            except:
                log('Cannot parse config file: %s', self.__filename,
                        sender=self, traceback=True)

        for key, value in self.Settings.items():
            fieldtype, default = value[:2]
            try:
                if not parser.has_section(self.__section):
                    value = default
                elif fieldtype == int:
                    value = parser.getint(self.__section, key)
                elif fieldtype == float:
                    value = parser.getfloat(self.__section, key)
                elif fieldtype == bool:
                    value = parser.getboolean(self.__section, key)
                else:
                    value = fieldtype(parser.get(self.__section, key))
            except:
                log('Invalid value in %s for %s: %s', self.__filename,
                        key, value, sender=self, traceback=True)
                value = default

            self[key] = value
Exemple #44
0
 def set_subscriptions(self, urls):
     if self.can_access_webservice():
         log('Uploading (overwriting) subscriptions...')
         self._client.put_subscriptions(self.device_id, urls)
         log('Subscription upload done.')
     else:
         raise Exception('Webservice access not enabled')
Exemple #45
0
    def on_create_window(self):
        self.textview.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#ffffff'))
        if self._config.enable_html_shownotes:
            try:
                import webkit
                webview_signals = gobject.signal_list_names(webkit.WebView)
                if 'navigation-policy-decision-requested' in webview_signals:
                    setattr(self, 'have_webkit', True)
                    setattr(self, 'htmlview', webkit.WebView())
                else:
                    log('Your WebKit is too old (see bug 1001).', sender=self)
                    setattr(self, 'have_webkit', False)

                def navigation_policy_decision(wv, fr, req, action, decision):
                    REASON_LINK_CLICKED, REASON_OTHER = 0, 5
                    if action.get_reason() == REASON_LINK_CLICKED:
                        util.open_website(req.get_uri())
                        decision.ignore()
                    elif action.get_reason() == REASON_OTHER:
                        decision.use()
                    else:
                        decision.ignore()

                self.htmlview.connect('navigation-policy-decision-requested', \
                        navigation_policy_decision)

                self.scrolled_window.remove(self.scrolled_window.get_child())
                self.scrolled_window.add(self.htmlview)
                self.textview = None
                self.htmlview.load_html_string('', '')
                self.htmlview.show()
            except ImportError:
                setattr(self, 'have_webkit', False)
        else:
            setattr(self, 'have_webkit', False)
Exemple #46
0
def get_header_param(headers, param, header_name):
    """Extract a HTTP header parameter from a dict

    Uses the "email" module to retrieve parameters
    from HTTP headers. This can be used to get the
    "filename" parameter of the "content-disposition"
    header for downloads to pick a good filename.

    Returns None if the filename cannot be retrieved.
    """
    try:
        headers_string = ['%s:%s'%(k,v) for k,v in headers.items()]
        msg = email.message_from_string('\n'.join(headers_string))
        if header_name in msg:
            value = msg.get_param(param, header=header_name)
            if value is None:
                return None
            decoded_list = email.Header.decode_header(value)
            value = []
            for part, encoding in decoded_list:
                if encoding:
                    value.append(part.decode(encoding))
                else:
                    value.append(unicode(part))
            return u''.join(value)
    except Exception, e:
        log('Error trying to get %s from %s: %s', \
                param, header_name, str(e), traceback=True)
Exemple #47
0
    def __init__(self, episode, config):
        self.__status = DownloadTask.INIT
        self.__status_changed = True
        self.__episode = episode
        self._config = config

        # Set names for the downloads list
        self.markup_name = saxutils.escape(self.__episode.title)
        self.markup_podcast_name = saxutils.escape(self.__episode.channel.title)

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

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

        # Variables for speed limit and speed calculation
        self.__start_time = 0
        self.__start_blocks = 0
        self.__limit_rate_value = self._config.limit_rate_value
        self.__limit_rate = self._config.limit_rate

        # If the tempname already exists, set progress accordingly
        if os.path.exists(self.tempname):
            try:
                already_downloaded = os.path.getsize(self.tempname)
                if self.total_size > 0:
                    self.progress = max(0.0, min(1.0, float(already_downloaded)/self.total_size))
            except OSError, os_error:
                log('Error while getting size for existing file: %s', os_error, sender=self)
Exemple #48
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
                log('I am going to spawn a new worker thread.', sender=self)

                # 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)
                worker.start()
Exemple #49
0
 def set_subscriptions(self, urls):
     if self.can_access_webservice():
         log('Uploading (overwriting) subscriptions...')
         self._client.put_subscriptions(self.device_id, urls)
         log('Subscription upload done.')
     else:
         raise Exception('Webservice access not enabled')
Exemple #50
0
    def __mtp_to_date(self, mtp):
        """
        this parse the mtp's string representation for date
        according to specifications (YYYYMMDDThhmmss.s) to
        a python time object

        """
        if not mtp:
            return None

        try:
            mtp = mtp.replace(" ", "0") # replace blank with 0 to fix some invalid string
            d = time.strptime(mtp[:8] + mtp[9:13],"%Y%m%d%H%M%S")
            _date = calendar.timegm(d)
            if len(mtp)==20:
                # TIME ZONE SHIFTING: the string contains a hour/min shift relative to a time zone
                try:
                    shift_direction=mtp[15]
                    hour_shift = int(mtp[16:18])
                    minute_shift = int(mtp[18:20])
                    shift_in_sec = hour_shift * 3600 + minute_shift * 60
                    if shift_direction == "+":
                        _date += shift_in_sec
                    elif shift_direction == "-":
                        _date -= shift_in_sec
                    else:
                        raise ValueError("Expected + or -")
                except Exception, exc:
                    log('WARNING: ignoring invalid time zone information for %s (%s)', mtp, exc, sender=self)
            return max( 0, _date )
Exemple #51
0
    def copy_player_cover_art(self, destination, local_filename, \
                                  cover_dst_name, cover_dst_format, \
                                  cover_dst_size):
        """
        Try to copy the channel cover to the podcast folder on the MP3
        player. This makes the player, e.g. Rockbox (rockbox.org), display the
        cover art in its interface.

        You need the Python Imaging Library (PIL) installed to be able to
        convert the cover file to a Bitmap file, which Rockbox needs.
        """
        try:
            cover_loc = os.path.join(os.path.dirname(local_filename), 'folder.jpg')
            cover_dst = os.path.join(destination, cover_dst_name)
            if os.path.isfile(cover_loc):
                log('Creating cover art file on player', sender=self)
                log('Cover art size is %s', cover_dst_size, sender=self)
                size = (cover_dst_size, cover_dst_size)
                try:
                    cover = Image.open(cover_loc)
                    cover.thumbnail(size)
                    cover.save(cover_dst, cover_dst_format)
                except IOError:
                    log('Cannot create %s (PIL?)', cover_dst, traceback=True, sender=self)
                return True
            else:
                log('No cover available to set as player cover', sender=self)
                return True
        except:
            log('Error getting cover using channel cover', sender=self)
        return False
Exemple #52
0
 def apply_fixes(self):
     # Here you can add fixes in case syntax changes. These will be
     # applied whenever a configuration file is loaded.
     if '{channel' in self.custom_sync_name:
         log('Fixing OLD syntax {channel.*} => {podcast.*} in custom_sync_name.',
             sender=self)
         self.custom_sync_name = self.custom_sync_name.replace(
             '{channel.', '{podcast.')
Exemple #53
0
 def remove_observer(self, callback):
     """
     Remove an observer previously added to this object.
     """
     if callback in self.__observers:
         self.__observers.remove(callback)
     else:
         log('Observer not added :%s', repr(callback), sender=self)
Exemple #54
0
 def purge(self):
     for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist):
         if gpod.itdb_filename_on_ipod(track) is None:
             log('Episode has no file: %s', track.title, sender=self)
             # self.remove_track_gpod(track)
         elif track.playcount > 0  and not track.rating:
             log('Purging episode: %s', track.title, sender=self)
             self.remove_track_gpod(track)
Exemple #55
0
    def remove_track(self, sync_track):
        self.notify('status', _('Removing %s') % sync_track.mtptrack.title)
        log("removing %s", sync_track.mtptrack.title, sender=self)

        try:
            self.__MTPDevice.delete_object(sync_track.mtptrack.item_id)
        except Exception, exc:
            log('unable remove file %s (%s)', sync_track.mtptrack.filename, exc, sender=self)
Exemple #56
0
 def __init__(self, filename):
     if filename is None:
         log('OPML Exporter with None filename', sender=self)
         self.filename = None
     elif filename.endswith('.opml') or filename.endswith('.xml'):
         self.filename = filename
     else:
         self.filename = '%s.opml' % (filename, )
Exemple #57
0
    def close(self):
        log("closing %s", self.get_name(), sender=self)
        self.notify('status', _('Closing %s') % self.get_name())

        try:
            self.__MTPDevice.disconnect()
        except Exception, exc:
            log('unable to close %s (%s)', self.get_name(), exc, sender=self)
            return False
Exemple #58
0
 def __init__(self, config):
     Device.__init__(self, config)
     self.__model_name = None
     try:
         self.__MTPDevice = MTP()
     except NameError, e:
         # pymtp not available / not installed (see bug 924)
         log('pymtp not found: %s', str(e), sender=self)
         self.__MTPDevice = None
Exemple #59
0
 def toggle_flag(self, name):
     if name in self.Settings:
         (fieldtype, default) = self.Settings[name][:2]
         if fieldtype == bool:
             setattr(self, name, not getattr(self, name))
         else:
             log('Cannot toggle value: %s (not boolean)', name, sender=self)
     else:
         log('Invalid setting name: %s', name, sender=self)