PageRenderTime 72ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/LMG/Tools/rss.py

http://lh-abc.googlecode.com/
Python | 985 lines | 817 code | 58 blank | 110 comment | 40 complexity | ee520fcb52e056ad4fd0715239a8dd29 MD5 | raw file
  1. """
  2. RSS support for BitTorrent.
  3. This module provides RSS support for:
  4. 1. reading feeds
  5. 2. downloading .torrent file from feeds
  6. 3. automatically download .torrent files using filters
  7. 4. downloading .torrent files from indirect RSS articles
  8. """
  9. __author__ = "Roee Shlomo"
  10. __version__ = "2.1"
  11. __date__ = "$2007/09/30$"
  12. __license__ = 'MIT license'
  13. import os
  14. import wx
  15. import pickle
  16. import re
  17. from threading import Thread, Event
  18. from time import time, mktime
  19. from feedparser import parse
  20. from LMG.GUI.Base.guiupdate import SafeInvocation
  21. from LMG.Utility.threadpool import ThreadPool, makeRequests
  22. from LMG.Utility.helpers import existsAndIsReadable
  23. from LMG.Utility.configreader import ConfigReader
  24. from LMG.Utility.urlfinder import HtmlTorrentScanner
  25. # Smart episode detection regex
  26. matchesmodes = [re.compile("(?P<name>.*?)s(?P<f_s>[0-9]+)e(?P<f_e>[0-9]+)[\\-\\+]s(?P<l_s>[0-9]+)e(?P<l_e>[0-9]+).*?"),
  27. re.compile("(?P<name>.*?)(?P<f_s>[0-9]+)x(?P<f_e>[0-9]+)[\\-\\+](?P<l_s>[0-9]+)x(?P<l_e>[0-9]+).*?"),
  28. re.compile("(?P<name>.*?)(?P<f_s>[0-9]+)(?P<f_e>[0-9]{2})[\\-\\+](?P<l_s>[0-9]+)(?P<l_e>[0-9]{2}).*?"),
  29. re.compile("(?P<name>.*?)s(?P<f_s>[0-9]+)e(?P<f_e>[0-9]+)[\\-\\+]e(?P<l_e>[0-9]+).*?"),
  30. re.compile("(?P<name>.*?)s(?P<f_s>[0-9]+)e(?P<f_e>[0-9]+)[\\-\\+](?P<l_e>[0-9]+).*?"),
  31. re.compile("(?P<name>.*?)(?P<f_s>[0-9]+)x(?P<f_e>[0-9]+)[\\-\\+](?P<l_e>[0-9]+).*?"),
  32. re.compile("(?P<name>.*?)(?P<f_s>[0-9]+)(?P<f_e>[0-9]{2})[\\-\\+](?P<l_e>[0-9]{2}).*?"),
  33. re.compile("(?P<name>.*?)s(?P<f_s>[0-9]+)e(?P<f_e>[0-9]+).*?"),
  34. re.compile("(?P<name>.*?)(?P<f_s>[0-9]+)x(?P<f_e>[0-9]+).*?"),
  35. re.compile("(?P<name>.*?)s(?P<f_s>[0-9]+).*?"),
  36. re.compile("(?P<name>.*?)season (?P<f_s>[0-9]+).*?"),
  37. ]
  38. #################################################################################
  39. class RSS(SafeInvocation):
  40. """
  41. Main RSS Class
  42. """
  43. def __init__(self, parent):
  44. # Initialization
  45. self.doneflag = Event()
  46. SafeInvocation.__init__(self)
  47. # Main management
  48. self.feeds = []
  49. self.rules = []
  50. self.history = {}
  51. # Set up config
  52. self.config = ConfigManager(self)
  53. # html scanner threads
  54. self.htmlscanners = {}
  55. # timer
  56. self.timer = wx.Timer(self, -1)
  57. self.timerCount = -1
  58. self.Bind(wx.EVT_TIMER, self.onTimer)
  59. #######################
  60. # Feeders
  61. #######################
  62. def addFeeder(self, args = [], kwargs = {}):
  63. """
  64. FEEDERS: Add new feeder
  65. """
  66. feed = RSSFeeder(self, RSSFeederOptions(*args, **kwargs))
  67. if feed:
  68. self.feeds.append(feed)
  69. def removeFeeder(self, index):
  70. """
  71. FEEDERS: Remove feeder by index
  72. """
  73. del self.feeds[index]
  74. def changeFeeder(self, index, optionsTuple):
  75. """
  76. FEEDERS: Changes feeder options
  77. """
  78. self.feeds[index].fromTuple(optionsTuple)
  79. def changeName(self, feeder, name):
  80. """
  81. FEEDERS: Change feeder name
  82. """
  83. more = 1
  84. newname = name
  85. names = [feed.options.name for feed in self.feeds if feed != feeder]
  86. while newname in names:
  87. more += 1
  88. newname = name + " " + str(more)
  89. feeder.options.name = newname
  90. self.config.save("feeds")
  91. # Update GUI
  92. self.updateFeederSelector()
  93. if utility.window.exists('rssfeeders'):
  94. utility.window['rssfeeders'].onRestore()
  95. def findFeeder(self, url = None, name = None):
  96. """
  97. FEEDERS: Find feeder by URL or name
  98. """
  99. for feed in self.feeds:
  100. if url and feed.options.url == url:
  101. return feed
  102. if name and feed.options.name == name:
  103. return feed
  104. return None
  105. #######################
  106. # Rules
  107. #######################
  108. def addRule(self, args = [], kwargs = {}):
  109. """
  110. RULES: Add new rule
  111. """
  112. if isinstance(args, Rule):
  113. rule = args
  114. else:
  115. rule = Rule(*args, **kwargs)
  116. self.rules.append(rule)
  117. self.config.save("rules")
  118. def removeRule(self, index):
  119. """
  120. RULES: Remove rule by index
  121. """
  122. del self.rules[index]
  123. self.config.save("rules")
  124. #######################
  125. # History
  126. #######################
  127. def addToHistory(self, article):
  128. """
  129. HISTORY: Add article to history
  130. """
  131. self.history[article.url.lower()] = article.title
  132. self.config.save("history")
  133. self.updateList()
  134. match, episodes, name = self.GetEpisodes(article.title)
  135. if match:
  136. self.addToEpHistory(name, episodes)
  137. def addLinkToHistory(self, link):
  138. """
  139. HISTORY: Add link to history
  140. """
  141. self.history[link.lower()] = link
  142. self.config.save("history")
  143. self.updateList()
  144. def removeFromHistory(self, url):
  145. """
  146. HISTORY: Remove article from history
  147. """
  148. del self.history[url.lower()]
  149. self.config.save("history")
  150. def isInHistory(self, article):
  151. """
  152. HISTORY: Check if an article is in history
  153. """
  154. return article.getURL() in self.history
  155. def clearHistory(self):
  156. """
  157. HISTORY: Clears history
  158. """
  159. self.history = {}
  160. self.config.save("history")
  161. def addToEpHistory(self, name, episodes):
  162. """
  163. HISTORY: Add to episodes history
  164. """
  165. oldepsave = self.config.episodes.Read(name.lower()) or "[]"
  166. oldepsave = eval(oldepsave)
  167. episodes = set(episodes)
  168. episodes.update(oldepsave)
  169. episodes = list(episodes)
  170. episodes.sort()
  171. self.config.episodes.Write(name, episodes)
  172. self.config.episodes.Flush()
  173. def removeFromEpHistory(self, name, episode):
  174. """
  175. HISTORY: Remove from episodes history
  176. """
  177. oldepsave = self.config.episodes.Read(name.lower()) or "[]"
  178. oldepsave = list(eval(oldepsave))
  179. if episode in oldepsave:
  180. oldepsave.remove(episode)
  181. self.config.episodes.Write(name, oldepsave)
  182. self.config.episodes.Flush()
  183. def clearEpHistory(self):
  184. self.config.episodes.DeleteGroup()
  185. self.config.episodes.Flush()
  186. #######################
  187. # Timer
  188. #######################
  189. def startTimer(self):
  190. """
  191. TIMER: Start the auto-grab torrents timer
  192. """
  193. self.timer.Start(1000)
  194. if self.timerCount == -1:
  195. self.timerCount = utility.config.Read('rsstimer', "int")
  196. self.timerCount = max(self.timerCount, 300)
  197. self.timerActivated = time()
  198. def stopTimer(self):
  199. """
  200. TIMER: Stop the auto-grab torrents timer
  201. """
  202. self.timerCount = -1
  203. self.timer.Stop()
  204. def onTimer(self, event):
  205. """
  206. TIMER: timer tick
  207. """
  208. current = time() - self.timerActivated
  209. if current > self.timerCount:
  210. self.onUpdateAll()
  211. #######################
  212. # Html Torrent Scanner
  213. #######################
  214. def HtmlTorrentScannerFailed(self, article, message):
  215. """
  216. Called from HtmlTorrentScanner when faild
  217. """
  218. if self.htmlscanners.has_key(article.key):
  219. del self.htmlscanners[article.key]
  220. self.invokeLater(self._HtmlTorrentScannerFailed, [article.url, message])
  221. def _HtmlTorrentScannerFailed(self, url, message):
  222. utility.frame.SetStatusText(message + url)
  223. wx.LogError(message + ' ' + url)
  224. def HtmlTorrentScannerSucceeded(self, article, rule, data):
  225. """
  226. Called from HtmlTorrentScanner when succeeded
  227. """
  228. if self.htmlscanners.has_key(article.key):
  229. del self.htmlscanners[article.key]
  230. self.addToHistory(article)
  231. if rule:
  232. rule.last_match = long(time())
  233. if rule.Type == "normal" and int(rule.Settings[0]):
  234. rule.active = False
  235. self.invokeLater(self._HtmlTorrentScannerSucceeded, [article, data, rule])
  236. def _HtmlTorrentScannerSucceeded(self, article, data, rule):
  237. utility.frame.SetStatusText(_("Downloaded:") + ' ' + article.title)
  238. wx.LogMessage(_("Downloaded:") + ' ' + article.title)
  239. self.addTorrentFromData(data, rule)
  240. #######################
  241. # Checks
  242. #######################
  243. def GetEpisodes(self, name):
  244. class Item(object):
  245. __slots__ = ('entrytitle', 'url')
  246. item = Item()
  247. item.entrytitle = name
  248. item.url = ""
  249. return self.CheckEpisodes(item)
  250. def CheckEpisodes(self, rssarticle, name = None, settings = None):
  251. """
  252. Episode checker
  253. @Arguments : str rssarticle = RSS entry to match
  254. str name = Name of the show
  255. str settings = RSS rule settings
  256. @Returns : ([bool]Found a match, [list]episodes, [string]name)
  257. """
  258. # Try to find a match in entrytitle
  259. string = rssarticle.entrytitle.lower()
  260. match, current = False, 0
  261. while not match and current < len(matchesmodes):
  262. match = matchesmodes[current].match(string)
  263. current += 1
  264. # Try to find a match in URL
  265. if not match:
  266. string = rssarticle.url.lower()
  267. if string.startswith("http://"):
  268. string = string.split("/")[-1]
  269. if "name=" in string:
  270. string = string.split("name=")[-1]
  271. match, current = False, 0
  272. while not match and current < len(matchesmodes):
  273. match = matchesmodes[current].match(string)
  274. current += 1
  275. # No match found
  276. if not match:
  277. return False, None, None
  278. # Found a match, list its episodes
  279. episodes = []
  280. if len(match.groups()) == 2:
  281. # Single season
  282. seasonStart = int(match.group(1))
  283. episodesRange = (seasonStart, 0, seasonStart, 9999)
  284. episodes.append((seasonStart, -1))
  285. elif len(match.groups()) == 3:
  286. # Single episode
  287. seasonStart = int(match.group(2))
  288. episodeStart = int(match.group(3))
  289. episodesRange = (seasonStart, episodeStart, seasonStart, episodeStart)
  290. episodes.append((seasonStart, episodeStart))
  291. elif len(match.groups()) == 4:
  292. # Multiple episodes, same season
  293. seasonStart = int(match.group(2))
  294. episodeStart = int(match.group(3))
  295. episodeEnd = int(match.group(4))
  296. episodesRange = (seasonStart, episodeStart, seasonStart, episodeEnd)
  297. i = episodeStart
  298. while i <= episodeEnd:
  299. episodes.append((seasonStart, i))
  300. i+=1
  301. elif len(match.groups()) == 5:
  302. # Multiple episodes, different seasons
  303. seasonStart = int(match.group(2))
  304. episodeStart = int(match.group(3))
  305. seasonEnd = int(match.group(4))
  306. episodeEnd = int(match.group(5))
  307. episodesRange = (seasonStart, episodeStart, seasonEnd, episodeEnd)
  308. s = seasonStart
  309. while s <= seasonEnd:
  310. if s == seasonStart:# partial season
  311. for x in xrange(episodeStart, 25):
  312. episodes.append((s, x))
  313. elif s < seasonEnd: # -1 = Full season
  314. episodes.append((s, -1))
  315. else: # specific episodes
  316. for x in xrange(1, episodeEnd+1):
  317. episodes.append((s, x))
  318. s+=1
  319. else:
  320. # No match found
  321. return False, None, None
  322. # Get the show name
  323. if not name:
  324. name = match.group(1)
  325. n = re.match(r'\[\w+\](.*)', name)
  326. if n:
  327. name = n.group(1)
  328. name = name.replace(".", " ").replace("[", " ").replace("]", " ").replace("!", " ").replace("{", " ").strip()
  329. # Check if needed
  330. existingEpisodes = self.config.episodes.Read(name.lower())
  331. if existingEpisodes:
  332. need = False
  333. existingEpisodes = eval(existingEpisodes)
  334. for episode in episodes:
  335. if not episode in existingEpisodes and \
  336. not (episode[0], -1) in existingEpisodes:
  337. need = True
  338. break
  339. # I don't need it...
  340. if not need:
  341. return False, None, None
  342. # No settings, just get it
  343. if not settings:
  344. return True, episodes, name
  345. # Match rule settings
  346. match, current = False, 0
  347. while not match and current < len(matchesmodes):
  348. match = matchesmodes[current].match(settings.lower())
  349. current += 1
  350. # No settings, just get it
  351. if not match:
  352. return True, episodes, name
  353. settings = match.groups()
  354. if len(match.groups()) == 5:
  355. setEpisodesRange = (int(settings[1]), int(settings[2]), int(settings[3]), int(settings[4]))
  356. elif len(match.groups()) == 4:
  357. setEpisodesRange = (int(settings[1]), int(settings[2]), int(settings[1]), int(settings[3]))
  358. elif len(match.groups()) == 3:
  359. setEpisodesRange = (int(settings[1]), int(settings[2]), int(settings[1]), int(settings[2]))
  360. elif len(match.groups()) == 2:
  361. setEpisodesRange = (int(settings[1]), 0, int(settings[1]), 9999)
  362. else:
  363. return True, episodes, name
  364. if self.inRange(episodesRange, setEpisodesRange):
  365. return True, episodes, name
  366. return False, None, None
  367. def inRange(self, episodesRange, setEpisodesRange):
  368. if episodesRange[0] < setEpisodesRange[0]:
  369. return False
  370. if episodesRange[0] == setEpisodesRange[0] and episodesRange[1] < setEpisodesRange[1]:
  371. return False
  372. if episodesRange[2] > setEpisodesRange[2]:
  373. return False
  374. if episodesRange[2] == setEpisodesRange[2] and episodesRange[3] > setEpisodesRange[3]:
  375. return False
  376. return True
  377. #######################
  378. # Functionality
  379. #######################
  380. def updateAll(self, event = None):
  381. """
  382. Get new results from all feeds
  383. """
  384. feeds = [feed for feed in self.feeds if feed.options.active]
  385. if not feeds:
  386. self.onUpdateAllDone()
  387. GetFeeds(self, feeds).start()
  388. return True
  389. def updateList(self, feed = None):
  390. """
  391. updates the list safely
  392. """
  393. pass
  394. def updateSingle(self, feed = None, url = None):
  395. """
  396. Get results from single feed
  397. """
  398. if url:
  399. feed = self.findFeeder(url = url)
  400. if feed:
  401. GetFeeds(self, feed).start()
  402. def onUpdateAllDone(self):
  403. pass
  404. def updateFeederSelector(self):
  405. pass
  406. def addTorrentFromURL(self, url, cookies, rule = None):
  407. """
  408. Downloads a torrent from URL
  409. """
  410. label = None
  411. if rule:
  412. label = rule.label
  413. if rule is None:
  414. t = Thread(target=utility.queue.addtorrents.AddTorrentURL, args=[url,], kwargs={"cookies": cookies, "label":label})
  415. t.start()
  416. return True
  417. return utility.queue.addtorrents.AddTorrentURL(url, cookies = cookies, label = label)
  418. def addTorrentFromData(self, data, rule = None):
  419. """
  420. Adds a torrent from rawdata
  421. """
  422. label = None
  423. if rule:
  424. label = rule.label
  425. return utility.queue.addtorrents.AddTorrentFromBencodeData(data.read(), label = label)
  426. def Download(self, rssarticle):
  427. """
  428. Downloads a torrent from rss article
  429. """
  430. if rssarticle.url.endswith(".torrent"):
  431. self.addTorrentFromURL(rssarticle.url, cookies = rssarticle.feeder.getCookie())
  432. self.addToHistory(rssarticle)
  433. else:
  434. try:
  435. self.htmlscanners[rssarticle.key] = HtmlTorrentScanner(self, rssarticle)
  436. self.htmlscanners[rssarticle.key].start()
  437. except IOError:
  438. pass
  439. #################################################################################
  440. class Rule(object):
  441. """
  442. Info for a single filtering rule
  443. """
  444. TYPES = ["normal", "tv", "date"]
  445. def __init__(self, name = "", feeds = [], filters = [], negfilters = [],
  446. regex = False, min_interval = 0, last_match = 0,
  447. active = True, rule_type = 0, settings = "0|", label = 0):
  448. self.name = name
  449. self.feeds = feeds
  450. self.filters = filters
  451. self.negfilters = negfilters
  452. self.regex = regex
  453. self.min_interval = min_interval
  454. self.last_match = last_match
  455. self.active = active
  456. self.rule_type = rule_type
  457. self.settings = settings
  458. self.label = label
  459. def GetType(self):
  460. return self.TYPES[self.rule_type]
  461. def SetType(self, nType):
  462. self.rule_type = nType
  463. Type = property(GetType, SetType)
  464. def GetSettings(self):
  465. return self.settings.split('|')
  466. def SetSettings(self, settings):
  467. self.settings = settings
  468. Settings = property(GetSettings, SetSettings)
  469. def toTuple(self):
  470. return (self.name, self.feeds, self.filters, self.negfilters, self.regex, self.min_interval,
  471. self.last_match, self.active, self.rule_type, self.settings, self.label)
  472. def fromTuple(self, Tuple):
  473. (self.name, self.feeds, self.filters, self.negfilters, self.regex, self.min_interval,
  474. self.last_match, self.active, self.rule_type, self.settings, self.label) = Tuple
  475. self.active = bool(self.active)
  476. self.regex = bool(self.regex)
  477. #################################################################################
  478. class RSSFeederOptions(object):
  479. """
  480. Info for a single RSSFeeder Options
  481. """
  482. def __init__(self, url, name = '', cookie = '', active = True):
  483. # Set feeder url
  484. if not ":/" in url and not ":\\" in url:
  485. url = "http://" + url
  486. self.url = url
  487. # Set feeder name
  488. if not name:
  489. self.name = self.url
  490. else:
  491. self.name = name
  492. # Set feeder cookie
  493. self.cookie = cookie
  494. # Set Activity
  495. self.active = active
  496. def toTuple(self):
  497. return (self.url, self.name, self.cookie, self.active)
  498. def fromTuple(self, Tuple):
  499. (self.url, self.name, self.cookie, self.active) = Tuple
  500. if not ":/" in self.url and not ":\\" in self.url:
  501. self.url = "http://" + self.url
  502. if not self.name:
  503. self.name = self.url
  504. self.active = bool(self.active)
  505. def __nonzero__(self):
  506. return self.url and self.url != "http://"
  507. #################################################################################
  508. class RSSFeeder(object):
  509. """
  510. RSSFeeder: manages a signle RSS feeder
  511. """
  512. def __init__(self, parent, options):
  513. self.parent = parent
  514. self.rssarticles = {}
  515. self.etag = None
  516. self.modified = None
  517. self.filtersChanged = True
  518. self.options = options
  519. def __nonzero__(self):
  520. return bool(self.options)
  521. def getCookie(self):
  522. """
  523. Returns the feeder's cookie
  524. """
  525. return self.options.cookie
  526. def toTuple(self):
  527. return self.options.toTuple()
  528. def fromTuple(self, Tuple):
  529. """
  530. Sets feeder options from string
  531. """
  532. options = RSSFeederOptions("")
  533. options.fromTuple(Tuple)
  534. if options:
  535. self.options = options
  536. def Getdata(self, result):
  537. """
  538. Called automatically after getting all feed results
  539. """
  540. self.FeedResults(result)
  541. def FeedResults(self, result):
  542. """
  543. Parses the feed results
  544. """
  545. # Page not available
  546. if not hasattr(result, "status"):
  547. result.status = 0
  548. # No updates
  549. if result.status == 304 and self.filtersChanged:
  550. for article in self.articles:
  551. self.Match(article)
  552. self.filtersChanged = False
  553. return
  554. # No articles
  555. if not result.entries:
  556. return
  557. # Feeder details
  558. feedbase = result.feed.title_detail.base.lower()
  559. feedtitle = result.feed.title
  560. if self.options.name == self.options.url and feedtitle.strip():
  561. self.parent.changeName(self, feedtitle)
  562. # Articles details
  563. for entry in result.entries:
  564. rssarticle = RSSArticle(self, entry, feedbase, feedtitle)
  565. if not rssarticle.key in self.rssarticles:
  566. self.rssarticles[rssarticle.key] = rssarticle
  567. self.Match(rssarticle)
  568. elif self.filtersChanged:
  569. self.Match(rssarticle)
  570. self.filtersChanged = False
  571. def Match(self, rssarticle):
  572. """
  573. Gets an article and checks if user is interested with its content
  574. """
  575. def RemoveMarks(item):
  576. """
  577. Adjust marks if not searching as regular expression
  578. """
  579. p = re.compile('(\*|\^|\$|\+|\?|\}|\{|\[|\]|\||\\\\|\(|\))')
  580. item = p.sub('.', item)
  581. p = re.compile('\#')
  582. item = p.sub('\d', item)
  583. return item
  584. # Only continue if torrent isn't in history list
  585. if self.parent.isInHistory(rssarticle):
  586. return
  587. for rule in self.parent.rules:
  588. if not rule.active:
  589. continue
  590. # Check if the feed is in the rule's feeds list
  591. if not rssarticle.feedbase in rule.feeds \
  592. and not rssarticle.feedbase2 in rule.feeds:
  593. continue
  594. # Minimum interval
  595. now = time()
  596. if rule.last_match and now > rule.last_match \
  597. and now - rule.last_match < rule.min_interval:
  598. continue
  599. # No filters
  600. if not rule.negfilters and not rule.filters:
  601. continue
  602. # Check filters
  603. download = True
  604. if rule.filters:
  605. for filter in rule.filters:
  606. if not rule.regex:
  607. filter = RemoveMarks(filter)
  608. find = re.search(filter, rssarticle.entrytitle, re.IGNORECASE)
  609. if not find:
  610. find = re.search(filter, rssarticle.url, re.IGNORECASE)
  611. if not find:
  612. download = False
  613. break
  614. if rule.negfilters:
  615. for filter in rule.negfilters:
  616. if not rule.regex:
  617. filter = RemoveMarks(filter)
  618. find = re.search(filter, rssarticle.entrytitle, re.IGNORECASE)
  619. if not find:
  620. find = re.search(filter, rssarticle.url, re.IGNORECASE)
  621. if find:
  622. download = False
  623. break
  624. # Download
  625. if download and self.CheckIfDownload(rssarticle, rule):
  626. break
  627. def CheckIfDownload(self, rssarticle, rule):
  628. """
  629. Check if needs to download from the article
  630. """
  631. # Only download if torrent isn't in history list
  632. if self.parent.isInHistory(rssarticle):
  633. download = False
  634. # TV rule
  635. elif rule.Type == "tv":
  636. download, episodes, name = self.parent.CheckEpisodes(rssarticle, rule.Settings[0], rule.Settings[1])
  637. if download:
  638. self.parent.addToEpHistory(name, episodes)
  639. # Date rule
  640. elif rule.Type == "date":
  641. raise NotImplementedError
  642. # Normal rule
  643. else:
  644. download = True
  645. if download and self.Download(rssarticle, rule):
  646. self.parent.addToHistory(rssarticle)
  647. rule.last_match = long(time())
  648. if rule.Type == "normal" and rule.Settings[0]:
  649. rule.active = False
  650. return True
  651. return False
  652. def Download(self, rssarticle, rule):
  653. """
  654. Downloads the torrent
  655. """
  656. if rssarticle.url.endswith(".torrent"):
  657. return self.parent.addTorrentFromURL(rssarticle.url, cookies = self.getCookie(), rule = rule)
  658. else:
  659. try:
  660. self.parent.htmlscanners[rssarticle.key] = HtmlTorrentScanner(self.parent, rssarticle, rule)
  661. self.parent.htmlscanners[rssarticle.key].start()
  662. except IOError:
  663. pass
  664. return False
  665. #################################################################################
  666. class RSSArticle(object):
  667. """
  668. A single RSS Article (single row in result)
  669. """
  670. def __init__(self, feeder, feedentry, feedbase, feedtitle):
  671. self.feeder = feeder
  672. self.feedbase = feedbase
  673. self.feedbase2 = feedbase.replace('www.', '', 1)
  674. self.feedtitle = feedtitle
  675. try:
  676. self.url = feedentry.enclosures[0].url
  677. except:
  678. try:
  679. self.url = feedentry.links[0].href
  680. except:
  681. self.url = "..."
  682. self.titlelink = feedentry.get('link', "")
  683. self.entrytitle = feedentry.get('title', "")
  684. self.entrysummary = feedentry.get('description', "")
  685. self.entrydatetime = feedentry.get('date', "")
  686. if not self.entrydatetime:
  687. self.entrydatetime = "unknown"
  688. self.enrtydate = feedentry.get('updated_parsed', "")
  689. if self.enrtydate:
  690. self.enrtydate = mktime(self.enrtydate)
  691. self.title = self.entrytitle
  692. self.key = hash((self.title.lower(), self.url.lower(), self.entrydatetime.lower()))
  693. def getIdentity(self):
  694. return (self.title, self.url, self.entrydatetime)
  695. def getTitle(self):
  696. return self.title.lower()
  697. def getURL(self):
  698. return self.url.lower()
  699. def getTime(self):
  700. return self.entrydatetime.lower()
  701. #################################################################################
  702. class ConfigManager(object):
  703. """
  704. A class for handling the RSS configuration
  705. """
  706. def __init__(self, parent):
  707. self.parent = parent
  708. self.__version = "1"
  709. # Load Config
  710. rulesPath = os.path.join(utility.getConfigPath(), "rules." + self.__version + ".conf")
  711. feedersPath = os.path.join(utility.getConfigPath(), "feeders." + self.__version + ".conf")
  712. historyPath = os.path.join(utility.getConfigPath(), "history." + self.__version + ".conf")
  713. episodePath = os.path.join(utility.getConfigPath(), "episode." + self.__version + ".conf")
  714. self.rules = ConfigReader(rulesPath , "LH/RSS", {})
  715. self.feeders = ConfigReader(feedersPath, "LH/RSS", {})
  716. self.history = ConfigReader(historyPath, "LH/RSS", {})
  717. self.episodes = ConfigReader(episodePath, "LH/RSS", {})
  718. self.loadAll()
  719. def loadAll(self):
  720. """
  721. Loads configuration
  722. """
  723. self.load("feeds")
  724. self.load("rules")
  725. self.load("history")
  726. def load(self, obj):
  727. """
  728. Loads object from file
  729. """
  730. if obj == "feeds":
  731. data = self.feeders.Read("feeds", "bencode-list")
  732. for feed_data in data:
  733. feed = RSSFeeder(self.parent, None)
  734. feed.fromTuple(feed_data)
  735. if feed:
  736. self.parent.feeds.append(feed)
  737. elif obj == "rules":
  738. data = self.rules.Read("rules", "bencode-list")
  739. for rule_data in data:
  740. rule = Rule()
  741. rule.fromTuple(rule_data)
  742. self.parent.rules.append(rule)
  743. elif obj == "history":
  744. self.parent.history = self.history.Read("history", "bencode-dict")
  745. else:
  746. raise Exception("Unknown object")
  747. def saveAll(self):
  748. """
  749. Saves configuration
  750. """
  751. self.save("feeds")
  752. self.save("rules")
  753. self.save("history")
  754. def save(self, obj):
  755. """
  756. Saves object to file
  757. """
  758. if obj == "feeds":
  759. data = [feed.toTuple() for feed in self.parent.feeds]
  760. self.feeders.Write("feeds", data, "bencode-list")
  761. self.feeders.Flush()
  762. elif obj == "rules":
  763. data = [rule.toTuple() for rule in self.parent.rules]
  764. self.rules.Write("rules", data, "bencode-list")
  765. self.rules.Flush()
  766. elif obj == "history":
  767. self.history.Write("history", self.parent.history, "bencode-dict")
  768. self.history.Flush()
  769. else:
  770. raise Exception("Unknown object")
  771. #################################################################################
  772. class GetFeeds(Thread):
  773. """
  774. Helper class for parsing multiple feeds using ThreadPool
  775. """
  776. def __init__(self, parent, feeds):
  777. Thread.__init__(self)
  778. self.setDaemon(True)
  779. self.parent = parent
  780. self.single = False
  781. if type(feeds) is not list:
  782. feeds = [feeds]
  783. self.single = True
  784. self.feeds = feeds
  785. def handle_exception(self, request, exc_info):
  786. """
  787. This will be called when an exception occurs within a thread
  788. """
  789. pass
  790. ## print "Exception occured in request #%s: %s" % \
  791. ## (request.requestID, exc_info[1])
  792. def run(self):
  793. """
  794. Create the ThreadPool and add tasks
  795. """
  796. try:
  797. self._run()
  798. except:
  799. pass
  800. def _run(self):
  801. tasks = []
  802. for feed in self.feeds:
  803. tasks.extend(makeRequests(self.getFeed, [feed], exc_callback = self.handle_exception))
  804. tp = ThreadPool(4)
  805. for task in tasks:
  806. tp.putRequest(task)
  807. tp.wait()
  808. self.done()
  809. def getFeed(self, feed):
  810. """
  811. The task to perform: parse the feed
  812. """
  813. data = None
  814. try:
  815. # Get Data
  816. data = parse(feed.options.url, agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
  817. cookies=feed.getCookie(), etag=feed.etag, modified = feed.modified)
  818. # Save etags
  819. try:
  820. feed.etag = data.etag
  821. except:
  822. pass
  823. # Save last-modified
  824. if data.has_key('modified'):
  825. feed.modified = data.modified
  826. except:
  827. return False
  828. try:
  829. feed.Getdata(data)
  830. except wx.PyDeadObjectError:
  831. pass
  832. return True
  833. def done(self):
  834. """
  835. Called when all threads are done
  836. """
  837. try:
  838. if self.single:
  839. if self.feeds[0].rssarticles:
  840. self.parent.updateList(self.feeds[0])
  841. else:
  842. self.parent.onUpdateAllDone()
  843. except wx.PyDeadObjectError:
  844. pass