Exemplo n.º 1
0
def main(nzbfile):
    try:
        nzb = open(nzbfile, "r").read()
        nzbparse = nzb_parser.parse(nzb)
    except:
        return False

    content = ""
    for f in nzbparse:
        unixdate = str(((f.date.toordinal() - date(1970, 1, 1).toordinal()) * 24 * 60 * 60))
        content += '<file poster="%s" date="%s" subject="%s">\n' % (
            cgi.escape(f.poster, True),
            unixdate,
            cgi.escape(f.subject, True),
        )

        content += "\t<groups>\n"
        for g in f.groups:
            content += "\t\t<group>%s</group>\n" % (g)
        content += "\t</groups>\n"

        content += "\t<segments>\n"
        for s in f.segments:
            content += '\t\t<segment bytes="%s" number="%s">%s</segment>\n' % (
                str(s.bytes),
                str(s.number),
                s.message_id,
            )
        content += "\t</segments>\n"

        content += "</file>\n"

    return content
Exemplo n.º 2
0
def main(nzbfile):
    try:
        nzb = open(nzbfile, 'r').read()
    except:
        print('Error reading nzb.')
        return False

    try:
        nzbparse = nzb_parser.parse(nzb)
    except:
        print('Error parsing nzb.')
        return False

    nzbsubs = [] 
    for f in nzbparse:
        if re.search(r'[._-](vob)?sub(title)?s?[._-]?.*\.(r(ar|\d+)|sfv|srt|idx|sub|par2)\"', f.subject, re.IGNORECASE) is not None:
            nzbsubs.append(f)

    if len(nzbsubs) == 0:
        print('No subs found in nzb.')
        return False

    content = '<?xml version="1.0" encoding="UTF-8"?>\n'
    content += '<!DOCTYPE nzb PUBLIC "-//newzBin//DTD NZB 1.1//EN" "http://www.newzbin.com/DTD/nzb/nzb-1.1.dtd">\n'
    content += '<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">\n'
    for f in nzbsubs:
        unixdate = str(((f.date.toordinal() - date(1970, 1, 1).toordinal()) * 24*60*60))
        content += '<file poster="%s" date="%s" subject="%s">\n' % (cgi.escape(f.poster, True), unixdate, cgi.escape(f.subject, True))

        content += '<groups>\n'
        for g in f.groups:
            content += '<group>%s</group>\n' % (g)
        content += '</groups>\n'

        content += '<segments>\n'
        for s in f.segments:
            content += '<segment bytes="%s" number="%s">%s</segment>\n' % (str(s.bytes), str(s.number), s.message_id)
        content += '</segments>\n'

        content += '</file>\n'

    content += '</nzb>'

    try:
        os.unlink(nzbfile)
    except:
        print('Error deleting input nzb.')
        return False

    with open(nzbfile, 'w') as f:
        try:
            f.write(content)
        except:
            print('Error writing nzb.')
            return False

    print('%r successfully processed.' % nzbfile)
Exemplo n.º 3
0
    def _get_files(self, url):
        files = {}

        try:
            resp = sickrage.srCore.srWebSession.get(url)

            for file in nzb_parser.parse(resp.content):
                total_length = 0
                for segment in file.segments:
                    total_length += int(segment.bytes)

                files[file.subject] = total_length
        except Exception:
            pass

        return files
Exemplo n.º 4
0
    def _get_files(self, url):
        files = {}

        try:
            resp = sickrage.srCore.srWebSession.get(url, raise_exceptions=False)

            for file in nzb_parser.parse(resp.content):
                total_length = 0
                for segment in file.segments:
                    total_length += int(segment.bytes)

                files[files.subject] = total_length
        except Exception:
            pass

        return files
Exemplo n.º 5
0
    def _get_size(self, url):
        size = -1

        try:
            resp = sickrage.srCore.srWebSession.get(url)

            total_length = 0
            for file in nzb_parser.parse(resp.content):
                for segment in file.segments:
                    total_length += int(segment.bytes)

            if total_length > 0:
                size = total_length
        except Exception:
            pass

        return size
Exemplo n.º 6
0
    def _get_size(self, url):
        size = -1

        try:
            resp = sickrage.srCore.srWebSession.get(url, raise_exceptions=False)

            total_length = 0
            for file in nzb_parser.parse(resp.content):
                for segment in file.segments:
                    total_length += int(segment.bytes)

            if total_length > 0:
                size = total_length
        except Exception:
            pass

        return size
Exemplo n.º 7
0
    def on_task_modify(self, task, config):
        """
        The downloaded file is accessible in modify phase
        """
        try:
            from pynzb import nzb_parser
        except ImportError:
            # TODO: remove builtin status so this won't get repeated on every task execution
            # TODO: this will get loaded even without any need for nzb
            raise plugin.DependencyError(issued_by='nzb_size', missing='lib pynzb')

        for entry in task.accepted:
            if (
                entry.get('mime-type') in ['text/nzb', 'application/x-nzb']
                or entry.get('filename')
                and entry['filename'].endswith('.nzb')
            ):

                if 'file' not in entry:
                    log.warning(
                        '`%s` does not have a `file` that could be used to get size information'
                        % entry['title']
                    )
                    continue

                filename = entry['file']
                log.debug('reading %s' % filename)
                xmldata = open(filename).read()

                try:
                    nzbfiles = nzb_parser.parse(xmldata)
                except Exception:
                    log.debug('%s is not a valid nzb' % entry['title'])
                    continue

                size = 0
                for nzbfile in nzbfiles:
                    for segment in nzbfile.segments:
                        size += segment.bytes

                size_mb = size / 1024 / 1024
                log.debug('%s content size: %s MB' % (entry['title'], size_mb))
                entry['content_size'] = size_mb
            else:
                log.trace('%s does not seem to be nzb' % entry['title'])
Exemplo n.º 8
0
    def on_task_modify(self, task, config):
        """
        The downloaded file is accessible in modify phase
        """
        try:
            from pynzb import nzb_parser
        except ImportError:
            # TODO: remove builtin status so this won't get repeated on every task execution
            # TODO: this will get loaded even without any need for nzb
            raise plugin.DependencyError(issued_by='nzb_size', missing='lib pynzb')

        for entry in task.accepted:
            if (
                entry.get('mime-type') in ['text/nzb', 'application/x-nzb']
                or entry.get('filename')
                and entry['filename'].endswith('.nzb')
            ):

                if 'file' not in entry:
                    log.warning(
                        '`%s` does not have a `file` that could be used to get size information'
                        % entry['title']
                    )
                    continue

                filename = entry['file']
                log.debug('reading %s' % filename)
                xmldata = open(filename).read()

                try:
                    nzbfiles = nzb_parser.parse(xmldata)
                except Exception:
                    log.debug('%s is not a valid nzb' % entry['title'])
                    continue

                size = 0
                for nzbfile in nzbfiles:
                    for segment in nzbfile.segments:
                        size += segment.bytes

                size_mb = size / 1024 / 1024
                log.debug('%s content size: %s MB' % (entry['title'], size_mb))
                entry['content_size'] = size_mb
            else:
                log.trace('%s does not seem to be nzb' % entry['title'])
Exemplo n.º 9
0
    def on_task_modify(self, task, config):
        """
        The downloaded file is accessible in modify phase
        """
        try:
            from pynzb import nzb_parser
        except ImportError:
            # TODO: remove builtin status so this won't get repeated on every task execution
            # TODO: this will get loaded even without any need for nzb
            raise plugin.DependencyError(issued_by="nzb_size", missing="lib pynzb")

        for entry in task.accepted:
            if entry.get("mime-type", None) in ["text/nzb", "application/x-nzb"] or entry.get("filename", "").endswith(
                ".nzb"
            ):

                if "file" not in entry:
                    log.warning(
                        "`%s` does not have a `file` that could be used to get size information" % entry["title"]
                    )
                    continue

                filename = entry["file"]
                log.debug("reading %s" % filename)
                xmldata = file(filename).read()

                try:
                    nzbfiles = nzb_parser.parse(xmldata)
                except:
                    log.debug("%s is not a valid nzb" % entry["title"])
                    continue

                size = 0
                for nzbfile in nzbfiles:
                    for segment in nzbfile.segments:
                        size += segment.bytes

                size_mb = size / 1024 / 1024
                log.debug("%s content size: %s MB" % (entry["title"], size_mb))
                entry["content_size"] = size_mb
            else:
                log.trace("%s does not seem to be nzb" % entry["title"])
Exemplo n.º 10
0
    def parseNZB(self, output):
        try:
            if output is None:
                raise InputError

            nzb_files = nzb_parser.parse(output)

            sub_seg = []
            subjects = []
            segments2 = []
            i = 0
            totalBytes = 0
            for nzb_file in nzb_files:
                subjects.append(nzb_file.subject)
                for segment in nzb_file.segments:
                    i = i + 1
                    tmpsegment = {}
                    tmpsegment['number'] = segment.number
                    tmpsegment['message_id'] = segment.message_id
                    tmpsegment['bytes'] = segment.bytes
                    totalBytes = totalBytes + segment.bytes
                    segments2.append(tmpsegment)

                d = {}
                segs = list(segments2)
                d['subject'] = nzb_file.subject
                d['segments'] = segs
                d['dates'] = '{}'.format(nzb_file.date)
                d['posters'] = '{}'.format(nzb_file.poster)
                d['groups'] = '{}'.format(nzb_file.groups)
                sub_seg.append(d)
                del segments2[:]

            self.nzbfile['sub_seg'] = sub_seg
            self.nzbfile['numsegments'] = i
            self.nzbfile['totalbytes'] = totalBytes
            return self.nzbfile

        except URLError, TypeError:
            return jsonify({"success":False})
Exemplo n.º 11
0
    def on_feed_modify(self, feed):
        """
        The downloaded file is accessible in modify phase
        """
        try:
            from pynzb import nzb_parser
        except ImportError:
            # TODO: remove builtin status so this won't get repeated on every feed execution
            # TODO: this will get loaded even without any need for nzb
            raise DependencyError(issued_by='nzb_size', missing='lib pynzb')

        for entry in feed.accepted:
            if entry.get('mime-type', None) in [u'text/nzb', u'application/x-nzb'] or \
               entry.get('filename', '').endswith('.nzb'):

                if 'file' not in entry:
                    log.warning('%s does not have a to get size from' % entry['title'])
                    continue

                filename = entry['file']
                log.debug('reading %s' % filename)
                xmldata = file(filename).read()

                try:
                    nzbfiles = nzb_parser.parse(xmldata)
                except:
                    log.debug('%s is not a valid nzb' % entry['title'])
                    continue

                size = 0
                for nzbfile in nzbfiles:
                    for segment in nzbfile.segments:
                        size += segment.bytes

                size_mb = size / 1024 / 1024
                log.debug('%s content size: %s MB' % (entry['title'], size_mb))
                entry['content_size'] = size_mb
            else:
                log.trace('%s does not seem to be nzb' % entry['title'])
Exemplo n.º 12
0
    def parse(self, filename):
        """
        Open the given file and parse it.
        """
        try:
            fh = open(filename)
        except:
            print("ERROR: Could not open NZB file '{filename}'".format(
                filename=filename))
            sys.exit(1)

        nzbxml = fh.read()
        fh.close()

        nzbdata = nzb_parser.parse(nzbxml)
        for nzb in nzbdata:
            self.results["totalFiles"] += 1
            for segment in nzb.segments:
                self.results["totalArticles"] += 1
                self.results["totalBytes"] += segment.bytes
        self.results["nzbdata"] = nzbdata
        return self.results
Exemplo n.º 13
0
    # checking on the result...
    if result.find('2')==0:
		body = getlinesuntildot(s,'')
    return result,body


    
    
### MAIN ###

if len(sys.argv) < 2:
    sys.exit('Usage: %s <nzb-filename>' % sys.argv[0])

nzbfilename = sys.argv[1]
my_nzb = open(nzbfilename).read()
files = nzb_parser.parse(my_nzb)

s,welcomemsg=connecttoserver('newszilla6.xs4all.nl',119)
# show Welcome message:
print "Welkom bericht:", welcomemsg

