Python

by Henk on March 9th, 2010
No notes
Syntax: Python
Show lines - Hide lines - Show in textbox - Download
  1. #!/usr/bin/python -OO
  2. # Copyright 2008-2009 The SABnzbd-Team <team@sabnzbd.org>
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; either version 2
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17.  
  18. """
  19. sabnzbd.decoder - article decoder
  20. """
  21.  
  22. import Queue
  23. import binascii
  24. import logging
  25. import re
  26. from threading import Thread
  27. try:
  28. import _yenc
  29. HAVE_YENC = True
  30.  
  31. except ImportError:
  32. HAVE_YENC = False
  33.  
  34. import sabnzbd
  35. from sabnzbd.constants import *
  36. import sabnzbd.articlecache as articlecache
  37. import sabnzbd.downloader
  38. import sabnzbd.cfg as cfg
  39. import sabnzbd.nzbqueue
  40. from sabnzbd.codecs import name_fixer
  41. from sabnzbd.lang import Ta
  42.  
  43. #-------------------------------------------------------------------------------
  44.  
  45. class CrcError(Exception):
  46. def __init__(self, needcrc, gotcrc, data):
  47. Exception.__init__(self)
  48. self.needcrc = needcrc
  49. self.gotcrc = gotcrc
  50. self.data = data
  51.  
  52. class BadYenc(Exception):
  53. def __init__(self):
  54. Exception.__init__(self)
  55.  
  56. #-------------------------------------------------------------------------------
  57.  
  58. class Decoder(Thread):
  59. def __init__(self, servers):
  60. Thread.__init__(self)
  61.  
  62. self.queue = Queue.Queue()
  63. self.servers = servers
  64.  
  65. def decode(self, article, lines):
  66. self.queue.put((article, lines))
  67. if self.queue.qsize() > MAX_DECODE_QUEUE:
  68. sabnzbd.downloader.delay_downloader()
  69.  
  70. def stop(self):
  71. self.queue.put(None)
  72.  
  73. def run(self):
  74. while 1:
  75. art_tup = self.queue.get()
  76. if not art_tup:
  77. break
  78.  
  79. if self.queue.qsize() < MIN_DECODE_QUEUE and sabnzbd.downloader.delayed():
  80. sabnzbd.downloader.undelay_downloader()
  81.  
  82. article, lines = art_tup
  83. nzf = article.nzf
  84. nzo = nzf.nzo
  85.  
  86. data = None
  87.  
  88. register = True
  89.  
  90. if lines:
  91. try:
  92. logging.info("Decoding %s", article)
  93.  
  94. data = decode(article, lines)
  95. nzf.increase_article_count()
  96. except IOError, e:
  97. logging.error(Ta('error-decode@1'), article)
  98. sabnzbd.downloader.pause_downloader()
  99.  
  100. article.fetcher = None
  101.  
  102. sabnzbd.nzbqueue.reset_try_lists(nzf, nzo)
  103.  
  104. register = False
  105.  
  106. except CrcError, e:
  107. logging.warning(Ta('warn-crcError@3'), article, e.needcrc, e.gotcrc)
  108.  
  109. data = e.data
  110.  
  111. if cfg.FAIL_ON_CRC.get():
  112. new_server_found = self.__search_new_server(article)
  113. if new_server_found:
  114. register = False
  115.  
  116. except BadYenc, e:
  117. logging.warning("Badly formed yEnc article in %s", article)
  118.  
  119. if cfg.FAIL_ON_CRC.get():
  120. new_server_found = self.__search_new_server(article)
  121. if new_server_found:
  122. register = False
  123.  
  124. except:
  125. logging.error(Ta('error-decodeUnknown@1'), article)
  126.  
  127. else:
  128. new_server_found = self.__search_new_server(article)
  129. if new_server_found:
  130. register = False
  131.  
  132. if data:
  133. articlecache.method.save_article(article, data)
  134.  
  135. if register:
  136. sabnzbd.nzbqueue.register_article(article)
  137.  
  138. def __search_new_server(self, article):
  139. article.add_to_try_list(article.fetcher)
  140.  
  141. nzf = article.nzf
  142. nzo = nzf.nzo
  143.  
  144. new_server_found = False
  145. fill_server_found = False
  146.  
  147. for server in self.servers:
  148. if server.active and not article.server_in_try_list(server):
  149. if server.fillserver:
  150. fill_server_found = True
  151. else:
  152. new_server_found = True
  153. break
  154.  
  155. # Only found one (or more) fill server(s)
  156. if not new_server_found and fill_server_found:
  157. article.allow_fill_server = True
  158. new_server_found = True
  159.  
  160. if new_server_found:
  161. article.fetcher = None
  162.  
  163. ## Allow all servers to iterate over this nzo and nzf again ##
  164. sabnzbd.nzbqueue.reset_try_lists(nzf, nzo)
  165.  
  166. logging.info('%s => found at least one untested server', article)
  167.  
  168. else:
  169. logging.warning(Ta('warn-artAllMissing@1'), article)
  170.  
  171. return new_server_found
  172. #-------------------------------------------------------------------------------
  173.  
  174. YDEC_TRANS = ''.join([chr((i + 256 - 42) % 256) for i in xrange(256)])
  175. def decode(article, data):
  176. data = strip(data)
  177. ## No point in continuing if we don't have any data left
  178. if data:
  179. nzf = article.nzf
  180. nzo = nzf.nzo
  181. yenc, data = yCheck(data)
  182. ybegin, ypart, yend = yenc
  183. decoded_data = None
  184.  
  185. #Deal with non-yencoded posts
  186. if not ybegin:
  187. found = False
  188. for i in xrange(10):
  189. if data[i].startswith('begin '):
  190. nzf.set_filename(name_fixer(data[i].split(None, 2)[2]))
  191. nzf.set_type('uu')
  192. found = True
  193. break
  194. if found:
  195. for n in xrange(i):
  196. data.pop(0)
  197. if data[-1] == 'end':
  198. data.pop()
  199. if data[-1] == '`':
  200. data.pop()
  201.  
  202. decoded_data = '\r\n'.join(data)
  203.  
  204. #Deal with yenc encoded posts
  205. elif (ybegin and yend):
  206. if 'name' in ybegin:
  207. nzf.set_filename(name_fixer(ybegin['name']))
  208. else:
  209. logging.debug("Possible corrupt header detected " + \
  210. "=> ybegin: %s", ybegin)
  211. nzf.set_type('yenc')
  212. # Decode data
  213. if HAVE_YENC:
  214. decoded_data, crc = _yenc.decode_string(''.join(data))[:2]
  215. partcrc = '%08X' % ((crc ^ -1) & 2**32L - 1)
  216. else:
  217. data = ''.join(data)
  218. for i in (0, 9, 10, 13, 27, 32, 46, 61):
  219. j = '=%c' % (i + 64)
  220. data = data.replace(j, chr(i))
  221. decoded_data = data.translate(YDEC_TRANS)
  222. crc = binascii.crc32(decoded_data)
  223. partcrc = '%08X' % (crc & 2**32L - 1)
  224.  
  225. if ypart:
  226. crcname = 'pcrc32'
  227. else:
  228. crcname = 'crc32'
  229.  
  230. if crcname in yend:
  231. _partcrc = '0' * (8 - len(yend[crcname])) + yend[crcname].upper()
  232. else:
  233. _partcrc = None
  234. logging.debug("Corrupt header detected " + \
  235. "=> yend: %s", yend)
  236.  
  237. if not (_partcrc == partcrc):
  238. raise CrcError(_partcrc, partcrc, decoded_data)
  239. else:
  240. raise BadYenc()
  241.  
  242. return decoded_data
  243.  
  244. def yCheck(data):
  245. ybegin = None
  246. ypart = None
  247. yend = None
  248.  
  249. ## Check head
  250. for i in xrange(10):
  251. try:
  252. if data[i].startswith('=ybegin '):
  253. splits = 3
  254. if data[i].find(' part=') > 0:
  255. splits += 1
  256. if data[i].find(' total=') > 0:
  257. splits += 1
  258.  
  259. ybegin = ySplit(data[i], splits)
  260.  
  261. if data[i+1].startswith('=ypart '):
  262. ypart = ySplit(data[i+1])
  263. data = data[i+2:]
  264. break
  265. else:
  266. data = data[i+1:]
  267. break
  268. except IndexError:
  269. break
  270.  
  271. ## Check tail
  272. for i in xrange(-1, -11, -1):
  273. try:
  274. if data[i].startswith('=yend '):
  275. yend = ySplit(data[i])
  276. data = data[:i]
  277. break
  278. except IndexError:
  279. break
  280.  
  281. return ((ybegin, ypart, yend), data)
  282.  
  283. # Example: =ybegin part=1 line=128 size=123 name=-=DUMMY=- abc.par
  284. YSPLIT_RE = re.compile(r'([a-zA-Z0-9]+)=')
  285. def ySplit(line, splits = None):
  286. fields = {}
  287.  
  288. if splits:
  289. parts = YSPLIT_RE.split(line, splits)[1:]
  290. else:
  291. parts = YSPLIT_RE.split(line)[1:]
  292.  
  293. if len(parts) % 2:
  294. return fields
  295.  
  296. for i in range(0, len(parts), 2):
  297. key, value = parts[i], parts[i+1]
  298. fields[key] = value.strip()
  299.  
  300. return fields
  301.  
  302. def strip(data):
  303. while data and not data[0]:
  304. data.pop(0)
  305.  
  306. while data and not data[-1]:
  307. data.pop()
  308.  
  309. for i in xrange(len(data)):
  310. if data[i][:2] == '..':
  311. data[i] = data[i][1:]
  312. return data
  313.  

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS