2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
#
|
|
|
|
# gPodder
|
|
|
|
# Copyright (c) 2005 Thomas Perl <thp@perli.net>
|
|
|
|
# Released under the GNU General Public License (GPL)
|
|
|
|
#
|
|
|
|
|
|
|
|
#
|
|
|
|
# libpodcasts.py -- data classes for gpodder
|
|
|
|
# thomas perl <thp@perli.net> 20051029
|
|
|
|
#
|
|
|
|
#
|
|
|
|
|
|
|
|
import gtk
|
|
|
|
import gobject
|
2006-03-19 18:44:47 +01:00
|
|
|
import htmlentitydefs
|
2005-11-21 19:21:25 +01:00
|
|
|
|
2006-02-04 11:37:23 +01:00
|
|
|
import libgpodder
|
|
|
|
|
2006-03-03 21:04:25 +01:00
|
|
|
from os.path import exists
|
2006-03-29 14:41:34 +02:00
|
|
|
from os.path import basename
|
2006-04-04 00:27:57 +02:00
|
|
|
from os.path import splitext
|
2006-03-03 21:04:25 +01:00
|
|
|
|
2006-02-04 11:37:23 +01:00
|
|
|
from liblocdbwriter import writeLocalDB
|
|
|
|
from liblocdbreader import readLocalDB
|
|
|
|
|
2006-03-03 21:04:25 +01:00
|
|
|
from threading import Event
|
|
|
|
from libwget import downloadThread
|
2006-02-26 00:13:29 +01:00
|
|
|
import re
|
|
|
|
|
2006-04-04 00:27:57 +02:00
|
|
|
import md5
|
|
|
|
|
2005-11-21 19:21:25 +01:00
|
|
|
class podcastChannel(object):
|
2006-03-03 21:04:25 +01:00
|
|
|
"""holds data for a complete channel"""
|
2005-11-21 19:21:25 +01:00
|
|
|
def __init__( self, url = "", title = "", link = "", description = ""):
|
|
|
|
self.url = url
|
|
|
|
self.title = title
|
|
|
|
self.link = link
|
2006-02-26 00:13:29 +01:00
|
|
|
self.description = stripHtml( description)
|
2005-11-21 19:21:25 +01:00
|
|
|
self.items = []
|
2006-03-03 21:04:25 +01:00
|
|
|
self.image = None
|
|
|
|
self.shortname = None
|
|
|
|
self.downloaded = None
|
|
|
|
self.__filename = None
|
|
|
|
self.__download_dir = None
|
2006-04-07 03:43:06 +02:00
|
|
|
# should this channel be synced to devices? (ex: iPod)
|
|
|
|
self.sync_to_devices = True
|
2006-03-03 21:04:25 +01:00
|
|
|
|
|
|
|
# Create all the properties
|
|
|
|
def get_filename(self):
|
|
|
|
if self.__filename == None:
|
|
|
|
self.__filename = ""
|
|
|
|
|
|
|
|
for char in self.title.lower():
|
|
|
|
if (char >= 'a' and char <= 'z') or (char >= 'A' and char <= 'Z') or (char >= '1' and char <= '9'):
|
|
|
|
self.__filename = self.__filename + char
|
|
|
|
|
|
|
|
if self.__filename == "":
|
|
|
|
self.__filename = "__unknown__"
|
|
|
|
|
|
|
|
return self.__filename
|
|
|
|
|
|
|
|
def set_filename(self, value):
|
|
|
|
self.__filename = value
|
|
|
|
|
|
|
|
filename = property(fget=get_filename,
|
|
|
|
fset=set_filename)
|
2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
def addItem( self, item):
|
|
|
|
self.items.append( item)
|
2006-02-04 11:37:23 +01:00
|
|
|
|
2006-04-07 03:43:06 +02:00
|
|
|
def get_localdb_channel( self):
|
|
|
|
ch = None
|
|
|
|
try:
|
|
|
|
locdb_reader = readLocalDB()
|
|
|
|
locdb_reader.parseXML( self.index_file)
|
|
|
|
return locdb_reader.channel
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def set_localdb_channel( self, channel):
|
|
|
|
if channel != None:
|
|
|
|
try:
|
|
|
|
writeLocalDB( self.index_file, channel)
|
|
|
|
except:
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print 'Cannot save localDB channel in set_localdb_channel( %s)' % channel.title
|
|
|
|
|
|
|
|
def set_metadata_from_localdb( self):
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print 'Reading metadata from localdb: %s' % self.index_file
|
|
|
|
libgpodder.getLock()
|
|
|
|
ch = self.get_localdb_channel()
|
|
|
|
if ch != None:
|
|
|
|
self.copy_metadata_from( ch)
|
|
|
|
libgpodder.releaseLock()
|
|
|
|
|
|
|
|
def save_metadata_to_localdb( self):
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print 'Saving metadata to localdb: %s' % self.index_file
|
|
|
|
libgpodder.getLock()
|
|
|
|
ch = self.get_localdb_channel()
|
|
|
|
if ch != None:
|
|
|
|
ch.copy_metadata_from( self)
|
|
|
|
self.set_localdb_channel( ch)
|
|
|
|
libgpodder.releaseLock()
|
|
|
|
|
|
|
|
def copy_metadata_from( self, ch):
|
|
|
|
# copy all metadata fields
|
|
|
|
self.sync_to_devices = ch.sync_to_devices
|
|
|
|
|
2006-02-04 11:37:23 +01:00
|
|
|
def addDownloadedItem( self, item):
|
2006-03-24 20:08:59 +01:00
|
|
|
# no multithreaded access
|
2006-03-19 15:21:48 +01:00
|
|
|
libgpodder.getLock()
|
2006-03-04 21:45:01 +01:00
|
|
|
localdb = self.index_file
|
2006-02-04 11:37:23 +01:00
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print "localdb: " + localdb
|
|
|
|
|
|
|
|
try:
|
|
|
|
locdb_reader = readLocalDB()
|
2006-02-04 18:29:17 +01:00
|
|
|
locdb_reader.parseXML( localdb)
|
2006-02-04 11:37:23 +01:00
|
|
|
self.downloaded = locdb_reader.channel
|
|
|
|
except:
|
2006-03-31 18:20:18 +02:00
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print "no local db found or local db error: creating new.."
|
2006-02-04 11:37:23 +01:00
|
|
|
self.downloaded = podcastChannel( self.url, self.title, self.link, self.description)
|
2006-02-04 18:29:17 +01:00
|
|
|
|
2006-04-03 21:43:59 +02:00
|
|
|
already_in_list = False
|
|
|
|
# try to find the new item in the list
|
|
|
|
for it in self.downloaded.items:
|
|
|
|
if it.equals( item):
|
|
|
|
already_in_list = True
|
|
|
|
break
|
|
|
|
|
|
|
|
# only append if not already in list
|
|
|
|
if not already_in_list:
|
|
|
|
self.downloaded.items.append( item)
|
|
|
|
else:
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print "no need to re-add already added podcast item to localDB"
|
|
|
|
|
2006-02-04 11:37:23 +01:00
|
|
|
writeLocalDB( localdb, self.downloaded)
|
2006-03-19 15:21:48 +01:00
|
|
|
libgpodder.releaseLock()
|
2006-04-03 21:43:59 +02:00
|
|
|
return not already_in_list
|
2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
def printChannel( self):
|
2006-03-03 21:04:25 +01:00
|
|
|
print '- Channel: "' + self.title + '"'
|
2005-11-21 19:21:25 +01:00
|
|
|
for item in self.items:
|
2006-03-03 21:04:25 +01:00
|
|
|
print '-- Item: "' + item.title + '"'
|
2005-11-22 14:30:28 +01:00
|
|
|
|
2005-12-24 15:58:55 +01:00
|
|
|
def isDownloaded( self, item):
|
2006-03-29 14:41:34 +02:00
|
|
|
return self.podcastFilenameExists( item.url)
|
2005-11-22 14:30:28 +01:00
|
|
|
|
2006-03-31 18:20:18 +02:00
|
|
|
def getItemsModel( self, want_color = True):
|
2005-11-22 14:30:28 +01:00
|
|
|
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, gobject.TYPE_STRING)
|
2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
for item in self.items:
|
2005-11-22 14:30:28 +01:00
|
|
|
# Skip items with no download url
|
|
|
|
if item.url != "":
|
2006-03-31 18:20:18 +02:00
|
|
|
if self.isDownloaded(item) and want_color:
|
2005-12-24 15:58:55 +01:00
|
|
|
background_color = "#eeeeee"
|
|
|
|
else:
|
|
|
|
background_color = "white"
|
2005-11-22 14:30:28 +01:00
|
|
|
new_iter = new_model.append()
|
|
|
|
new_model.set( new_iter, 0, item.url)
|
|
|
|
new_model.set( new_iter, 1, item.title)
|
|
|
|
new_model.set( new_iter, 2, item.getSize())
|
|
|
|
new_model.set( new_iter, 3, True)
|
|
|
|
new_model.set( new_iter, 4, background_color)
|
2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
return new_model
|
|
|
|
|
|
|
|
def getActiveByUrl( self, url):
|
|
|
|
i = 0
|
|
|
|
|
|
|
|
for item in self.items:
|
|
|
|
if item.url == url:
|
|
|
|
return i
|
|
|
|
i = i + 1
|
|
|
|
|
|
|
|
return -1
|
|
|
|
|
2006-03-03 21:04:25 +01:00
|
|
|
def downloadRss( self, force_update = True):
|
|
|
|
|
|
|
|
if (self.filename == "__unknown__" or exists( self.cache_file) == False) or force_update:
|
2006-03-29 13:51:25 +02:00
|
|
|
# remove old cache file
|
|
|
|
libgpodder.gPodderLib().deleteFilename( self.cache_file)
|
2006-03-03 21:04:25 +01:00
|
|
|
event = Event()
|
|
|
|
downloadThread(self.url, self.cache_file, event).download()
|
|
|
|
|
|
|
|
while event.isSet() == False:
|
|
|
|
event.wait( 0.2)
|
|
|
|
#FIXME: we do not want gtk code when not needed
|
|
|
|
while gtk.events_pending():
|
|
|
|
gtk.main_iteration( False)
|
2006-03-29 13:51:25 +02:00
|
|
|
|
|
|
|
# check if download was a success
|
|
|
|
if exists( self.cache_file) == False:
|
|
|
|
return None
|
2006-03-03 21:04:25 +01:00
|
|
|
|
|
|
|
return self.cache_file
|
|
|
|
|
|
|
|
def get_save_dir(self):
|
|
|
|
savedir = self.download_dir + self.filename + "/"
|
|
|
|
libgpodder.gPodderLib().createIfNecessary( savedir)
|
|
|
|
return savedir
|
|
|
|
|
|
|
|
save_dir = property(fget=get_save_dir)
|
|
|
|
|
|
|
|
def get_download_dir(self):
|
|
|
|
if self.__download_dir == None:
|
|
|
|
return libgpodder.gPodderLib().downloaddir
|
|
|
|
else:
|
|
|
|
return self.__download_dir
|
2005-11-21 19:21:25 +01:00
|
|
|
|
2006-03-03 21:04:25 +01:00
|
|
|
def set_download_dir(self, value):
|
|
|
|
self.__download_dir = value
|
|
|
|
libgpodder.gPodderLib().createIfNecessary(self.__download_dir)
|
2006-04-07 03:43:06 +02:00
|
|
|
# the following disabled at the moment..
|
|
|
|
#if libgpodder.isDebugging():
|
|
|
|
# print "set_download_dir: ", self, self.__download_dir
|
2006-03-03 21:04:25 +01:00
|
|
|
|
|
|
|
download_dir = property (fget=get_download_dir,
|
|
|
|
fset=set_download_dir)
|
|
|
|
|
|
|
|
def get_cache_file(self):
|
|
|
|
return libgpodder.gPodderLib().cachedir + self.filename + ".xml"
|
|
|
|
|
|
|
|
cache_file = property(fget=get_cache_file)
|
|
|
|
|
|
|
|
def get_index_file(self):
|
|
|
|
# gets index xml filename for downloaded channels list
|
|
|
|
return self.save_dir + "index.xml"
|
2005-11-21 19:21:25 +01:00
|
|
|
|
2006-03-03 21:04:25 +01:00
|
|
|
index_file = property(fget=get_index_file)
|
2006-03-29 14:41:34 +02:00
|
|
|
|
2006-03-31 18:20:18 +02:00
|
|
|
def get_cover_file( self):
|
|
|
|
# gets cover filename for cover download cache
|
|
|
|
return self.save_dir + "cover"
|
|
|
|
|
|
|
|
cover_file = property(fget=get_cover_file)
|
|
|
|
|
2006-03-29 14:41:34 +02:00
|
|
|
def getPodcastFilename( self, url):
|
|
|
|
# strip question mark (and everything behind it), fix %20 errors
|
|
|
|
filename = basename( url).replace( "%20", " ")
|
|
|
|
indexOfQuestionMark = filename.rfind( "?")
|
|
|
|
if indexOfQuestionMark != -1:
|
|
|
|
filename = filename[:indexOfQuestionMark]
|
|
|
|
# end strip questionmark
|
2006-04-04 00:27:57 +02:00
|
|
|
extension = splitext( filename)[1].lower()
|
|
|
|
|
|
|
|
legacy_location = self.save_dir + filename
|
|
|
|
new_location = self.save_dir + md5.new(url).hexdigest() + extension
|
|
|
|
|
|
|
|
# this supports legacy podcast locations, should be removed at some point (or move files from old location to new location)
|
|
|
|
if exists( legacy_location):
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print "(gpodder < 0.7 compat) using old filename scheme for already downloaded podcast."
|
|
|
|
return legacy_location
|
|
|
|
else:
|
|
|
|
return new_location
|
2006-03-29 14:41:34 +02:00
|
|
|
|
|
|
|
def podcastFilenameExists( self, url):
|
|
|
|
return exists( self.getPodcastFilename( url))
|
|
|
|
|
2006-03-24 20:08:59 +01:00
|
|
|
def deleteDownloadedItemByUrlAndTitle(self, url, title):
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print "deleteDownloadedItemByUrlAndTitle: " + title + " (" + url + ")"
|
|
|
|
# no multithreaded access
|
|
|
|
libgpodder.getLock()
|
|
|
|
nr_items = 0
|
|
|
|
localdb = self.index_file
|
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print "localdb: " + localdb
|
|
|
|
try:
|
|
|
|
locdb_reader = readLocalDB()
|
|
|
|
locdb_reader.parseXML( localdb)
|
|
|
|
self.downloaded = locdb_reader.channel
|
|
|
|
for item in self.downloaded.items:
|
|
|
|
if item.title == title and item.url == url:
|
|
|
|
nr_items += 1
|
|
|
|
self.downloaded.items.remove(item)
|
|
|
|
except:
|
2006-03-31 18:20:18 +02:00
|
|
|
print _("No LocalDB found or error in existing LocalDB.")
|
2006-03-24 20:08:59 +01:00
|
|
|
if libgpodder.isDebugging():
|
|
|
|
print " found", nr_items, "matching item(s)"
|
|
|
|
if nr_items > 0:
|
|
|
|
writeLocalDB( localdb, self.downloaded)
|
|
|
|
libgpodder.releaseLock()
|
|
|
|
if nr_items > 0:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2006-03-03 21:04:25 +01:00
|
|
|
class podcastItem(object):
|
|
|
|
"""holds data for one object in a channel"""
|
|
|
|
def __init__( self,
|
|
|
|
url = "",
|
|
|
|
title = "",
|
|
|
|
length = "0",
|
|
|
|
mimetype = "",
|
|
|
|
guid = "",
|
|
|
|
description = "",
|
|
|
|
link = ""):
|
2005-11-21 19:21:25 +01:00
|
|
|
self.url = url
|
|
|
|
self.title = title
|
|
|
|
self.length = length
|
|
|
|
self.mimetype = mimetype
|
|
|
|
self.guid = guid
|
2006-02-26 00:13:29 +01:00
|
|
|
self.description = stripHtml( description)
|
2005-11-21 19:21:25 +01:00
|
|
|
self.link = ""
|
2006-04-03 21:43:59 +02:00
|
|
|
|
|
|
|
def equals( self, other_item):
|
|
|
|
if other_item == None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# we suppose it's the same when the download URL is the same..
|
|
|
|
return self.url == other_item.url
|
2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
def getSize( self):
|
|
|
|
kilobyte = 1024
|
|
|
|
megabyte = kilobyte * 1024
|
|
|
|
gigabyte = megabyte * 1024
|
|
|
|
|
|
|
|
size = int( self.length)
|
|
|
|
if size > gigabyte:
|
2006-03-31 18:20:18 +02:00
|
|
|
# Might be a bit big, but who cares...
|
|
|
|
return '%d GB' % str(size / gigabyte)
|
2005-11-21 19:21:25 +01:00
|
|
|
if size > megabyte:
|
2006-03-31 18:20:18 +02:00
|
|
|
return '%d MB' % int(size / megabyte)
|
2005-11-21 19:21:25 +01:00
|
|
|
if size > kilobyte:
|
2006-03-31 18:20:18 +02:00
|
|
|
return '%d KB' % int(size / kilobyte)
|
2005-11-21 19:21:25 +01:00
|
|
|
|
2006-03-31 18:20:18 +02:00
|
|
|
return '%d Bytes' % size
|
2005-11-21 19:21:25 +01:00
|
|
|
|
|
|
|
def channelsToModel( channels):
|
|
|
|
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_OBJECT)
|
|
|
|
|
|
|
|
for channel in channels:
|
|
|
|
new_iter = new_model.append()
|
|
|
|
new_model.set( new_iter, 0, channel.url)
|
2006-03-04 21:45:01 +01:00
|
|
|
new_model.set( new_iter, 1, channel.title) # + " ("+channel.url+")")
|
2005-11-21 19:21:25 +01:00
|
|
|
#if channel.image != None:
|
|
|
|
# new_model.set( new_iter, 2, gtk.gdk.pixbuf_new_from_file_at_size( channel.image, 60, 60))
|
|
|
|
#else:
|
|
|
|
# new_model.set( new_iter, 2, None)
|
|
|
|
|
|
|
|
return new_model
|
|
|
|
|
2006-02-26 00:13:29 +01:00
|
|
|
def stripHtml( html):
|
|
|
|
# strips html from a string (fix for <description> tags containing html)
|
2006-03-19 18:44:47 +01:00
|
|
|
dict = htmlentitydefs.entitydefs
|
2006-02-26 00:13:29 +01:00
|
|
|
rexp = re.compile( "<[^>]*>")
|
2006-03-19 18:44:47 +01:00
|
|
|
stripstr = rexp.sub( "", html)
|
|
|
|
# strips html entities
|
|
|
|
for key in dict.keys():
|
|
|
|
stripstr = stripstr.replace( '&'+unicode(key,'iso-8859-1')+';', unicode(dict[key], 'iso-8859-1'))
|
|
|
|
return stripstr
|
|
|
|
|