def ask_for_assignment_sync(settings): choice = -1 while choice not in (1, 2): settings.print_advanced_settings(clear=True) print(ANSI.format(u"\n\nAssignments settings", u"announcer")) print( ANSI.format( u"Would you like CanvasSync to synchronize assignments?\n\n" u"The assignment description will be downloaded as a HTML to be viewed offline\n" u"and files hosted on the Canvas server that are described in the assignment\n" u"description section will be downloaded to the same folder.\n", u"white")) print(ANSI.format(u"1) Sync assignments (default)", u"bold")) print(ANSI.format(u"2) Do not sync assignments", u"bold")) try: choice = int(input(u"\nChoose number: ")) except ValueError: continue if choice == 1: return True elif choice == 2: return False else: continue
def ask_for_avoid_duplicates(settings): choice = -1 while choice not in (1, 2): settings.print_advanced_settings(clear=True) print(ANSI.format(u"\n\nVarious files settings", u"announcer")) print( ANSI.format( u"In addition to synchronizing modules and assignments,\n" u"CanvasSync will sync files located under the 'Files'\n" u"section in Canvas into a 'Various Files' folder.\n" u"Often some of the files stored under 'Files' is mentioned in\n" u"modules and assignments and may thus already exist in another\n" u"folder after running CanvasSync.\n\n" u"Do you want CanvasSync to avoid duplicates by only downloading\n" u"files into the 'Various Files' folder, if they are not already\n" u"present in one of the modules or assignments folders?\n", u"white")) print(ANSI.format(u"1) Yes, avoid duplicates (default)", u"bold")) print( ANSI.format(u"2) No, download all files to 'Various files'", u"bold")) try: choice = int(input(u"\nChoose number: ")) except ValueError: continue if choice == 1: return True elif choice == 2: return False else: continue
def ask_for_download_linked(settings): choice = -1 while choice not in (1, 2): settings.print_advanced_settings(clear=True) print(ANSI.format(u"\n\nAssignments settings", u"announcer")) print( ANSI.format( u"You have chosen to synchronise assignments. URLs detected in the\n" u"description field that point to files on Canvas will be downloaded\n" u"to the assignment folder.\n\n" u"CanvasSync may also attempt to download linked files that are NOT\n" u"hosted on the Canvas server itself. CanvasSync is looking for URLs that\n" u"end in a filename to avoid downloading other linked material such as\n" u"web-sites. However, be aware that errors could occur.\n" u"\nDo you wish to enable this feature?\n", u"white")) print( ANSI.format(u"1) Enable linked file downloading (default)", u"bold")) print(ANSI.format(u"2) Disable linked file downloading", u"bold")) try: choice = int(input(u"\nChoose number: ")) except ValueError: continue if choice == 1: return True elif choice == 2: return False else: continue
def __repr__(self): """ String representation, overwriting base class method """ status = ANSI.format(u"[SYNCED]", formatting=u"green") return status + u" " * 7 + u"| " + u"\t" * self.indent + u"%s: %s" \ % (ANSI.format(u"Assignments Folder", formatting=u"assignments"), self.name)
def ask_for_use_nicknames(settings): choice = -1 while choice not in (1, 2): settings.print_advanced_settings(clear=True) print(ANSI.format(u"\n\nCourse display name settings", u"announcer")) print( ANSI.format( u"In addition to identifying courses by their couse code,\n" u"Canvas can also identify courses using user-defined\n" u"nicknames that can be specified from within the Canvas UI.\n" u"Do you want CanvasSync to use course nicknames rather than\n" u"course codes for display and directory structure?\n", u"white")) print(ANSI.format(u"1) No, use course codes (default)", u"bold")) print( ANSI.format( u"2) Yes, use course nicknames (you will be \n" u" prompted to reselect courses)", u"bold")) try: choice = int(input(u"\nChoose number: ")) except ValueError: continue if choice == 1: return False elif choice == 2: return True else: continue
def print_settings(self, first_time_setup=True, clear=True): """ Print the settings currently in memory. Clear the console first if specified by the 'clear' parameter """ if clear: static_functions.clear_console() if first_time_setup: print( ANSI.format( u"This is a first time setup.\nYou must specify at least the following settings" u" in order to run CanvasSync:\n", u"announcer")) else: print(ANSI.format(u"-----------------------------", u"file")) print(ANSI.format(u"CanvasSync - Current settings", u"file")) print(ANSI.format(u"-----------------------------\n", u"file")) print(ANSI.format(u"Standard settings", u"announcer")) print(ANSI.BOLD + u"[*] Sync path: \t" + ANSI.ENDC + ANSI.BLUE + self.sync_path + ANSI.ENDC) print(ANSI.BOLD + u"[*] Canvas domain: \t" + ANSI.ENDC + ANSI.BLUE + self.domain + ANSI.ENDC) print(ANSI.BOLD + u"[*] Authentication token: \t" + ANSI.ENDC + ANSI.BLUE + self.token + ANSI.ENDC) if len(self.courses_to_sync) != 0: if self.courses_to_sync[0] == u"Not set": d = u"" else: d = u"1) " print(ANSI.BOLD + u"[*] Courses to be synced: \t%s" % d + ANSI.ENDC + ANSI.BLUE + self.courses_to_sync[0] + ANSI.ENDC) for index, course in enumerate(self.courses_to_sync[1:]): print(u" " * 27 + u"\t%s) " % (index + 2) + ANSI.BLUE + course + ANSI.ENDC)
def ask_for_advanced_settings(settings): choice = -1 while choice not in (1, 2): settings.print_settings(clear=True) print( ANSI.format( u"\n\nAll mandatory settings are set. Do you wish see advanced settings?", u"announcer")) print( ANSI.format(u"\n[1]\tShow advanced settings (recommended)", u"bold")) print(ANSI.format(u"[2]\tUse default settings", u"bold")) try: choice = int(input(u"\nChoose number: ")) except ValueError: continue if choice == 1: return True elif choice == 2: return False else: continue
def __repr__(self): """ String representation, overwriting base class method """ status = ANSI.format( u"[SYNCED]" if self.to_be_synced else u"[SKIPPED]", formatting=u"green" if self.to_be_synced else u"yellow") return status + u" " * (7 if self.to_be_synced else 6) + u"| " + u"\t" * self.indent + u"%s: %s" \ % (ANSI.format(u"Course", formatting=u"course"), self.name)
def print_status(self, status, color, overwrite_previous_line=False): """ Print status to console """ if overwrite_previous_line: # Move up one line sys.stdout.write(ANSI.format(u"", formatting=u"lineup")) sys.stdout.flush() print(ANSI.format(u"[%s]" % status, formatting=color) + str(self)[len(status) + 2:]) sys.stdout.flush()
def print_advanced_settings(self, clear=True): """ Print the advanced settings currently in memory. Clear the console first if specified by the 'clear' parameter """ if clear: static_functions.clear_console() print(ANSI.format(u"\nAdvanced settings", u"announcer")) module_settings_string = ANSI.BOLD + u"[*] Sync module items: \t" + ANSI.ENDC count = 0 for item in self.modules_settings: if self.modules_settings[item]: d = u" & " if count != 0 else u"" module_settings_string += d + ANSI.BLUE + item + ANSI.ENDC count += 1 if count == 0: module_settings_string += ANSI.RED + u"False" + ANSI.ENDC print(module_settings_string) print(ANSI.BOLD + u"[*] Sync assignments: \t" + ANSI.ENDC + (ANSI.GREEN if self.sync_assignments else ANSI.RED) + str(self.sync_assignments) + ANSI.ENDC) print(ANSI.BOLD + u"[*] Download linked files: \t" + ANSI.ENDC + (ANSI.GREEN if self.download_linked else ANSI.RED) + str(self.download_linked) + ANSI.ENDC) print(ANSI.BOLD + u"[*] Avoid item duplicates: \t" + ANSI.ENDC + (ANSI.GREEN if self.avoid_duplicates else ANSI.RED) + str(self.avoid_duplicates) + ANSI.ENDC) print(ANSI.BOLD + u"[*] Use course nicknames: \t" + ANSI.ENDC + (ANSI.GREEN if self.use_nicknames else ANSI.RED) + str(self.use_nicknames) + ANSI.ENDC)
def set_settings(self): try: self._set_settings() except KeyboardInterrupt: print( ANSI.format(u"\n\n[*] Setup interrupted, nothing was saved.", formatting=u"red")) sys.exit() self.write_settings()
def ask_for_module_settings(module_settings, settings): choice = -1 while choice != 0: settings.print_advanced_settings(clear=True) print(ANSI.format(u"\n\nModule settings", u"announcer")) print( ANSI.format( u"In Canvas, 'Modules' may contain various items such as files, HTML pages of\n" u"exercises or reading material as well as links to external web-pages.\n\n" u"Below you may specify, if you would like CanvasSync to avoid syncing some of these items.\n" u"OBS: If you chose 'False' to all items, Modules will be skipped all together.", u"white")) print(ANSI.format(u"\nSync this item\tNumber\t\tItem", u"blue")) list_of_keys = list(module_settings.keys()) for index, item in enumerate(list_of_keys): boolean = module_settings[item] print(u"%s\t\t[%s]\t\t%s" % (ANSI.format(str(boolean), u"green" if boolean else u"red"), index + 1, item)) print(u"\n\t\t[%s]\t\t%s" % (0, ANSI.format(u"Confirm selection", u"blue"))) try: choice = int(input(u"\nChoose number: ")) if choice < 0 or choice > len(module_settings): continue except ValueError: continue if choice == 0: break else: module_settings[list_of_keys[choice - 1]] = module_settings[ list_of_keys[choice - 1]] is not True return module_settings
def show_main_screen(settings_file_exists): """ Prompt the user for initial choice of action. Does not allow Synchronization before settings file has been set """ choice = -1 to_do = "quit" while choice not in (0, 1, 2, 3, 4): static_functions.clear_console() # Load version string import CanvasSync version = CanvasSync.__version__ title = u"CanvasSync, " pretty_string = u"-" * (len(title) + len(version)) print( ANSI.format( u"%s\n%s%s\n%s" % (pretty_string, title, version, pretty_string), u"file")) print( ANSI.format( u"Automatically synchronize modules, assignments & files located on a Canvas web server.", u"announcer")) print(ANSI.format(u"\nWhat would you like to do?", u"underline")) print(u"\n\t1) " + ANSI.format(u"Synchronize my Canvas", u"blue")) print(u"\t2) " + ANSI.format(u"Set new settings", u"white")) print(u"\t3) " + ANSI.format(u"Show current settings", u"white")) print(u"\t4) " + ANSI.format(u"Show help", u"white")) print(u"\n\t0) " + ANSI.format(u"Quit", u"yellow")) try: choice = int(input(u"\nChoose number: ")) if choice < 0 or choice > 4: continue except ValueError: continue if choice == 1 and not settings_file_exists: to_do = u"set_settings" else: to_do = [ u"quit", u"sync", u"set_settings", u"show_settings", u"show_help" ][choice] return to_do
def sync(self): """ Synchronize by creating a local URL shortcut file in in at the sync_pat ExternalUrl objects have no children objects and represents an end point of a folder traverse. """ make_url_shortcut(url=self.url_info[u"external_url"], path=self.sync_path) # As opposed to the File and Page classes we never write the "DOWNLOAD" status as we already have # all information needed to create the URL shortcut at this point. Here we just print the SYNCED status # no matter if the shortcut was recreated or not print( ANSI.format(u"[SYNCED]", formatting=u"green") + str(self)[len(u"[SYNCED]"):]) sys.stdout.flush()
def ask_for_courses(settings, api): courses = api.get_courses() if settings.use_nicknames: courses = [name[u"name"] for name in courses] else: courses = [name[u"course_code"].split(";")[-1] for name in courses] choices = [True] * len(courses) choice = -1 while choice != 0: settings.print_settings(clear=True) print( ANSI.format( u"\n\nPlease choose which courses you would like CanvasSync to sync for you:\n", u"white")) print(ANSI.format(u"Sync this item\tNumber\tCourse Title", u"blue")) for index, course in enumerate(courses): print(u"%s\t\t[%s]\t%s" % (ANSI.format(str(choices[index]), u"green" if choices[index] else u"red"), index + 1, courses[index])) print(u"\n\n\t\t[%s]\t%s" % (0, ANSI.format(u"Confirm selection (at least one course required)", "blue"))) print(u"\t\t[%s]\t%s" % (-1, ANSI.format(u"Select all", u"green"))) print(u"\t\t[%s]\t%s" % (-2, ANSI.format(u"Deselect all", u"red"))) try: choice = int(input(u"\nChoose number: ")) if choice < -2 or choice > len(courses): continue except ValueError: continue if choice == 0: if sum(choices) == 0: choice = -1 continue else: break elif choice == -1: choices = [True] * len(courses) elif choice == -2: choices = [False] * len(courses) else: choices[choice - 1] = choices[choice - 1] is not True print(choices) return [x for index, x in enumerate(courses) if choices[index]]
def walk(self): """ Walk by adding all Courses to the list of children """ # Print initial walk message print(self) print( ANSI.format( u"\n[*] Mapping out the Canvas folder hierarchy. Please wait...", u"red")) self.add_courses() counter = [2] for course in self: course.walk(counter) return counter
def do_sync(settings, password=None): # Initialize the Instructure Api object used to make API # calls to the Canvas server valid_token = settings.load_settings(password) if not valid_token: settings.print_auth_token_reset_error() sys.exit() # Initialize the API object api = InstructureApi(settings) # Start Synchronizer with the current settings synchronizer = Synchronizer(settings=settings, api=api) synchronizer.sync() # If here, sync was completed, show prompt print(ANSI.format(u"\n\n[*] Sync complete", formatting=u"bold"))
def write_settings(self): self.print_settings(first_time_setup=False, clear=True) self.print_advanced_settings(clear=False) print(ANSI.format(u"\n\nThese settings will be saved", u"announcer")) # Write password encrypted settings to hidden file in home directory with open(self.settings_path, u"wb") as out_file: settings = self.sync_path + u"\n" + self.domain + u"\n" + self.token + u"\n" for course in self.courses_to_sync: settings += u"SYNC COURSE$" + course + u"\n" settings += u"Files$" + str( self.modules_settings[u"Files"]) + u"\n" settings += u"HTML pages$" + str( self.modules_settings[u"HTML pages"]) + u"\n" settings += u"External URLs$" + str( self.modules_settings[u"External URLs"]) + u"\n" settings += u"Assignments$" + str(self.sync_assignments) + u"\n" settings += u"Linked files$" + str(self.download_linked) + u"\n" settings += u"Avoid duplicates$" + str(self.avoid_duplicates) settings += u"Use nicknames$" + str(self.use_nicknames) out_file.write(encrypt(settings))
def load_settings(self, password): """ Loads the current settings from the settings file and sets the attributes of the Settings object """ if self.is_loaded(): return static_functions.validate_token(self.domain, self.token) if not self.settings_file_exists(): self.set_settings() return True encrypted_message = open(self.settings_path, u"rb").read() messages = decrypt(encrypted_message, password) if not messages: # Password file did not exist, set new settings print( ANSI.format( u"\n[ERROR] The hashed password file does not longer exist. You must re-enter settings.", u"announcer")) input(u"\nPres enter to continue.") self.set_settings() return self.load_settings("") else: messages = messages.decode(u"utf-8").split(u"\n") # Set sync path, domain and auth token self.sync_path, self.domain, self.token = messages[:3] # Extract synchronization settings for message in messages: if message[:12] == u"SYNC COURSE$": if self.courses_to_sync[0] == u"Not set": self.courses_to_sync.pop(0) self.courses_to_sync.append(message.split(u"$")[-1]) if message[:6] == u"Files$": self.modules_settings[u"Files"] = True if message.split( u"$")[-1] == u"True" else False if message[:11] == u"HTML pages$": self.modules_settings[u"HTML pages"] = True if message.split( u"$")[-1] == u"True" else False if message[:14] == u"External URLs$": self.modules_settings[ u"External URLs"] = True if message.split( u"$")[-1] == u"True" else False if message[:12] == u"Assignments$": self.sync_assignments = True if message.split( u"$")[-1] == u"True" else False if message[:13] == u"Linked files$": self.download_linked = True if message.split( u"$")[-1] == u"True" else False if message[:17] == u"Avoid duplicates$": self.avoid_duplicates = True if message.split( u"$")[-1] == u"True" else False if message[:14] == u"Use nicknames$": self.use_nicknames = True if message.split( u"$")[-1] == u"True" else False if not static_functions.validate_token(self.domain, self.token): return False else: return True
def __repr__(self): """ String representation, overwriting base class method """ status = ANSI.format(u"[SYNCED]", formatting=u"green") return (status + u" " * 7 + u"| " + u"\t" * self.indent + u"%s: %s" % (ANSI.format(u"Module", formatting=u"module"), self.name))
api = InstructureApi(settings) # Start Synchronizer with the current settings synchronizer = Synchronizer(settings=settings, api=api) synchronizer.sync() # If here, sync was completed, show prompt print(ANSI.format(u"\n\n[*] Sync complete", formatting=u"bold")) # If main module if __name__ == u"__main__": if os.name == u"nt": # Warn Windows users static_functions.clear_console() input( u"\n[OBS] You are running CanvasSync on a Windows operating system.\n" u" The application is not developed for Windows machines and may be\n" u" unstable. Some pretty output formatting and tab-autocompletion\n" u" is not supported... :-(\n" u"\n Anyway, hit enter to start.") try: run_canvas_sync() except KeyboardInterrupt: print( ANSI.format(u"\n\n[*] Synchronization interrupted", formatting=u"red")) sys.exit()
def __repr__(self): """ String representation, overwriting base class method """ return u" " * 15 + u"| " + u"\t" * self.indent + u"%s: %s" % ( ANSI.format(u"File", formatting=u"file"), self.name)