def get_readable_tasks(courseid): """ Returns the list of all available tasks in a course """ tasks = [ task for task in os.listdir(os.path.join(get_tasks_directory(), courseid)) if os.path.isdir(os.path.join(get_tasks_directory(), courseid, task)) and _task_file_exists(os.path.join(get_tasks_directory(), courseid, task))] return tasks
def _synchronize_task_dir_p2(self, agent, local_td, generated_files, async_value_remote_td): """ Synchronizes the task directory with the remote agent, part 2 """ try: remote_td = copy.deepcopy(async_value_remote_td.value) except: print "An error occured while retrieving list of files in the task dir from remote agent" return if remote_td is None: # sync disabled for this Agent return to_update, to_delete = directory_compare_from_hash(local_td, remote_td) tmpfile = tempfile.TemporaryFile() tar = tarfile.open(fileobj=tmpfile, mode='w:gz') for path in to_update: # be a little safe about what the agent returns... if os.path.relpath(os.path.join(get_tasks_directory(), path), get_tasks_directory()) == path and ".." not in path: if path in generated_files: # the file do not really exists on disk, it was generated info = tarfile.TarInfo(name=path) info.size = generated_files[path].len info.mode = 0o777 tar.addfile(tarinfo=info, fileobj=generated_files[path]) else: # the file really exists on disk tar.add(arcname=path, name=os.path.join(get_tasks_directory(), path)) else: print "Agent returned non-safe file path: " + path tar.close() tmpfile.flush() tmpfile.seek(0) # sync the agent async_update = rpyc.async(agent.root.update_task_directory) # do not forget to close the file async_update(tmpfile, to_delete).add_callback(lambda r: tmpfile.close())
def get_all_courses(cls): """Returns a table containing courseid=>Course pairs.""" files = [os.path.splitext(f)[0] for f in os.listdir(get_tasks_directory()) if os.path.isfile(os.path.join(get_tasks_directory(), f, "course.yaml")) or os.path.isfile(os.path.join(get_tasks_directory(), f, "course.json"))] output = {} for course in files: try: output[course] = cls(course) except Exception as e: # todo log the error print e return output
def GET(self, courseid, taskid, path): """ GET request """ if User.is_logged_in(): try: course = FrontendCourse(courseid) if not course.is_open_to_user(User.get_username(), course.is_group_course()): return renderer.course_unavailable() task = course.get_task(taskid) if not task.is_visible_by_user(User.get_username()): return renderer.task_unavailable() path_norm = posixpath.normpath(urllib.unquote(path)) public_folder_path = os.path.normpath(os.path.realpath(os.path.join(get_tasks_directory(), courseid, taskid, "public"))) file_path = os.path.normpath(os.path.realpath(os.path.join(public_folder_path, path_norm))) # Verify that we are still inside the public directory if os.path.normpath(os.path.commonprefix([public_folder_path, file_path])) != public_folder_path: raise web.notfound() if os.path.isfile(file_path): mimetypes.init() mime_type = mimetypes.guess_type(file_path) web.header('Content-Type', mime_type[0]) with open(file_path) as static_file: return static_file.read() else: raise web.notfound() except: if web.config.debug: raise else: raise web.notfound() else: return renderer.index(False)
def start(self): # init the synchronization of task directories self._last_content_in_task_directory = directory_content_with_hash(get_tasks_directory()) threading.Timer((30 if not self._is_testing else 2), self._try_synchronize_task_dir).start() # connect to agents self._try_agent_connection()
def delete_all_possible_task_files(courseid, taskid): """ Deletes all possibles task files in directory, to allow to change the format """ for ext in get_available_task_file_managers().keys(): try: os.remove(os.path.join(get_tasks_directory(), courseid, taskid, "task.{}".format(ext))) except: pass
def test_init(self): """ Test the initialisation of the common lib """ init_common_lib(os.path.join(os.path.dirname(__file__), 'tasks'), [".c"], 1024 * 1024) assert os.path.abspath(get_tasks_directory()) == os.path.abspath(os.path.join(os.path.dirname(__file__), 'tasks')) assert get_allowed_file_extensions() == [".c"] assert get_max_file_size() == 1024 * 1024
def _get_course_descriptor_path(cls, courseid): """Returns the path to the file that describes the course 'courseid'""" if not id_checker(courseid): raise Exception("Course with invalid name: " + courseid) base_file = os.path.join(get_tasks_directory(), courseid, "course") if os.path.isfile(base_file + ".yaml"): return base_file + ".yaml" else: return base_file + ".json"
def _try_synchronize_task_dir(self): """ Check if the remote tasks dirs (on the remote agents) should be updated """ if self._closed: return current_content_in_task_directory = directory_content_with_hash(get_tasks_directory()) changed, deleted = directory_compare_from_hash(current_content_in_task_directory, self._last_content_in_task_directory) if len(changed) != 0 or len(deleted) != 0: self._last_content_in_task_directory = current_content_in_task_directory for agent in self._agents: if agent is not None: self._synchronize_task_dir(agent) if not self._is_testing: threading.Timer(30, self._try_synchronize_task_dir).start()
def action_delete(self, courseid, taskid, path): """ Delete a file or a directory """ wanted_path = self.verify_path(courseid, taskid, path) if wanted_path is None: return self.show_tab_file(courseid, taskid, "Internal error") # special case: cannot delete current directory of the task if "." == os.path.relpath(wanted_path, os.path.join(get_tasks_directory(), courseid, taskid)): return self.show_tab_file(courseid, taskid, "Internal error") if os.path.isdir(wanted_path): shutil.rmtree(wanted_path) else: os.unlink(wanted_path) return self.show_tab_file(courseid, taskid)
def get_task_filelist(cls, courseid, taskid): """ Returns a flattened version of all the files inside the task directory, excluding the files task.* and hidden files. It returns a list of tuples, of the type (Integer Level, Boolean IsDirectory, String Name, String CompleteName) """ path = os.path.join(get_tasks_directory(), courseid, taskid) if not os.path.exists(path): return [] result_dict = {} for root, _, files in os.walk(path): rel_root = os.path.normpath(os.path.relpath(root, path)) insert_dict = result_dict if rel_root != ".": hidden_dir = False for i in rel_root.split(os.path.sep): if i.startswith("."): hidden_dir = True break if i not in insert_dict: insert_dict[i] = {} insert_dict = insert_dict[i] if hidden_dir: continue for f in files: # Do not follow symlinks and do not take into account task describers if not os.path.islink( os.path.join( root, f)) and not ( root == path and os.path.splitext(f)[0] == "task" and os.path.splitext(f)[1][ 1:] in get_available_task_file_managers().keys()) and not f.startswith( "."): insert_dict[f] = None def recur_print(current, level, current_name): iteritems = sorted(current.iteritems()) # First, the files recur_print.flattened += [(level, False, f, os.path.join(current_name, f)) for f, t in iteritems if t is None] # Then, the dirs for name, sub in iteritems: if sub is not None: recur_print.flattened.append((level, True, name, os.path.join(current_name, name))) recur_print(sub, level + 1, os.path.join(current_name, name)) recur_print.flattened = [] recur_print(result_dict, 0, '') return recur_print.flattened
def verify_path(self, courseid, taskid, path, new_path=False): """ Return the real wanted path (relative to the INGInious root) or None if the path is not valid/allowed """ task_dir_path = os.path.join(get_tasks_directory(), courseid, taskid) # verify that the dir exists if not os.path.exists(task_dir_path): return None wanted_path = os.path.normpath(os.path.join(task_dir_path, path)) rel_wanted_path = os.path.relpath(wanted_path, task_dir_path) # normalized # verify that the path we want exists and is withing the directory we want if (new_path == os.path.exists(wanted_path)) or os.path.islink(wanted_path) or rel_wanted_path.startswith('..'): return None # do not allow touching the task.* file if os.path.splitext(rel_wanted_path)[0] == "task" and os.path.splitext(rel_wanted_path)[1][1:] in get_available_task_file_managers().keys(): return None # do not allow hidden dir/files if rel_wanted_path != ".": for i in rel_wanted_path.split(os.path.sep): if i.startswith("."): return None return wanted_path
def action_upload(self, courseid, taskid, path, fileobj): """ Upload a file """ wanted_path = self.verify_path(courseid, taskid, path, True) if wanted_path is None: return self.show_tab_file(courseid, taskid, "Invalid new path") curpath = os.path.join(get_tasks_directory(), courseid, taskid) rel_path = os.path.relpath(wanted_path, curpath) for i in rel_path.split(os.path.sep)[:-1]: curpath = os.path.join(curpath, i) if not os.path.exists(curpath): os.mkdir(curpath) if not os.path.isdir(curpath): return self.show_tab_file(courseid, taskid, i + " is not a directory!") try: open(wanted_path, "w").write(fileobj.file.read()) return self.show_tab_file(courseid, taskid) except: return self.show_tab_file(courseid, taskid, "An error occurred while writing the file")
def action_create(self, courseid, taskid, path): """ Delete a file or a directory """ want_directory = path.strip().endswith("/") wanted_path = self.verify_path(courseid, taskid, path, True) if wanted_path is None: return self.show_tab_file(courseid, taskid, "Invalid new path") curpath = os.path.join(get_tasks_directory(), courseid, taskid) rel_path = os.path.relpath(wanted_path, curpath) for i in rel_path.split(os.path.sep)[:-1]: curpath = os.path.join(curpath, i) if not os.path.exists(curpath): os.mkdir(curpath) if not os.path.isdir(curpath): return self.show_tab_file(courseid, taskid, i + " is not a directory!") if rel_path.split(os.path.sep)[-1] != "": if want_directory: os.mkdir(os.path.join(curpath, rel_path.split(os.path.sep)[-1])) else: open(os.path.join(curpath, rel_path.split(os.path.sep)[-1]), 'a') return self.show_tab_file(courseid, taskid)
def get_task_file_manager(courseid, taskid): """ Returns the appropriate task file manager for this task """ for ext, subclass in get_available_task_file_managers().iteritems(): if os.path.isfile(os.path.join(get_tasks_directory(), courseid, taskid, "task.{}".format(ext))): return subclass(courseid, taskid) return None
def read(self): """ Read the file describing the task and returns a dict """ return self._get_content(codecs.open(os.path.join(get_tasks_directory(), self._courseid, self._taskid, "task." + self.get_ext()), "r", 'utf-8').read())
def get_course_tasks_directory(self): """Return the complete path to the tasks directory of the course""" return os.path.join(get_tasks_directory(), self._id)
def write(self, data): """ Write data to the task file """ with codecs.open(os.path.join(get_tasks_directory(), self._courseid, self._taskid, "task." + self.get_ext()), "w", 'utf-8') as task_desc_file: task_desc_file.write(self._generate_content(data))
def POST(self, courseid, taskid): """ Edit a task """ if not id_checker(taskid) or not id_checker(courseid): raise Exception("Invalid course/task id") course, _ = get_course_and_check_rights(courseid, allow_all_staff=False) # Parse content try: data = web.input(task_file={}) try: task_zip = data.get("task_file").file except: task_zip = None del data["task_file"] problems = self.dict_from_prefix("problem", data) limits = self.dict_from_prefix("limits", data) data = {key: val for key, val in data.iteritems() if not key.startswith("problem") and not key.startswith("limits")} del data["@action"] try: file_manager = get_available_task_file_managers()[data["@filetype"]](courseid, taskid) except Exception as inst: return json.dumps({"status": "error", "message": "Invalid file type: {}".format(str(inst))}) del data["@filetype"] if problems is None: return json.dumps({"status": "error", "message": "You cannot create a task without subproblems"}) # Order the problems (this line also deletes @order from the result) data["problems"] = OrderedDict([(key, self.parse_problem(val)) for key, val in sorted(problems.iteritems(), key=lambda x: int(x[1]['@order']))]) data["limits"] = limits if "hard_time" in data["limits"] and data["limits"]["hard_time"] == "": del data["limits"]["hard_time"] # Weight try: data["weight"] = float(data["weight"]) except: return json.dumps({"status": "error", "message": "Grade weight must be a floating-point number"}) # Accessible if data["accessible"] == "custom": data["accessible"] = "{}/{}".format(data["accessible_start"], data["accessible_end"]) elif data["accessible"] == "true": data["accessible"] = True else: data["accessible"] = False del data["accessible_start"] del data["accessible_end"] # Checkboxes if data.get("responseIsHTML"): data["responseIsHTML"] = True if data.get("contextIsHTML"): data["contextIsHTML"] = True except Exception as message: return json.dumps({"status": "error", "message": "Your browser returned an invalid form ({})".format(str(message))}) # Get the course try: course = FrontendCourse(courseid) except: return json.dumps({"status": "error", "message": "Error while reading course's informations"}) # Get original data try: orig_data = get_task_file_manager(courseid, taskid).read() data["order"] = orig_data["order"] except: pass try: FrontendTask(course, taskid, data) except Exception as message: return json.dumps({"status": "error", "message": "Invalid data: {}".format(str(message))}) if not os.path.exists(os.path.join(get_tasks_directory(), courseid, taskid)): os.mkdir(os.path.join(get_tasks_directory(), courseid, taskid)) if task_zip: try: zipfile = ZipFile(task_zip) except Exception as message: return json.dumps({"status": "error", "message": "Cannot read zip file. Files were not modified"}) try: zipfile.extractall(os.path.join(get_tasks_directory(), courseid, taskid)) except Exception as message: return json.dumps( {"status": "error", "message": "There was a problem while extracting the zip archive. Some files may have been modified"}) delete_all_possible_task_files(courseid, taskid) file_manager.write(data) return json.dumps({"status": "ok"})