Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Copyright (C) 2008 LottaNZB Development Team
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; version 3.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
- """
- This module can be used to download NZB files from the Newzbin service. It
- does not provide search capabilities and they are unlikely to be added in the
- future.
- """
- import re
- import gzip
- import shutil
- import os
- import logging
- log = logging.getLogger(__name__)
- from time import sleep
- from gettext import ngettext
- from httplib import HTTPConnection, HTTPException
- from urllib import urlencode
- from lottanzb import __version__
- from lottanzb.util import gproperty, gsignal, _
- from lottanzb.core import App
- from lottanzb.util import Thread
- class Interface(Thread):
- """
- Abstract base class containing information required to connect to the
- Newzbin servers.
- """
- HOST = "v3.newzbin.com"
- AGENT = "lottanzb/%s" % __version__
- HEADERS = {
- "Content-Type": "application/x-www-form-urlencoded",
- "Accept-Encoding": "gzip",
- "Accept": "text/plain"
- }
- username = gproperty(type=str)
- password = gproperty(type=str)
- def __init__(self, username, password):
- Thread.__init__(self)
- self.username = username
- self.password = password
- self.params = { "username": self.username, "password": self.password }
- # The only way to handle exceptions in a Thread seems to be by storing
- # the exception object as a property in the Thread object. Handlers
- # attached to the 'completed' event will have to check whether this
- # property is None or not.
- self.exception = None
- def run(self):
- """Needs to be implemented by subclasses."""
- raise NotImplementedError
- class Downloader(Interface):
- """
- Asynchronously downloads a Newzbin NZB based on the corresponding report ID.
- """
- report_id = gproperty(
- type=int)
- name = gproperty(
- type=str,
- nick="Download name, as seen on Newzbin")
- category = gproperty(
- type=str,
- nick="English download category")
- more_info = gproperty(
- type=str,
- nick="URL referring to additional information about the download")
- nfo_id = gproperty(
- type=int,
- nick="ID of the NFO file bundled with the download, if any")
- filename = gproperty(
- type=str,
- nick="The absolute path and name of the downloaded NZB file")
- gsignal("retry", int)
- def __init__(self, username, password, report_id):
- Interface.__init__(self, username, password)
- self.report_id = report_id
- self.connection = None
- self.params["reportid"] = self.report_id
- def run(self):
- """
- Downloads the NZB file denoted by the Newzbin report ID. Instead of this
- method, the start method should be called to start the download
- opertation.
- The 'completed' event is emitted as soon as the download is complete or
- there was an error during the process.
- """
- try:
- try:
- self.__download()
- except (NewzbinError, HTTPException, IOError), error:
- log.error(str(error))
- self.exception = error
- finally:
- # I wish I had a 'with' statement...
- self.connection.close()
- self.emit("completed")
- def __download(self):
- """Does the actual work."""
- log.debug("Downloading NZB for Newzbin file ID %i..." % self.report_id)
- self.connection = HTTPConnection(self.HOST)
- self.connection.request("POST", "api/dnzb/", \
- urlencode(self.params), self.HEADERS)
- # Synchronous
- response = self.connection.getresponse()
- if response.status == 500:
- raise InvalidResponseError()
- elif response.status == 503:
- raise ServiceUnavailableError()
- # Newzbin-specific response codes
- newzbin_code = response.getheader("x-dnzb-rcode")
- if newzbin_code == "400":
- raise ReportNotFoundError(self.report_id)
- if newzbin_code == "401":
- raise InvalidCredentialsError()
- elif newzbin_code == "402":
- raise NoPremiumAccountError()
- elif newzbin_code == "404":
- raise ReportNotFoundError(self.report_id)
- elif newzbin_code == "450":
- text = response.getheader("x-dnzb-rtext")
- match = re.search(r"wait (\d+) second", text)
- if match:
- # TODO: Needs testing.
- timeout = match.group(1)
- log.warning(ngettext(
- _("Too many NZB download requests. Waiting %d second."),
- _("Too many NZB download requests. Waiting %d seconds."),
- timeout)
- )
- self.connection.close()
- self.emit("retry", timeout)
- sleep(timeout + 5)
- self.__download()
- else:
- # Don't know if there are other reasons a 450 error could
- # show up.
- raise InvalidResponseError()
- elif newzbin_code == "200":
- # Extracting the meta data.
- self.name = response.getheader("x-dnzb-name")
- self.category = response.getheader("x-dnzb-category")
- self.more_info = response.getheader("x-dnzb-moreinfo")
- self.nfo_id = response.getheader("x-dnzb-nfo")
- self.filename = App().temp_dir(self.name + ".nzb")
- dest_file = open(self.filename, "wb")
- if response.getheader("content-encoding") == "gzip":
- log.debug("Extracting compressed Newzbin NZB...")
- gzipped_filename = self.filename + ".gz"
- gzipped_file = open(gzipped_filename, "wb")
- gzipped_file.write(response.read())
- gzipped_file.close()
- gzipped_file = gzip.open(gzipped_filename)
- shutil.copyfileobj(gzipped_file, dest_file)
- gzipped_file.close()
- os.remove(gzipped_filename)
- else:
- dest_file.write(response.read())
- dest_file.close()
- class NewzbinError(Exception):
- def __str__(self):
- return self.message
- class InvalidResponseError(NewzbinError):
- message = _("Invalid response from the Newzbin server received.")
- class ServiceUnavailableError(NewzbinError):
- message = _("The Newzbin service is currently unavailable.")
- class InvalidCredentialsError(NewzbinError):
- message = _("The specified Newzbin username or password is invalid.")
- class ReportNotFoundError(NewzbinError):
- def __init__(self, report_id):
- self.report_id = report_id
- NewzbinError.__init__(self)
- def __str__(self):
- return _("Newzbin report %i does not exist.") % self.report_id
- class NoPremiumAccountError(NewzbinError):
- message = _("The specified account does not have any Premium credit left.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement