Beispiel #1
0
 def end_job(self, nzo: NzbObject):
     """ Send NZO to the post-processing queue """
     # Notify assembler to call postprocessor
     if not nzo.deleted:
         logging.info("[%s] Ending job %s", caller_name(), nzo.final_name)
         nzo.deleted = True
         if nzo.precheck:
             nzo.save_to_disk()
             # Check result
             enough, _ = nzo.check_availability_ratio()
             if enough:
                 # Enough data present, do real download
                 self.send_back(nzo)
                 return
             else:
                 # Not enough data, let postprocessor show it as failed
                 pass
         sabnzbd.Assembler.process(nzo)
Beispiel #2
0
def process_job(nzo: NzbObject):
    """ Process one job """
    start = time.time()

    # keep track of whether we can continue
    all_ok = True
    # keep track of par problems
    par_error = False
    # keep track of any unpacking errors
    unpack_error = False
    # Signal empty download, for when 'empty_postproc' is enabled
    empty = False
    nzb_list = []
    # These need to be initialized in case of a crash
    workdir_complete = ""
    script_log = ""
    script_line = ""

    # Get the job flags
    nzo.save_attribs()
    flag_repair, flag_unpack, flag_delete = nzo.repair_opts
    # Normalize PP
    if flag_delete:
        flag_unpack = True
    if flag_unpack:
        flag_repair = True

    # Get the NZB name
    filename = nzo.final_name

    # Download-processes can mark job as failed, skip all steps
    if nzo.fail_msg:
        all_ok = False
        par_error = True
        unpack_error = 1

    try:
        # Get the folder containing the download result
        workdir = nzo.download_path
        tmp_workdir_complete = None

        # if no files are present (except __admin__), fail the job
        if all_ok and len(globber(workdir)) < 2:
            if nzo.precheck:
                _, ratio = nzo.check_availability_ratio()
                emsg = T(
                    "Download might fail, only %s of required %s available"
                ) % (ratio, cfg.req_completion_rate())
            else:
                emsg = T("Download failed - Not on your server(s)")
                empty = True
            emsg += " - https://sabnzbd.org/not-complete"
            nzo.fail_msg = emsg
            nzo.set_unpack_info("Download", emsg)
            nzo.status = Status.FAILED
            # do not run unpacking or parity verification
            flag_repair = flag_unpack = False
            all_ok = cfg.empty_postproc() and empty
            if not all_ok:
                par_error = True
                unpack_error = 1

        script = nzo.script
        logging.info(
            "Starting Post-Processing on %s => Repair:%s, Unpack:%s, Delete:%s, Script:%s, Cat:%s",
            filename,
            flag_repair,
            flag_unpack,
            flag_delete,
            script,
            nzo.cat,
        )

        # Set complete dir to workdir in case we need to abort
        workdir_complete = workdir

        # Send post-processing notification
        notifier.send_notification(T("Post-processing"), nzo.final_name, "pp",
                                   nzo.cat)

        # Par processing, if enabled
        if all_ok and flag_repair:
            par_error, re_add = parring(nzo, workdir)
            if re_add:
                # Try to get more par files
                return False

        # If we don't need extra par2, we can disconnect
        if sabnzbd.NzbQueue.actives(grabs=False) == 0 and cfg.autodisconnect():
            # This was the last job, close server connections
            sabnzbd.Downloader.disconnect()

        # Sanitize the resulting files
        if sabnzbd.WIN32:
            sanitize_files_in_folder(workdir)

        # Check if user allows unsafe post-processing
        if flag_repair and cfg.safe_postproc():
            all_ok = all_ok and not par_error

        if all_ok:
            # Fix encodings
            fix_unix_encoding(workdir)

            # Use dirs generated by direct-unpacker
            if nzo.direct_unpacker and nzo.direct_unpacker.unpack_dir_info:
                (
                    tmp_workdir_complete,
                    workdir_complete,
                    file_sorter,
                    one_folder,
                    marker_file,
                ) = nzo.direct_unpacker.unpack_dir_info
            else:
                # Generate extraction path
                tmp_workdir_complete, workdir_complete, file_sorter, one_folder, marker_file = prepare_extraction_path(
                    nzo)

            newfiles = []
            # Run Stage 2: Unpack
            if flag_unpack:
                # Set the current nzo status to "Extracting...". Used in History
                nzo.status = Status.EXTRACTING
                logging.info("Running unpack_magic on %s", filename)
                unpack_error, newfiles = unpack_magic(nzo, workdir,
                                                      tmp_workdir_complete,
                                                      flag_delete, one_folder,
                                                      (), (), (), (), ())
                logging.info("Unpacked files %s", newfiles)

                if sabnzbd.WIN32:
                    # Sanitize the resulting files
                    newfiles = sanitize_files_in_folder(tmp_workdir_complete)
                logging.info("Finished unpack_magic on %s", filename)

            if cfg.safe_postproc():
                all_ok = all_ok and not unpack_error

            if all_ok:
                # Move any (left-over) files to destination
                nzo.status = Status.MOVING
                nzo.set_action_line(T("Moving"), "...")
                for root, _dirs, files in os.walk(workdir):
                    if not root.endswith(JOB_ADMIN):
                        for file_ in files:
                            path = os.path.join(root, file_)
                            new_path = path.replace(workdir,
                                                    tmp_workdir_complete)
                            ok, new_path = move_to_path(path, new_path)
                            if new_path:
                                newfiles.append(new_path)
                            if not ok:
                                nzo.set_unpack_info(
                                    "Unpack",
                                    T("Failed moving %s to %s") %
                                    (path, new_path))
                                all_ok = False
                                break

            # Set permissions right
            set_permissions(tmp_workdir_complete)

            if all_ok and marker_file:
                del_marker(os.path.join(tmp_workdir_complete, marker_file))
                remove_from_list(marker_file, newfiles)

            if all_ok:
                # Remove files matching the cleanup list
                cleanup_list(tmp_workdir_complete, skip_nzb=True)

                # Check if this is an NZB-only download, if so redirect to queue
                # except when PP was Download-only
                if flag_repair:
                    nzb_list = nzb_redirect(tmp_workdir_complete,
                                            nzo.final_name, nzo.pp, script,
                                            nzo.cat, nzo.priority)
                else:
                    nzb_list = None
                if nzb_list:
                    nzo.set_unpack_info("Download",
                                        T("Sent %s to queue") % nzb_list)
                    cleanup_empty_directories(tmp_workdir_complete)
                else:
                    # Full cleanup including nzb's
                    cleanup_list(tmp_workdir_complete, skip_nzb=False)

        script_output = ""
        script_ret = 0
        if not nzb_list:
            # Give destination its final name
            if cfg.folder_rename() and tmp_workdir_complete and not one_folder:
                if not all_ok:
                    # Rename failed folders so they are easy to recognize
                    workdir_complete = tmp_workdir_complete.replace(
                        "_UNPACK_", "_FAILED_")
                    workdir_complete = get_unique_path(workdir_complete,
                                                       create_dir=False)

                try:
                    newfiles = rename_and_collapse_folder(
                        tmp_workdir_complete, workdir_complete, newfiles)
                except:
                    logging.error(
                        T('Error renaming "%s" to "%s"'),
                        clip_path(tmp_workdir_complete),
                        clip_path(workdir_complete),
                    )
                    logging.info("Traceback: ", exc_info=True)
                    # Better disable sorting because filenames are all off now
                    file_sorter.sort_file = None

            if empty:
                job_result = -1
            else:
                job_result = int(par_error) + int(bool(unpack_error)) * 2

            if cfg.ignore_samples():
                remove_samples(workdir_complete)

            # TV/Movie/Date Renaming code part 2 - rename and move files to parent folder
            if all_ok and file_sorter.sort_file:
                if newfiles:
                    file_sorter.rename(newfiles, workdir_complete)
                    workdir_complete, ok = file_sorter.move(workdir_complete)
                else:
                    workdir_complete, ok = file_sorter.rename_with_ext(
                        workdir_complete)
                if not ok:
                    nzo.set_unpack_info("Unpack", T("Failed to move files"))
                    all_ok = False

            if cfg.deobfuscate_final_filenames() and all_ok and not nzb_list:
                # Deobfuscate the filenames
                logging.info("Running deobfuscate")
                deobfuscate.deobfuscate_list(newfiles, nzo.final_name)

            # Run the user script
            script_path = make_script_path(script)
            if (all_ok or not cfg.safe_postproc()) and (
                    not nzb_list) and script_path:
                # Set the current nzo status to "Ext Script...". Used in History
                nzo.status = Status.RUNNING
                nzo.set_action_line(T("Running script"), script)
                nzo.set_unpack_info("Script",
                                    T("Running user script %s") % script,
                                    unique=True)
                script_log, script_ret = external_processing(
                    script_path, nzo, clip_path(workdir_complete),
                    nzo.final_name, job_result)
                script_line = get_last_line(script_log)
                if script_log:
                    script_output = nzo.nzo_id
                if script_line:
                    nzo.set_unpack_info("Script", script_line, unique=True)
                else:
                    nzo.set_unpack_info("Script",
                                        T("Ran %s") % script,
                                        unique=True)
            else:
                script = ""
                script_line = ""
                script_ret = 0

        # Maybe bad script result should fail job
        if script_ret and cfg.script_can_fail():
            script_error = True
            all_ok = False
            nzo.fail_msg = T("Script exit code is %s") % script_ret
        else:
            script_error = False

        # Email the results
        if (not nzb_list) and cfg.email_endjob():
            if (cfg.email_endjob()
                    == 1) or (cfg.email_endjob() == 2 and
                              (unpack_error or par_error or script_error)):
                emailer.endjob(
                    nzo.final_name,
                    nzo.cat,
                    all_ok,
                    workdir_complete,
                    nzo.bytes_downloaded,
                    nzo.fail_msg,
                    nzo.unpack_info,
                    script,
                    script_log,
                    script_ret,
                )

        if script_output:
            # Can do this only now, otherwise it would show up in the email
            if script_ret:
                script_ret = "Exit(%s) " % script_ret
            else:
                script_ret = ""
            if len(script_log.rstrip().split("\n")) > 1:
                nzo.set_unpack_info(
                    "Script",
                    '%s%s <a href="./scriptlog?name=%s">(%s)</a>' %
                    (script_ret, script_line, encoding.xml_name(script_output),
                     T("More")),
                    unique=True,
                )
            else:
                # No '(more)' button needed
                nzo.set_unpack_info("Script",
                                    "%s%s " % (script_ret, script_line),
                                    unique=True)

        # Cleanup again, including NZB files
        if all_ok:
            cleanup_list(workdir_complete, False)

        # Force error for empty result
        all_ok = all_ok and not empty

        # Update indexer with results
        if cfg.rating_enable():
            if nzo.encrypted > 0:
                sabnzbd.Rating.update_auto_flag(nzo.nzo_id,
                                                sabnzbd.Rating.FLAG_ENCRYPTED)
            if empty:
                hosts = [s.host for s in sabnzbd.Downloader.nzo_servers(nzo)]
                if not hosts:
                    hosts = [None]
                for host in hosts:
                    sabnzbd.Rating.update_auto_flag(
                        nzo.nzo_id, sabnzbd.Rating.FLAG_EXPIRED, host)

    except:
        logging.error(T("Post Processing Failed for %s (%s)"), filename,
                      T("see logfile"))
        logging.info("Traceback: ", exc_info=True)

        nzo.fail_msg = T("Post-processing was aborted")
        notifier.send_notification(T("Download Failed"), filename, "failed",
                                   nzo.cat)
        nzo.status = Status.FAILED
        par_error = True
        all_ok = False

        if cfg.email_endjob():
            emailer.endjob(
                nzo.final_name,
                nzo.cat,
                all_ok,
                clip_path(workdir_complete),
                nzo.bytes_downloaded,
                nzo.fail_msg,
                nzo.unpack_info,
                "",
                "",
                0,
            )

    if all_ok:
        # If the folder only contains one file OR folder, have that as the path
        # Be aware that series/generic/date sorting may move a single file into a folder containing other files
        workdir_complete = one_file_or_folder(workdir_complete)
        workdir_complete = os.path.normpath(workdir_complete)

    # Clean up the NZO data
    try:
        nzo.purge_data(delete_all_data=all_ok)
    except:
        logging.error(T("Cleanup of %s failed."), nzo.final_name)
        logging.info("Traceback: ", exc_info=True)

    # Use automatic retry link on par2 errors and encrypted/bad RARs
    if par_error or unpack_error in (2, 3):
        try_alt_nzb(nzo)

    # Check if it was aborted
    if not nzo.pp_active:
        nzo.fail_msg = T("Post-processing was aborted")
        all_ok = False

    # Show final status in history
    if all_ok:
        notifier.send_notification(T("Download Completed"), filename,
                                   "complete", nzo.cat)
        nzo.status = Status.COMPLETED
    else:
        notifier.send_notification(T("Download Failed"), filename, "failed",
                                   nzo.cat)
        nzo.status = Status.FAILED

    # Log the overall time taken for postprocessing
    postproc_time = int(time.time() - start)

    with database.HistoryDB() as history_db:
        # Add the nzo to the database. Only the path, script and time taken is passed
        # Other information is obtained from the nzo
        history_db.add_history_db(nzo, workdir_complete, postproc_time,
                                  script_log, script_line)
        # Purge items
        history_db.auto_history_purge()

    sabnzbd.history_updated()
    return True