Advertisement
Guest User

Untitled

a guest
May 27th, 2018
147
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.01 KB | None | 0 0
  1. # Copyright (C) 2008 LottaNZB Development Team
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; version 3.
  6. #
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with this program; if not, write to the Free Software
  14. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
  15.  
  16. """
  17. This module can be used to download NZB files from the Newzbin service. It
  18. does not provide search capabilities and they are unlikely to be added in the
  19. future.
  20. """
  21.  
  22. import re
  23. import gzip
  24. import shutil
  25. import os
  26.  
  27. import logging
  28. log = logging.getLogger(__name__)
  29.  
  30. from time import sleep
  31. from gettext import ngettext
  32. from httplib import HTTPConnection, HTTPException
  33. from urllib import urlencode
  34.  
  35. from lottanzb import __version__
  36. from lottanzb.util import gproperty, gsignal, _
  37. from lottanzb.core import App
  38. from lottanzb.util import Thread
  39.  
  40. class Interface(Thread):
  41. """
  42. Abstract base class containing information required to connect to the
  43. Newzbin servers.
  44. """
  45.  
  46. HOST = "v3.newzbin.com"
  47. AGENT = "lottanzb/%s" % __version__
  48. HEADERS = {
  49. "Content-Type": "application/x-www-form-urlencoded",
  50. "Accept-Encoding": "gzip",
  51. "Accept": "text/plain"
  52. }
  53.  
  54. username = gproperty(type=str)
  55. password = gproperty(type=str)
  56.  
  57. def __init__(self, username, password):
  58. Thread.__init__(self)
  59.  
  60. self.username = username
  61. self.password = password
  62.  
  63. self.params = { "username": self.username, "password": self.password }
  64.  
  65. # The only way to handle exceptions in a Thread seems to be by storing
  66. # the exception object as a property in the Thread object. Handlers
  67. # attached to the 'completed' event will have to check whether this
  68. # property is None or not.
  69. self.exception = None
  70.  
  71. def run(self):
  72. """Needs to be implemented by subclasses."""
  73.  
  74. raise NotImplementedError
  75.  
  76. class Downloader(Interface):
  77. """
  78. Asynchronously downloads a Newzbin NZB based on the corresponding report ID.
  79. """
  80.  
  81. report_id = gproperty(
  82. type=int)
  83.  
  84. name = gproperty(
  85. type=str,
  86. nick="Download name, as seen on Newzbin")
  87.  
  88. category = gproperty(
  89. type=str,
  90. nick="English download category")
  91.  
  92. more_info = gproperty(
  93. type=str,
  94. nick="URL referring to additional information about the download")
  95.  
  96. nfo_id = gproperty(
  97. type=int,
  98. nick="ID of the NFO file bundled with the download, if any")
  99.  
  100. filename = gproperty(
  101. type=str,
  102. nick="The absolute path and name of the downloaded NZB file")
  103.  
  104. gsignal("retry", int)
  105.  
  106. def __init__(self, username, password, report_id):
  107. Interface.__init__(self, username, password)
  108.  
  109. self.report_id = report_id
  110. self.connection = None
  111. self.params["reportid"] = self.report_id
  112.  
  113. def run(self):
  114. """
  115. Downloads the NZB file denoted by the Newzbin report ID. Instead of this
  116. method, the start method should be called to start the download
  117. opertation.
  118.  
  119. The 'completed' event is emitted as soon as the download is complete or
  120. there was an error during the process.
  121. """
  122.  
  123. try:
  124. try:
  125. self.__download()
  126. except (NewzbinError, HTTPException, IOError), error:
  127. log.error(str(error))
  128.  
  129. self.exception = error
  130. finally:
  131. # I wish I had a 'with' statement...
  132. self.connection.close()
  133.  
  134. self.emit("completed")
  135.  
  136. def __download(self):
  137. """Does the actual work."""
  138.  
  139. log.debug("Downloading NZB for Newzbin file ID %i..." % self.report_id)
  140.  
  141. self.connection = HTTPConnection(self.HOST)
  142. self.connection.request("POST", "api/dnzb/", \
  143. urlencode(self.params), self.HEADERS)
  144.  
  145. # Synchronous
  146. response = self.connection.getresponse()
  147.  
  148. if response.status == 500:
  149. raise InvalidResponseError()
  150. elif response.status == 503:
  151. raise ServiceUnavailableError()
  152.  
  153. # Newzbin-specific response codes
  154. newzbin_code = response.getheader("x-dnzb-rcode")
  155.  
  156. if newzbin_code == "400":
  157. raise ReportNotFoundError(self.report_id)
  158. if newzbin_code == "401":
  159. raise InvalidCredentialsError()
  160. elif newzbin_code == "402":
  161. raise NoPremiumAccountError()
  162. elif newzbin_code == "404":
  163. raise ReportNotFoundError(self.report_id)
  164. elif newzbin_code == "450":
  165. text = response.getheader("x-dnzb-rtext")
  166. match = re.search(r"wait (\d+) second", text)
  167.  
  168. if match:
  169. # TODO: Needs testing.
  170. timeout = match.group(1)
  171.  
  172. log.warning(ngettext(
  173. _("Too many NZB download requests. Waiting %d second."),
  174. _("Too many NZB download requests. Waiting %d seconds."),
  175. timeout)
  176. )
  177.  
  178. self.connection.close()
  179. self.emit("retry", timeout)
  180.  
  181. sleep(timeout + 5)
  182.  
  183. self.__download()
  184. else:
  185. # Don't know if there are other reasons a 450 error could
  186. # show up.
  187. raise InvalidResponseError()
  188.  
  189. elif newzbin_code == "200":
  190. # Extracting the meta data.
  191. self.name = response.getheader("x-dnzb-name")
  192. self.category = response.getheader("x-dnzb-category")
  193. self.more_info = response.getheader("x-dnzb-moreinfo")
  194. self.nfo_id = response.getheader("x-dnzb-nfo")
  195.  
  196. self.filename = App().temp_dir(self.name + ".nzb")
  197.  
  198. dest_file = open(self.filename, "wb")
  199.  
  200. if response.getheader("content-encoding") == "gzip":
  201. log.debug("Extracting compressed Newzbin NZB...")
  202.  
  203. gzipped_filename = self.filename + ".gz"
  204.  
  205. gzipped_file = open(gzipped_filename, "wb")
  206. gzipped_file.write(response.read())
  207. gzipped_file.close()
  208.  
  209. gzipped_file = gzip.open(gzipped_filename)
  210. shutil.copyfileobj(gzipped_file, dest_file)
  211. gzipped_file.close()
  212. os.remove(gzipped_filename)
  213. else:
  214. dest_file.write(response.read())
  215.  
  216. dest_file.close()
  217.  
  218. class NewzbinError(Exception):
  219. def __str__(self):
  220. return self.message
  221.  
  222. class InvalidResponseError(NewzbinError):
  223. message = _("Invalid response from the Newzbin server received.")
  224.  
  225. class ServiceUnavailableError(NewzbinError):
  226. message = _("The Newzbin service is currently unavailable.")
  227.  
  228. class InvalidCredentialsError(NewzbinError):
  229. message = _("The specified Newzbin username or password is invalid.")
  230.  
  231. class ReportNotFoundError(NewzbinError):
  232. def __init__(self, report_id):
  233. self.report_id = report_id
  234.  
  235. NewzbinError.__init__(self)
  236.  
  237. def __str__(self):
  238. return _("Newzbin report %i does not exist.") % self.report_id
  239.  
  240. class NoPremiumAccountError(NewzbinError):
  241. message = _("The specified account does not have any Premium credit left.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement