Exemple #1
0
def setup_new_class():
    """Setup a new class based on a zyBooks class code"""
    window = ui.get_window()
    zy_api = Zybooks()

    text_input = ui.layers.TextInputLayer("Class Code")
    text_input.set_prompt(["Enter class code"])
    window.run_layer(text_input)
    if text_input.canceled:
        return

    # Check if class code is valid
    code = text_input.get_text()
    valid = zy_api.check_valid_class(code)
    if valid:
        popup = ui.layers.Popup("Valid", [f"{code} is valid"])
        window.run_layer(popup)
    else:
        popup = ui.layers.Popup("Invalid", [f"{code} is invalid"])
        window.run_layer(popup)
        return

    # If code is valid, add it to the shared configuration
    SharedData.add_class(code)

    # Download the list of students
    roster = zy_api.get_roster()

    save_roster(roster)
    popup = ui.layers.Popup("Finished",
                            ["Successfully downloaded student roster"])
    window.run_layer(popup)
    class_section_manager()
Exemple #2
0
def handle_args(args):
    if args.set_data_dir:
        if preferences.set_data_directory(args.set_data_dir):
            print(
                f"Successfully set the shared data dir to {args.set_data_dir}")
            time.sleep(1)
        else:
            print(f'Error: The path "{args.set_data_dir}" does not exist.')
            sys.exit()

    if args.init_data_dir:
        if not os.path.exists(args.init_data_dir):
            print(f"Error: the path {args.init_data_dir} does not exist.")
            sys.exit()

        shared_data_dir = os.path.join(args.init_data_dir, "zygrader_data")

        print(f"Warning: You are about to create the following directory:")
        print(f"{shared_data_dir}")
        print(f"Any existing data will be overwritten.")
        print(f"Type 'Y' to confirm, anything else to cancel: ", end="")

        confirm = input()
        if confirm not in {"Y", "y", "yes", "Yes"}:
            print("Canceled")
            sys.exit()

        SharedData.create_shared_data_directory(shared_data_dir)
        preferences.set_data_directory(shared_data_dir)

        print(f"Successfully created the shared data dir at {shared_data_dir}")
        print()
        print(f"Instruct users to run zygrader with the following flag:")
        print(f"zygrader --set-data-dir {shared_data_dir}")
        sys.exit()
Exemple #3
0
def watch_students(student_list, students, lab, use_locks):
    """Register paths when the filtered list is created"""
    paths = [
        SharedData.get_locks_directory(),
        SharedData.get_flags_directory()
    ]
    data.fs_watch.fs_watch_register(paths, "student_list_watch",
                                    fill_student_list, student_list, students,
                                    lab, use_locks, student_select_fn)
Exemple #4
0
    def get_submission_zip(self, url):
        """Download the submission at the given URL, or from a local cache if available

        While this is technically accessing files at Amazon's servers, it is coupled closely
        enough to zyBooks to include it in this file, rather than making a new file for just
        this feature.

        The cache is stored in the zygrader_data/SEMESTER_FOLDER/.cache/ directory

        Returns a ZipFile
        """
        # Check if the zip file is already cached. Only use the basename of the url
        cached_name = os.path.join(SharedData.get_cache_directory(),
                                   os.path.basename(url))
        if os.path.exists(cached_name):
            return zipfile.ZipFile(cached_name)

        # If not cached, download
        zip_response = requests.get(url)
        if not zip_response.ok:
            return Zybooks.ERROR

        # Write zip to cache
        with open(cached_name, "wb") as _file:
            _file.write(zip_response.content)
        return zipfile.ZipFile(cached_name)
Exemple #5
0
def remove_lock_file(_file):
    """Remove a specific lock file (not logged to locks_log.csv)"""
    locks_directory = SharedData.get_locks_directory()

    os.remove(os.path.join(locks_directory, _file))

    logger.log("lock file was removed manually", _file, logger.WARNING)
Exemple #6
0
def get_flag_file_path(student: Student, lab: Lab):
    """Return path for a unique flag file given a student and lab"""

    lab_name = lab.get_unique_name()
    student_name = student.get_unique_name()

    flag_path = f"{lab_name}.{student_name}.flag"
    return os.path.join(SharedData.get_flags_directory(), flag_path)
Exemple #7
0
def start():
    """Setup before initializing curses"""

    # Handle Signals
    signal.signal(signal.SIGINT, sigint_handler)
    signal.signal(signal.SIGTSTP, sigtstp_handler)
    signal.signal(signal.SIGHUP, sighup_handler)

    # Set a short ESC key delay (curses environment variable)
    os.environ.setdefault("ESCDELAY", "25")

    args = parse_args()

    # Check for updates
    if not args.no_update and not args.install_version:
        latest_version = updater.get_latest_version()
        if latest_version != SharedData.VERSION:
            updater.update_zygrader(latest_version)
            sys.exit()

    if args.install_version:
        updater.install_version(args.install_version)
        sys.exit()

    # Setup user configuration
    preferences.initialize()

    versioning.versioning_update_preferences()

    # Handle configuration based args after config has been initialized
    handle_args(args)

    # Check for shared data dir
    data_dir = preferences.get("data_dir")
    if not data_dir:
        print("You have not set the shared data directory")
        print("Please run with the flag --set-data-dir [path]")
        sys.exit()

    # Start application and setup data folders
    if not SharedData.initialize_shared_data(data_dir):
        sys.exit()

    # Load data for the current class
    data.get_students()
    data.get_labs()

    # Change directory to the default output dir
    os.chdir(os.path.expanduser(preferences.get("output_dir")))

    name = data.netid_to_name(getpass.getuser())

    # Create a zygrader window, callback to main function
    ui.Window(main, f"zygrader {SharedData.VERSION}", name, args)

    logger.log("zygrader exited normally")
Exemple #8
0
def change_class():
    """Change the current class.

    This applies globally to all users of zygrader.
    """
    window = ui.get_window()
    class_codes = SharedData.get_class_codes()

    popup = ui.layers.ListLayer("Class Code", popup=True)
    for code in class_codes:
        popup.add_row_text(code)
    window.run_layer(popup)
    if popup.canceled:
        return

    code_index = popup.selected_index()
    SharedData.set_current_class_code(class_codes[code_index])
    popup = ui.layers.Popup("Changed Class",
                            [f"Class changed to {class_codes[code_index]}"])
    window.run_layer(popup)
Exemple #9
0
def write_labs(labs):
    SharedData.LABS = labs

    labs_json = []

    for lab in labs:
        labs_json.append(lab.to_json())

    path = SharedData.get_labs_data()
    with open(path, "w") as _file:
        json.dump(labs_json, _file, indent=2)
Exemple #10
0
def write_tas(tas):
    SharedData.TAS = tas

    tas_json = []

    for ta in tas:
        tas_json.append(ta.to_json())

    path = SharedData.get_ta_data()
    with open(path, "w") as _file:
        json.dump(tas_json, _file, indent=2)
Exemple #11
0
def write_class_sections(class_sections):
    SharedData.CLASS_SECTIONS = class_sections

    class_sections_json = []

    for class_section in class_sections:
        class_sections_json.append(class_section.to_json())

    path = SharedData.get_class_sections_data()
    with open(path, "w") as _file:
        json.dump(class_sections_json, _file, indent=2)
Exemple #12
0
def unlock_all_labs_by_grader(username: str):
    """Remove all lock files for a given grader"""
    # Look at all lock files
    for lock in get_lock_files():
        lock_parts = lock.split(".")

        # Only look at the lock files graded by the current grader
        if lock_parts[0] == username:
            os.remove(os.path.join(SharedData.get_locks_directory(), lock))

    logger.log("All locks under the current grader were removed",
               logger.WARNING)
Exemple #13
0
def load_class_sections() -> list:
    SharedData.CLASS_SECTIONS.clear()
    path = SharedData.get_class_sections_data()
    if not os.path.exists(path):
        return []

    with open(path, "r") as class_sections_file:
        class_sections_json = json.load(class_sections_file)

    for class_section in class_sections_json:
        SharedData.CLASS_SECTIONS.append(ClassSection.from_json(class_section))

    return SharedData.CLASS_SECTIONS
Exemple #14
0
def load_labs() -> list:
    SharedData.LABS.clear()
    path = SharedData.get_labs_data()
    if not os.path.exists(path):
        return []

    with open(path, "r") as labs_file:
        labs_json = json.load(labs_file)

    for a in labs_json:
        SharedData.LABS.append(Lab(a["name"], a["parts"], a["options"]))

    return SharedData.LABS
Exemple #15
0
def load_tas() -> list:
    SharedData.TAS.clear()
    path = SharedData.get_ta_data()
    if not os.path.exists(path):
        return []

    with open(path, "r") as tas_file:
        tas_json = json.load(tas_file)

    for ta in tas_json:
        SharedData.TAS.append(TA.from_json(ta))

    return SharedData.TAS
Exemple #16
0
def get_lock_file_path(student: Student, lab: Lab = None):
    """Return path for lock file"""
    username = getpass.getuser()

    # We can safely store both lab+student and lab locks in the
    # Same directory
    if lab:
        lab_name = lab.get_unique_name()
        student_name = student.get_unique_name()
        lock_path = f"{username}.{lab_name}.{student_name}.lock"
    else:
        student_name = student.get_unique_name()
        lock_path = f"{username}.{student_name}.lock"

    return os.path.join(SharedData.get_locks_directory(), lock_path)
Exemple #17
0
def load_students() -> list:
    SharedData.STUDENTS.clear()
    path = SharedData.get_student_data()
    if not os.path.exists(path):
        return []

    with open(path, "r") as students_file:
        try:
            students_json = json.load(students_file)
        except json.decoder.JSONDecodeError:
            students_json = []

    for student in students_json:
        SharedData.STUDENTS.append(
            Student(
                student["first_name"],
                student["last_name"],
                student["email"],
                student["section"],
                student["id"],
            ))

    return SharedData.STUDENTS
Exemple #18
0
def save_roster(roster):
    """Save the roster of students to a json file"""
    roster = roster["roster"]  # It is stored under "roster" in the json

    # Download students (and others)
    students = []
    for role in roster:
        for person in roster[role]:
            student = {}
            student["first_name"] = person["first_name"]
            student["last_name"] = person["last_name"]
            student["email"] = person["primary_email"]
            student["id"] = person["user_id"]

            if "class_section" in person:
                student["section"] = person["class_section"]["value"]
            else:
                student["section"] = -1

            students.append(student)

    out_path = SharedData.get_student_data()
    with open(out_path, "w") as _file:
        json.dump(students, _file, indent=2)
Exemple #19
0
def get_flag_files():
    """Return a list of all flag files"""
    return [f for f in os.listdir(SharedData.get_flags_directory())]
Exemple #20
0
def get_lock_log_path():
    """Return path to lock log file"""
    return os.path.join(SharedData.get_logs_directory(), "locks_log.csv")
Exemple #21
0
def unlock_all_labs():
    """Remove all locks"""
    for lock in get_lock_files():
        os.remove(os.path.join(SharedData.get_locks_directory(), lock))
Exemple #22
0
def get_lock_files():
    """Return a list of all lock files"""
    return [
        l for l in os.listdir(SharedData.get_locks_directory())
        if l.endswith(".lock")
    ]
Exemple #23
0
def get_global_lock_path():
    return os.path.join(SharedData.get_logs_directory(), "log.txt")
Exemple #24
0
def lock_cleanup():
    # If terminating before shared directories are initialized, the folders would be
    # created in the current directory when removing locks. See #72 for more details.
    if SharedData.is_initialized():
        data.lock.unlock_all_labs_by_grader(getpass.getuser())