#print "DATE resultaat:", getoneline(s,'DATE')
#print "HELP resultaat:", getlinesuntildot(s,'HELP')
#print "LIST resultaat:", getlinesuntildot(s,'LIST')

#print "BLABLA resultaat:", getoneline(s,'BLABLA')

# Print out each file's subject and the first two segment message ids
counter=100000	# for the filenames
import time
timestamp = int(time.time())
Exemplo n.º 14
0
def main(nzbfile):
    try:
        if nzbfile.split('.')[-1] == 'gz':
            nzb = gzip.open(nzbfile, 'r').read()
        else:
            nzb = open(nzbfile, 'r').read()
    except:
        print('Error reading nzb.')
        return False

    try:
        nzbparse = nzb_parser.parse(nzb)
    except:
        print('Error parsing nzb.')
        return False

    posters = []
    dates = []
    subjects = []
    parfiles = []
    groups = []
    segments = []

    filecount = 0
    filesize = 0
    parsize = 0
    parredundancy = 0

    for f in nzbparse:
        posters.append(f.poster)
        dates.append(f.date)
        subjects.append(f.subject)

        if re.search(r'\.par2\"', f.subject, re.IGNORECASE) is not None:
            parfiles.append(f)

        for g in f.groups:
            groups.append(g)

        for s in f.segments:
            segments.append(s)

    print('Complete name\t\t\t : %s' % nzbfile)

    sys.stdout.write('Poster(s)\t\t\t : ')
    sys.stdout.flush()
    print(', '.join(str(s) for s in set(posters)))

    print('Age\t\t\t\t : %s' % (date.today() - sorted(dates)[-1]))

    sys.stdout.write('Group(s)\t\t\t : ')
    sys.stdout.flush()
    print(', '.join(str(s) for s in set(groups)))

    print('File count\t\t\t : %s (+%s)' %
          ((len(subjects) - len(parfiles)), len(parfiles)))

    for s in set(segments):
        filesize += s.bytes

    for p in parfiles:
        for ps in p.segments:
            parsize += ps.bytes

    print('File size\t\t\t : %s MB' % ((filesize - parsize) / float(1 << 20)))

    if parsize:
        parredundancy = (filesize / parsize)

    print('Par redundancy\t\t\t : %s' % parredundancy + '%')

    print('\n')
