def start_uploading(settings):
    """
    start uploading process based on "upload_scheduler.txt"

    :return: None
    """
    today = datetime.date.today()
    with open("upload_scheduler.txt", "r") as f:
        lines = [l for l in f if l.strip()]

        for i, lin in enumerate(lines):
            path, time, status = lin.split()
            program = os.path.basename(os.path.dirname(path))
            target_date = datetime.datetime.strptime(time, "%Y-%m-%d").date()

            if status == "pending" and target_date == today:
                if program not in args.observing_programs:
                    Message.addMessage(
                        "skipping uploading program: {} ({})".format(
                            program, os.path.basename(path)),
                        dump="header")
                    continue

                Message.clearMessage("program")
                Message.clearMessage("session")
                Message.clearMessage("download")
                Message.clearMessage("log")

                upload = settings[program].get("upload", "no").lower()
                if upload == "ivs":
                    code = os.path.basename(os.path.dirname(path))
                    Transfer.upload(path)
                    emails = Helper.read_emails(settings[program],
                                                args.fallback_email)
                    SendMail.writeMail_upload(code, emails)
                elif upload == "no":
                    pass
                elif upload == "gow":
                    code = os.path.basename(os.path.dirname(path))
                    Transfer.upload_GOW_ftp(path)
                    emails = Helper.read_emails(settings[program],
                                                args.fallback_email)
                    SendMail.writeMail_upload(code, emails)
                else:
                    emails = upload.split(",")
                    with open(os.path.join(path, "selected", "email.txt"),
                              "r") as f:
                        body = f.read()
                    SendMail.writeMail(os.path.join(path, "selected"), emails,
                                       body)

                lines[i] = lin.replace("pending", "uploaded")

    with open("upload_scheduler.txt", "w") as f:
        for lout in lines:
            path, time, status = lout.split()
            target_date = datetime.datetime.strptime(time, "%Y-%m-%d").date()
            # do not list sessions older than 1 year in upload_scheduler.txt
            if target_date + datetime.timedelta(days=365) > today:
                f.write(lout)
def start(master,
          path_scheduler,
          code,
          code_regex,
          select_best,
          emails,
          delta_days,
          delta_days_upload,
          statistic_field,
          output_path="./Schedules/",
          upload=False,
          pre_fun=None,
          post_fun=None):
    """
    start auto processing for one observing program

    :param master: list of dictionaries with session specific fields read from session master
    :param path_scheduler: path to VieSched++ executable
    :param code: observing program code
    :param code_regex: regular expression to match session name
    :param select_best: function to select best schedule from statistics dataframe
    :param emails: list of email addresses
    :param statistic_field: fields to be stored in statistics file
    :param delta_days: time offset in days from where schedule should be generated
    :param delta_days_upload: time offset in days when schedule should be updated
    :param output_path: prefix for output path
    :param upload: flag if session needs to be uploaded
    :param pre_fun: list of functions executed prior to scheduling
    :param post_fun: list of functions executed after to scheduling
    :return: None
    """

    Message.clearMessage("program")
    pattern = re.compile(code_regex)

    Message.addMessage("=== {} observing program ===".format(code),
                       dump="program")
    Message.addMessage("contact:", dump="program")
    for email in emails:
        Message.addMessage("    {}".format(email), dump="program")

    Message.addMessage("schedule master contained {} sessions".format(
        len(master)),
                       dump="program")
    today = datetime.date.today()
    sessions = []
    if delta_days == "next":
        for s in master:
            if s["date"].date() < today:
                continue
            if pattern.match(s["name"]):
                sessions.append(s)
                break
        upload = False
    else:
        target_day = today + datetime.timedelta(days=delta_days)
        Message.addMessage("date offset: {} days".format(delta_days),
                           dump="program")
        Message.addMessage(
            "target start time: {:%B %d, %Y}".format(target_day),
            dump="program")
        sessions = [
            s for s in master if pattern.match(s["name"])
            if s["date"].date() == target_day
        ]
    Message.addMessage("{} session(s) will be processed".format(len(sessions)),
                       dump="program")

    # get list of templates
    templates = []
    template_path = os.path.join("Templates", code)
    for file in os.listdir(template_path):
        if file.endswith(".xml"):
            templates.append(os.path.join(template_path, file))

    # loop over all sessions
    for session in sessions:
        Message.clearMessage("session")
        Message.clearMessage("log")
        Message.addMessage("##### {} #####".format(session["code"].upper()))
        Message.addMessage(
            "{name} ({code}) start {date} duration {duration}h stations {stations}"
            .format(**session))
        xmls = adjust_template(output_path, session, templates, pre_fun)
        xml_dir = os.path.dirname(xmls[0])
        df_list = []

        flag_VLBA = any(
            ["VLBA" in sta or "PIETOWN" in sta for sta in session["stations"]])
        flag_DSS = any([sta.startswith("DSS") for sta in session["stations"]])
        if flag_VLBA or flag_DSS:
            post_fun.append(post_scheduling_functions._vex_in_sked_format)
        if flag_VLBA:
            post_fun.append(post_scheduling_functions._vlba_vex_adjustments)

        # loop over all templates
        for xml in xmls:
            Message.addMessage("   processing file: {}".format(xml))
            xml = os.path.abspath(xml)
            p = subprocess.run([path_scheduler, xml],
                               cwd=xml_dir,
                               capture_output=True,
                               text=True)
            log = p.stdout
            if log:
                Message.addMessage(log, dump="log")
            errlog = p.stderr
            if errlog:
                Message.addMessage(errlog, dump="log")
            p.check_returncode()

            # rename statistics.csv and simulation_summary file to avoid name clashes
            statistic_in = os.path.join(xml_dir, "statistics.csv")
            statistic_out = "statistics_{}.csv".format(
                os.path.basename(os.path.splitext(xml)[0]))
            statistic_out = os.path.join(xml_dir, statistic_out)
            if os.path.exists(statistic_out):
                os.remove(statistic_out)
            os.rename(statistic_in, statistic_out)

            simulation_summary_in = os.path.join(xml_dir,
                                                 "simulation_summary.txt")
            simulation_summary_out = "simulation_summary_{}.txt".format(
                os.path.basename(os.path.splitext(xml)[0]))
            simulation_summary_out = os.path.join(xml_dir,
                                                  simulation_summary_out)
            if os.path.exists(simulation_summary_out):
                os.remove(simulation_summary_out)
            os.rename(simulation_summary_in, simulation_summary_out)

            # read statistics.csv file
            df = pd.read_csv(statistic_out, index_col=0)
            df_list.append(df)

        # concatenate all statistics.csv files
        stats = pd.concat(df_list)
        stats = stats.drop_duplicates()
        stats.sort_index(inplace=True)

        # find best schedule based on statistics
        best_idx = select_best(stats, template_path=template_path)
        Message.addMessage("best version: v{:03d}".format(best_idx))
        if upload:
            Message.addMessage(
                "this session will be uploaded on: {:%B %d, %Y}".format(
                    today +
                    datetime.timedelta(days=delta_days - delta_days_upload)))
            if delta_days - delta_days_upload < 1:
                Message.addMessage("[WARNING]: upload date already passed!")
        else:
            Message.addMessage("this session will NOT be uploaded!")

        summary_file = os.path.join(os.path.dirname(xml_dir), "summary.txt")
        summary_df = Helper.addStatistics(stats, best_idx, statistic_field,
                                          session["code"].upper(),
                                          summary_file)

        # copy best schedule to selected folder
        version_pattern = "_v{:03d}".format(best_idx)
        bestFiles = glob.glob(
            os.path.join(xml_dir, "*{}*".format(version_pattern)))
        xml_dir_selected = os.path.join(xml_dir, "selected")

        if os.path.exists(xml_dir_selected):
            shutil.rmtree(xml_dir_selected)

        os.makedirs(xml_dir_selected)
        for f in bestFiles:
            fname = os.path.basename(f).replace(version_pattern, "")
            destination = os.path.join(xml_dir_selected, fname)
            shutil.copy(f, destination)
        stats.to_csv(os.path.join(xml_dir_selected, "merged_statistics.csv"))

        if upload:
            Helper.update_uploadScheduler(xml_dir_selected,
                                          delta_days - delta_days_upload,
                                          upload)

        try:
            skdFile = os.path.join(xml_dir_selected,
                                   "{}.skd".format(session["code"].lower()))
            skd = skd_parser.skdParser(skdFile)
            skd.parse()
            Plotting.summary(summary_df, xml_dir_selected)
            Plotting.polar_plots(skd, xml_dir_selected, "duration")
            Plotting.polar_plots(skd, xml_dir_selected, "start_time")
            Plotting.close_all()
        except:
            Message.addMessage("#### ERROR ####")
            Message.addMessage(traceback.format_exc())

        for post_f in post_fun:
            post_f(path=xml_dir_selected,
                   ds=stats.loc[best_idx, :],
                   session=session,
                   program_code=code)

        SendMail.writeMail(xml_dir_selected, emails)