from UserList import UserList import copy,re import socket,urllib2 from urllib import urlencode import os,stat from BitTornado.bencode import bdecode from BitQueue import timeoutsocket from BitQueue.log import get_logger from BitQueue import policy from element import Element from misc import asciiquote,string from aurllib import urlopen STRING = 0 INTEGER = 1 LONG = 2 FLOAT = 3 LIST = 4 class Generic: BASE_ATTRIBUTES = {} ATTRIBUTES = {} BASE_COMPARE_ORDER = [] COMPARE_ORDER = [] def __init__(self,tag=None,attrs={},content=''): if not tag: tag = self.__class__.__name__ self.tag = tag self.content = content self.log = get_logger() self.attributes = copy.copy(self.BASE_ATTRIBUTES) self.attributes.update(self.ATTRIBUTES) self.compare_order = copy.copy(self.BASE_COMPARE_ORDER) self.compare_order.extend(copy.copy(self.COMPARE_ORDER)) self.set_attributes(attrs) def __repr__(self): return '<%s %s>' % (self.tag,string(self.get_attributes())) def filename(self): torrent_path = policy.get_policy().get(policy.TORRENT_PATH) media_file = os.path.join(torrent_path, asciiquote(self.title)+'.torrent') return media_file def exists(self): return os.path.exists(self.filename()) def set_attributes(self,attrs): keys = self.attributes.keys() for key in keys: if not attrs.has_key(key): value = '' else: value = string(attrs[key]) atype = self.attributes[key] if atype == STRING: pass elif atype == INTEGER: try: value = int(value) except ValueError: value = 0 elif atype == LONG: try: value = long(value) except ValueError: value = 0L elif atype == FLOAT: try: value = float(value) except ValueError: value = 0.0 elif atype == LIST: value = value.split(',') self.__dict__[key] = value def get_attributes(self): keys = self.attributes.keys() dict = {} for key in keys: atype = self.attributes[key] value = self.__dict__[key] if atype == LIST: value = ','.join(value) else: value = string(value) dict[key] = value return dict def __cmp__(self,other): for attr in self.compare_order: try: ret = cmp(getattr(self,attr,''),getattr(other,attr,'')) if ret != 0: return ret except AttributeError: pass return 0 def newer(self,other): return cmp(self,other) == 0 def fetch(self,referer=None): if self.exists(): try: bdecode(open(self.filename(),'rb').read()) return except Exception,why: self.log.warn('invalid existing torrent: %s\n' % self.filename()) pass try: web = urlopen(self.link,referer=referer) meta = web.read() except Exception,why: self.log.error('torrent failed: %s\n' % str(why)) return try: torrent_file = self.filename() fo = open(torrent_file,'wb') fo.write(meta) fo.close() except IOError,why: self.log.error('IO failed: %s %s\n' % (self.filename(),str(why))) def to_element(self): e = Element(self.tag,self.get_attributes(),self.content) return e class GenericMedia(Generic): BASE_ATTRIBUTES = {'title': STRING, 'link': STRING, 'type': STRING, 'description': STRING, 'publisher': STRING} ATTRIBUTES = {} BASE_COMPARE_ORDER = ['title','link','type','publisher'] COMPARE_ORDER = [] class List(Generic,UserList): def __init__(self,tag=None,attrs={},content='',data=[]): Generic.__init__(self,tag,attrs,content) UserList.__init__(self,data) def to_element(self): e = Generic.to_element(self) for item in self.data: e.add(item.to_element()) return e def find_elements(self,tag,instances=0,default=None,content=0): if content and default is None: instances = 1 default = '' l = [] for item in self.data: if item.tag == tag: l.append(item) if instances == 1: if len(l) == 0: l = default else: l = l[0] if content: l = l.content elif instances > 1: l = l[:instances] return l def update(self,item): try: idx = self.index(item) self[idx] = item except: self.append(item) class MediaFactory: def __init__(self,default_media_class,default_list_class): self.known_media = {} self.default_media_class = default_media_class self.default_list_class = default_list_class def create(self,media_name,tag,attrs={},content=''): media_class = self.known_media.get(media_name,None) if not media_class: return None return media_class(tag=tag,attrs=attrs,content=content) def register(self,media_name,media_class): self.known_media[media_name] = media_class def unregister(self,media_name): if self.known_media.has_key(media_name): del self.known_media[media_name] def from_file(self,file): close_flag = 0 if type(file) == type(''): fd = open(file,'r') close_flag = 1 else: fd = file e = Element() e.load(fd) if close_flag: fd.close() return self.from_element(e) def from_element(self,element): tag = element.tag if self.known_media.has_key(tag): media_class = self.known_media[tag] else: if tag.endswith('List'): media_class = self.default_list_class else: media_class = self.default_media_class obj = media_class(tag,attrs=element.attrs,content=element.content) if len(element.children) > 0 and hasattr(obj,'append'): for e in element.children: obj.append(factory.from_element(e)) return obj class Series(GenericMedia): ATTRIBUTES = {'episode': STRING} def filename(self): torrent_path = policy.get_policy().get(policy.TORRENT_PATH) name = asciiquote('%s-%s' % (self.title,self.episode)) media_file = os.path.join(torrent_path,name+'.torrent') return media_file def newer(self,other): return cmp(self.episode,other.episode) # first, check for the form of episode eplist = [-1, -1] idx = 0 for ep in [self.episode, other.episode] : ep = ep or '0' tmp1 = ep.split('-') tmp2 = ep.split('v') if len(tmp1) > len(tmp2): tmp = tmp1 else: tmp = tmp2 eplist[idx] = -1 if len(tmp) > 1 : for subep in tmp : if int(subep) > self_max : eplist[idx] = int(subep) else : eplist[idx] = int(ep) idx = idx + 1 return eplist[0] > eplist[1] class Tracker(List): BASE_ATTRIBUTES = {'url': STRING, 'loader': STRING, 'filter': STRING, 'publisher': STRING, 'user': STRING, 'password': STRING, 'media': STRING, 'tag': STRING} class Url(List): BASE_ATTRIBUTES = {'name': STRING, 'url': STRING, 'user': STRING, 'password': STRING, 'method': STRING} def urlopen(self,url=None,referer=None): url = url or self.url method = self.method or 'get' data = {} params = self.find_elements('Param') for param in params: data[param.name] = param.value return urlopen(self.url, data=urlencode(data), user=self.user, passwd=self.password, method=method, referer=referer) class Param(List): BASE_ATTRIBUTES = {'name': STRING, 'value': STRING} class Filter(Generic): BASE_ATTRIBUTES = {'name': STRING, 'option': STRING} def __init__(self,tag=None,attrs={},content=''): Generic.__init__(self,tag,attrs,content) self.flags = 0 if self.option == '-': return self.option = self.option or 'I,S,M' options = [i.strip().upper() for i in self.option.split(',')] newopts = [] for opt in options: if opt in ['X','VERBOSE']: self.flags |= re.VERBOSE newopts.append('VERBOSE') elif opt in ['I','IGNORECASE']: self.flags |= re.IGNORECASE newopts.append('IGNORECASE') elif opt in ['S','DOTALL']: self.flags |= re.DOTALL newopts.append('DOTALL') elif opt in ['M','MULTILINE']: self.flags |= re.MULTILINE newopts.append('MULTILINE') elif opt in ['U','UNICODE']: self.flags |= re.UNICODE newopts.append('UNICODE') self.option = ','.join(newopts) class Interest(Generic): BASE_ATTRIBUTES = {'attribute': STRING, 'mode': STRING, 'title': STRING} def __init__(self,tag=None,attrs={},content=''): Generic.__init__(self,tag,attrs,content) self.cre = re.compile(self.content,re.I|re.S|re.M) def is_interest(self,media): if not hasattr(media,self.attribute): return 0 value = string(getattr(media,self.attribute)) found = self.cre.search(value) if self.mode == 'include' and found: return 1 elif self.mode == 'exclude' and found: return -1 return 0 class InterestSet(List): BASE_ATTRIBUTES = {'mode': STRING, 'title': STRING} def is_interest(self,media): if not self.mode in ['and','or']: self.mode = 'and' result = [0,0,0] for inte in self.data: ret = inte.is_interest(media) result[ret+1] += 1 if self.mode == 'and': return result[2] == len(self.data) elif self.mode == 'or': return result[2] > 0 class rssRSS(List): BASE_ATTRIBUTES = {'version': STRING} def channels(self): return self.find_elements('channel') class rssChannel(List): def title(self,content=0): return self.find_elements('title',1,content=content) def link(self,content=0): return self.find_elements('link',1,content=content) def description(self,content=0): return self.find_elements('description',1,content=content) def language(self,content=0): return self.find_elements('language',1,content=content) def items(self,content=0): return self.find_elements('item',content=content) class rssItem(List): def title(self,content=0): return self.find_elements('title',1,content=content) def description(self,content=0): return self.find_elements('description',1,content=content) def pubDate(self,content=0): return self.find_elements('pubDate',1,content=content) def guid(self,content=0): return self.find_elements('guid',1,content=content) def enclosure(self,content=0): return self.find_elements('enclosure',1,content=content) class rssEnclosure(List): BASE_ATTRIBUTES = {'url': STRING, 'length': LONG, 'type': STRING} factory = MediaFactory(GenericMedia,List) factory.register('GenericMedia',GenericMedia) factory.register('Series',Series) factory.register('Anime',Series) factory.register('Tracker',Tracker) factory.register('Url',Url) factory.register('Param',Param) factory.register('Filter',Filter) factory.register('Interest',Interest) factory.register('InterestSet',InterestSet) factory.register('rss',rssRSS) factory.register('channel',rssChannel) factory.register('item',rssItem) factory.register('enclosure',rssEnclosure) if __name__ == '__main__': import sys from cStringIO import StringIO mlist = List('MediaList') mlist.append(Generic('Media',{'title': 'test'})) mlist.append(Series('Anime',{'title': 'Gundam Seed','episode': 1})) fd = StringIO() mlist.to_element().save(fd) fd.seek(0,0) result = fd.read() print result fd.seek(0,0) element = Element() element.load(fd) mlist = factory.from_element(element) fd.close() mlist.to_element().save(sys.stdout) fd = StringIO(''' \s*[^\s*(?P[^<]+)</a></td>\s*<td class="[^"]+" align="right">(?P<files>\d+)</td>\s*<td class="[^"]+" align="center">[^<]+</td>\s*<td class="[^"]+" align="center" nowrap>(?P<date>[^<]+(<br/>| )[^<]*)</td>\s*<td class="[^"]+" align="center">[^<]+</td>\s*<td class="[^"]+" align="center">\d+</td>\s*<td class="[^"]+" align="right">\d+</td>\s*<td class="[^"]+" align="right">\d+</td>\s*<td class="[^"]+" align="center"><a href="[^"]+">(?P<publisher>[^<]+)</a></td>\s*</tr>]]></Filter> <Filter name="detail"><![CDATA[<a href="(?P<download>[^\?]+\?act=bt&func=download&id=\d+)">]]></Filter> </Url> <Url name="logout" url="http://th-torrent.mine.nu/"/> </Tracker> </TrackerList>''') element.load(fd) tlist = factory.from_element(element) fd.close() tlist.to_element().save(sys.stdout) sys.exit() mlist = List('InterestList') mlist.append(Interest(attrs={'attribute': 'publisher', 'mode': 'include'},content='loading')) mlist.append(Interest(attrs={'attribute': 'title', 'mode': 'exclude'},content='trial')) fd = StringIO() mlist.to_element().save(fd) fd.seek(0,0) result = fd.read() print result m1 = GenericMedia('Media',attrs={'title': 'test1'}) m2 = GenericMedia('Media',attrs={'title': 'test1'}) m3 = GenericMedia('Media',attrs={'title': 'test 1'}) m4 = Series('Media',attrs={'title': 'test 1','episode': '1'}) m5 = Series('Media',attrs={'title': 'test 1','episode': '2'}) m6 = Series('Media',attrs={'title': 'test 1','episode': '2v2'}) print cmp(m1,m2) print cmp(m1,m3) print cmp(m3,m4),m3.newer(m4) print cmp(m4,m5),m4.newer(m5) print cmp(m5,m4),m5.newer(m4) print cmp(m5,m6),m6.newer(m6) mlist = List('MediaList') mlist.append(m1) mlist.append(m2) mlist.append(m3) print mlist[2] mlist.update(m4) print mlist[2]