Exemplo n.º 15
0
    def __init__(self,
                 server,
                 port,
                 username,
                 password,
                 num_threads,
                 nzb_source,
                 nzb_directory,
                 par2exe_directory,
                 common_prefix="",
                 progress_update=None):
        self.server = server
        self.port = port
        self.username = username
        self.password = password
        self.num_threads = num_threads

        self.par2exe_directory = par2exe_directory
        self.progress_update = progress_update

        self.common_prefix = common_prefix
        self.rar_filepath = None
        self.sorted_filenames = list()
        self.downloaded_files = dict()
        self.download_queue = Queue.Queue(0)
        self.decode_queue = Queue.Queue(0)
        self.cancelled = False
        self.finished_files = 0

        self.threads = list()

        # note on calls to _update_progress: a call is made before the task begins and after the task completes;
        # this allows the consumer to cancel during the task

        nzb_string = ""
        nzb_files = None
        nzb_filepath = ""
        try:
            title = self.common_prefix
            if title == "":
                if nzb_source[:7] == "file://" and os.path.exists(
                        urllib.url2pathname(nzb_source)):
                    nzb_filepath = urllib.url2pathname(nzb_source)
                title = "NZB"
            else:
                parts = title.split('.')
                if len(parts) > 1:
                    ext = parts[-1].lower()
                    if ext == "par2" or ext == "nzb" or ext == "nfo":
                        title = '.'.join(parts[:-1])

            if nzb_filepath == "":
                nzb_filepath = os.path.join(nzb_directory, title) + ".nzb"

            print "NZB filepath: " + nzb_filepath

            if nzb_source.startswith("<?xml"):
                nzb_string = nzb_source
            elif os.path.exists(nzb_filepath) and os.path.isfile(nzb_filepath):
                nzb_file = open(nzb_filepath, "r")
                nzb_string = string.join(nzb_file.readlines(), "")
                nzb_file.close()
                #nzb_filepath = possible_nzb_filepath
            else:
                nzb_url = nzb_source
                if self._update_progress("Downloading " + title,
                                         STATUS_INITIALIZING,
                                         os.path.basename(nzb_url)):
                    return
                urllib.urlopen(nzb_url)
                nzb_string = string.join(
                    urllib.urlopen(nzb_url).readlines(), "")
                if self._update_progress("Downloading " + title,
                                         STATUS_INITIALIZING,
                                         os.path.basename(nzb_url)):
                    return

            if self._update_progress("Parsing " + title, STATUS_INITIALIZING,
                                     title):
                return
            nzb_files = nzb_parser.parse(nzb_string)
            sort_nzb_rar_files(nzb_files)

            for nzb_file in nzb_files:
                filename = sre.search("\"(.*?)\"", nzb_file.subject).group(1)
                filename = filename.encode('utf8').lower()
                self.sorted_filenames.append(filename)

            # a common prefix from the file list is preferred
            better_common_prefix = os.path.commonprefix(
                self.sorted_filenames).rstrip(". ")
            if better_common_prefix != "":
                self.common_prefix = better_common_prefix

            if self.common_prefix == "":
                self.common_prefix = self.sorted_filenames[0]

            parts = self.common_prefix.split('.')
            print parts
            if len(parts) > 1:
                ext = parts[-1].lower()
                if ext == "par2" or ext == "nzb" or ext == "nfo":
                    self.common_prefix = '.'.join(parts[:-1])

            print "Common prefix: " + self.common_prefix

            self.download_directory = os.path.join(nzb_directory,
                                                   self.common_prefix)
            self.status_filepath = os.path.join(self.download_directory,
                                                self.common_prefix + ".status")

            if self._update_progress("Parsing " + title, STATUS_INITIALIZING,
                                     title):
                return

            # make sure the download directory exists
            try:
                os.makedirs(self.download_directory)
            except:
                pass

            nzb_filepath = os.path.join(nzb_directory,
                                        self.common_prefix + ".nzb")
            nzb_filepath = nzb_filepath.encode('utf8')
            #print nzb_filepath
            #if os.path.exists(nzb_filepath) and os.path.isdir(nzb_filepath):
            #    shutil.rmtree(nzb_filepath) # remove the directory containing the nzb; it is rewritten below
            if not os.path.exists(nzb_filepath) or os.path.getsize(
                    nzb_filepath) != len(nzb_string):
                nzb = open(nzb_filepath, "w+b")
                nzb.write(nzb_string)
                nzb.close()

            # run par2 if we already have the .par2 file
            par2_file = os.path.join(self.download_directory,
                                     self.common_prefix + ".par2")
            print "PAR2 file: " + par2_file
            par2_targets = None
            if os.path.exists(par2_file):
                if self._update_progress("Verifying with PAR2",
                                         STATUS_INITIALIZING,
                                         os.path.basename(par2_file)):
                    return
                par2_targets = self._verify_with_par2(par2_file)
                if self._update_progress("Verifying with PAR2",
                                         STATUS_INITIALIZING,
                                         os.path.basename(par2_file)):
                    return

                #for target in par2_targets:
                #    print "\t" + target + ": " + par2_targets[target]

            nested_nzbs = list()

            for nzb_file in nzb_files:
                nzb_file.filename = sre.search(
                    "\"(.*?)\"", nzb_file.subject.lower()).group(1)
                nzb_file.filename = nzb_file.filename.encode('utf8')
                nzb_file.filepath = os.path.join(self.download_directory,
                                                 nzb_file.filename)
                nzb_file.filepath = nzb_file.filepath.encode('utf8')

                #print filepath
                # create an empty file if it doesn't exist
                if not os.path.exists(nzb_file.filepath):
                    open(nzb_file.filepath, "w+b").close()

                if not self.rar_filepath:
                    rar_match = sre.search("(.+?)\.part(\d+).rar",
                                           nzb_file.filename)
                    if rar_match and int(rar_match.group(2)) == 1:
                        self.rar_filepath = nzb_file.filepath
                        self.rar_filename = os.path.basename(nzb_file.filepath)
                    else:
                        rar_match = sre.search("(.+?)\.rar", nzb_file.filename)
                        if rar_match:
                            self.rar_filepath = nzb_file.filepath
                            self.rar_filename = os.path.basename(
                                nzb_file.filepath)
                    if self.rar_filepath:
                        print "First RAR file is " + self.rar_filename

                if os.path.splitext(nzb_file.filepath)[1] == ".nzb":
                    nested_nzbs.append(nzb_file.filepath)

                self.downloaded_files[nzb_file.filename] = multipart_file(
                    nzb_file.filename, nzb_file.filepath, nzb_file)

                nzb_file.finished = False

                # skip non-PAR2 files if par2 validated it
                if par2_targets and par2_targets.has_key(
                        nzb_file.filename) and par2_targets[
                            nzb_file.filename] == "found":
                    print "PAR2 verified " + nzb_file.filename + ": skipping"
                    self.finished_files += 1
                    self.downloaded_files[nzb_file.filename].finished = True
                    nzb_file.finished = True
                    continue

                # sort segments in ascending order by article number
                nzb_file.segments.sort(key=lambda obj: obj.number)

            # if no RAR file and no nested NZBs, abort
            if not self.rar_filepath:
                if len(nested_nzbs) == 0:
                    raise Exception(
                        "nothing to do: NZB did not have a RAR file or any nested NZBs"
                    )
                self.rar_filepath = self.rar_filename = ""

            # check if first RAR is already finished
            if par2_targets and par2_targets.has_key(
                    self.rar_filename) and par2_targets[
                        self.rar_filename] == "found":
                if self._update_progress("First RAR is ready.", STATUS_READY,
                                         self.rar_filepath):
                    return

            if self._update_progress(
                    "Starting " + ` self.num_threads ` + " download threads",
                    STATUS_INITIALIZING):
                return

            # queue first segment of each file to get each file's total size
            #for nzb_file in nzb_files:
            # skip non-PAR2 files if par2 validated it
            #if nzb_file.finished: continue
            #self.download_queue.put([nzb_file.filename, nzb_file, nzb_file.segments[0]], timeout=1)

            # queue the rest of the segments in order
            for nzb_file in nzb_files:

                # skip non-PAR2 files if par2 validated it
                if nzb_file.finished: continue

                if self._update_progress("Queueing file", STATUS_INITIALIZING,
                                         nzb_file.filename):
                    return
                for nzb_segment in nzb_file.segments[0:]:
                    self.download_queue.put(
                        [nzb_file.filename, nzb_file, nzb_segment], timeout=1)

            # start download threads
            for i in range(self.num_threads):
                thread = threading.Thread(name=` i `,
                                          target=self._download_thread)
                thread.start()
                self.threads.append(thread)

            if self._update_progress(
                    "Starting " + ` self.num_threads ` + " download threads",
                    STATUS_INITIALIZING):
                return
Exemplo n.º 16
0
    def __init__(self, server, port, username, password, num_threads, nzb_source, nzb_directory, par2exe_directory, common_prefix = "", progress_update = None):
        self.server = server
        self.port = port
        self.username = username
        self.password = password
        self.num_threads = num_threads

        self.par2exe_directory = par2exe_directory
        self.progress_update = progress_update

        self.common_prefix = common_prefix
        self.rar_filepath = None
        self.sorted_filenames = list()
        self.downloaded_files = dict()
        self.download_queue = Queue.Queue(0)
        self.decode_queue = Queue.Queue(0)
        self.cancelled = False
        self.finished_files = 0

        self.threads = list()

        # note on calls to _update_progress: a call is made before the task begins and after the task completes;
        # this allows the consumer to cancel during the task

        nzb_string = ""
        nzb_files = None
        nzb_filepath = ""
        try:
            title = self.common_prefix
            if title == "":
                if nzb_source[:7] == "file://" and os.path.exists(urllib.url2pathname(nzb_source)):
                    nzb_filepath = urllib.url2pathname(nzb_source)
                title = "NZB"
            else:
                parts = title.split('.')
                if len(parts) > 1:
                    ext = parts[-1].lower()
                    if ext == "par2" or ext == "nzb" or ext == "nfo":
                        title = '.'.join(parts[:-1])

            if nzb_filepath == "":
                nzb_filepath = os.path.join(nzb_directory, title) + ".nzb"

            print "NZB filepath: " + nzb_filepath

            if nzb_source.startswith("<?xml"):
                nzb_string = nzb_source
            elif os.path.exists(nzb_filepath) and os.path.isfile(nzb_filepath):
                nzb_file = open(nzb_filepath, "r")
                nzb_string = string.join(nzb_file.readlines(), "")
                nzb_file.close()
                #nzb_filepath = possible_nzb_filepath
            else:
                nzb_url = nzb_source
                if self._update_progress("Downloading " + title, STATUS_INITIALIZING, os.path.basename(nzb_url)): return
                urllib.urlopen(nzb_url)
                nzb_string = string.join(urllib.urlopen(nzb_url).readlines(), "")
                if self._update_progress("Downloading " + title, STATUS_INITIALIZING, os.path.basename(nzb_url)): return

            if self._update_progress("Parsing " + title, STATUS_INITIALIZING, title): return
            nzb_files = nzb_parser.parse(nzb_string)
            sort_nzb_rar_files(nzb_files)

            for nzb_file in nzb_files:
                filename = sre.search("\"(.*?)\"", nzb_file.subject).group(1)
                filename = filename.encode('utf8').lower()
                self.sorted_filenames.append(filename)

            # a common prefix from the file list is preferred
            better_common_prefix = os.path.commonprefix(self.sorted_filenames).rstrip(". ")
            if better_common_prefix != "":
                self.common_prefix = better_common_prefix

            if self.common_prefix == "":
                self.common_prefix = self.sorted_filenames[0]

            parts = self.common_prefix.split('.')
            print parts
            if len(parts) > 1:
                ext = parts[-1].lower()
                if ext == "par2" or ext == "nzb" or ext == "nfo":
                    self.common_prefix = '.'.join(parts[:-1])

            print "Common prefix: " + self.common_prefix

            self.download_directory = os.path.join(nzb_directory, self.common_prefix)
            self.status_filepath = os.path.join(self.download_directory, self.common_prefix + ".status")

            if self._update_progress("Parsing " + title, STATUS_INITIALIZING, title): return

            # make sure the download directory exists
            try: os.makedirs(self.download_directory)
            except: pass

            nzb_filepath = os.path.join(nzb_directory, self.common_prefix + ".nzb" )
            nzb_filepath = nzb_filepath.encode('utf8')
            #print nzb_filepath
            #if os.path.exists(nzb_filepath) and os.path.isdir(nzb_filepath):
            #    shutil.rmtree(nzb_filepath) # remove the directory containing the nzb; it is rewritten below
            if not os.path.exists(nzb_filepath) or os.path.getsize(nzb_filepath) != len(nzb_string):
                nzb = open(nzb_filepath, "w+b")
                nzb.write(nzb_string)
                nzb.close()

            # run par2 if we already have the .par2 file
            par2_file = os.path.join(self.download_directory, self.common_prefix + ".par2")
            print "PAR2 file: " + par2_file
            par2_targets = None
            if os.path.exists(par2_file):
                if self._update_progress("Verifying with PAR2", STATUS_INITIALIZING, os.path.basename(par2_file)): return
                par2_targets = self._verify_with_par2(par2_file)
                if self._update_progress("Verifying with PAR2", STATUS_INITIALIZING, os.path.basename(par2_file)): return

                #for target in par2_targets:
                #    print "\t" + target + ": " + par2_targets[target]

            nested_nzbs = list()

            for nzb_file in nzb_files:
                nzb_file.filename = sre.search("\"(.*?)\"", nzb_file.subject.lower()).group(1)
                nzb_file.filename = nzb_file.filename.encode('utf8')
                nzb_file.filepath = os.path.join(self.download_directory, nzb_file.filename)
                nzb_file.filepath = nzb_file.filepath.encode('utf8')

                #print filepath
                # create an empty file if it doesn't exist
                if not os.path.exists(nzb_file.filepath):
                    open(nzb_file.filepath, "w+b").close()

                if not self.rar_filepath:
                    rar_match = sre.search("(.+?)\.part(\d+).rar", nzb_file.filename)
                    if rar_match and int(rar_match.group(2)) == 1:
                        self.rar_filepath = nzb_file.filepath
                        self.rar_filename = os.path.basename(nzb_file.filepath)
                    else:
                        rar_match = sre.search("(.+?)\.rar", nzb_file.filename)
                        if rar_match:
                            self.rar_filepath = nzb_file.filepath
                            self.rar_filename = os.path.basename(nzb_file.filepath)
                    if self.rar_filepath:
                        print "First RAR file is " + self.rar_filename

                if os.path.splitext(nzb_file.filepath)[1] == ".nzb":
                    nested_nzbs.append(nzb_file.filepath)

                self.downloaded_files[nzb_file.filename] = multipart_file(nzb_file.filename, nzb_file.filepath, nzb_file)

                nzb_file.finished = False

                # skip non-PAR2 files if par2 validated it
                if par2_targets and par2_targets.has_key(nzb_file.filename) and par2_targets[nzb_file.filename] == "found":
                    print "PAR2 verified " + nzb_file.filename + ": skipping"
                    self.finished_files += 1
                    self.downloaded_files[nzb_file.filename].finished = True
                    nzb_file.finished = True
                    continue

                # sort segments in ascending order by article number
                nzb_file.segments.sort(key=lambda obj: obj.number)

            # if no RAR file and no nested NZBs, abort
            if not self.rar_filepath:
                if len(nested_nzbs) == 0:
                    raise Exception("nothing to do: NZB did not have a RAR file or any nested NZBs")
                self.rar_filepath = self.rar_filename = ""

            # check if first RAR is already finished
            if par2_targets and par2_targets.has_key(self.rar_filename) and par2_targets[self.rar_filename] == "found":
                if self._update_progress("First RAR is ready.", STATUS_READY, self.rar_filepath): return

            if self._update_progress("Starting " + `self.num_threads` + " download threads", STATUS_INITIALIZING): return

            # queue first segment of each file to get each file's total size
            #for nzb_file in nzb_files:
                # skip non-PAR2 files if par2 validated it
                #if nzb_file.finished: continue
                #self.download_queue.put([nzb_file.filename, nzb_file, nzb_file.segments[0]], timeout=1)

            # queue the rest of the segments in order
            for nzb_file in nzb_files:

                # skip non-PAR2 files if par2 validated it
                if nzb_file.finished: continue

                if self._update_progress("Queueing file", STATUS_INITIALIZING, nzb_file.filename): return
                for nzb_segment in nzb_file.segments[0:]:
                    self.download_queue.put([nzb_file.filename, nzb_file, nzb_segment], timeout=1)

            # start download threads
            for i in range(self.num_threads):
                thread = threading.Thread(name=`i`, target=self._download_thread)
                thread.start()
                self.threads.append(thread)

            if self._update_progress("Starting " + `self.num_threads` + " download threads", STATUS_INITIALIZING): return

            # decode parts as they are downloaded
            # begins streaming when the first RAR is finished
            self._decode_loop()

            # if no RAR file was found, try the nested NZBs that were downloaded
            if self.rar_filepath == "":
                if self._update_progress("No RAR files found.", STATUS_INITIALIZING): return
                for nested_nzb_filepath in nested_nzbs:
                    if self._update_progress("Trying nested NZB: " + os.path.basename(nested_nzb_filepath), STATUS_INITIALIZING): return
                    nzb_stream(self.server, self.port, self.username, self.password,
                               self.num_threads,
                               urllib.pathname2url(nested_nzb_filepath),
                               nzb_directory,
                               par2exe_directory,
                               "",
                               self.progress_update)

        except:
            traceback.print_exc()
            self._update_progress("Error parsing NZB", STATUS_FAILED, self.common_prefix)

        # cancel all threads before returning
        self.cancelled = True
        for thread in self.threads:
            if thread.isAlive():
                print "Cancelled thread " + thread.getName()
                thread.join()