From 86163dd2d683e61fcd9d913d4885776745809e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=99=E9=9D=99?= Date: Sun, 25 Apr 2021 18:43:46 +0800 Subject: [PATCH 01/43] New line for failure message --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index c11c4f8..bd598c8 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -29,7 +29,7 @@ async def downloadOne(self, src, dst): async with self.sem: async with self.sess.get(src, proxy=self.config.get("proxies")) as res: if res.status != 200: - print(f"{src} Download failed: {res.status}") + print(f"\n{src} Download failed: {res.status}") return async with aiofiles.open(dst, "+wb") as f: while True: From c00576cc1d934b5b393164166d41956f1428e69f Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 30 Aug 2022 19:44:49 +0800 Subject: [PATCH 02/43] allow retry in clientGetJson --- canvassyncer/__main__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index ebcbadf..8725987 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -84,7 +84,14 @@ async def close(self): async def clientGetJson(self, *args, **kwargs): async with self.sem: - resp = await self.client.get(*args, **kwargs) + check=0 + while (check<=5): + resp = await self.client.get(*args, **kwargs) + try: + temp = resp.json() + check = 10 + except Exception as ee : + check=check+1 return resp.json() async def clientHead(self, *args, **kwargs): From 610e3cbf6eb5ca6a87d91bb4081115a7f78028fa Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 30 Aug 2022 19:46:09 +0800 Subject: [PATCH 03/43] JI changes canvas link. Change the default link accordingly --- README.md | 2 +- canvassyncer/__main__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5be8b23..e9da6a8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Then input the information following the guide. *Note:* 1. `courseCode` should be something like `VG100`, `VG101` -2. `courseID` should be an integer. Check the canvas link of the course. e.g. `courseID = 7` for . +2. `courseID` should be an integer. Check the canvas link of the course. e.g. `courseID = 7` for . If you have not installed `pip` yet, you may refer to or the search engine to get your `pip`. diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 8725987..a25a107 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -359,7 +359,7 @@ def initConfig(): elif os.path.exists("./canvassyncer.json"): oldConfig = json.load(open("./canvassyncer.json")) print("Generating new config file...") - prevu = oldConfig.get("canvasURL", "") if oldConfig else "https://umjicanvas.com" + prevu = oldConfig.get("canvasURL", "") if oldConfig else "https://jicanvas.com" url = input("Canvas url(Defuault: " + prevu + "):").strip() if not url: url = prevu From 5235712c1cd280213fc1840e1b99940c2b7f9179 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Wed, 31 Aug 2022 15:00:23 +0800 Subject: [PATCH 04/43] fix: format & remove unused var --- canvassyncer/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index a25a107..56ea893 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -87,10 +87,10 @@ async def clientGetJson(self, *args, **kwargs): check=0 while (check<=5): resp = await self.client.get(*args, **kwargs) - try: - temp = resp.json() + try: + _=resp.json() check = 10 - except Exception as ee : + except Exception : check=check+1 return resp.json() From 5c9da3ee32118978dc976105b5940b6a3b2a5585 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Sep 2022 15:01:37 +0800 Subject: [PATCH 05/43] change from umjicanvas.com to jicanvas.com --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 17e19bc..78cdbb4 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -347,7 +347,7 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): print("Generating new config file...") url = promptConfigStr( - "Canvas url", "canvasURL", defaultValOnMissing="https://umjicanvas.com" + "Canvas url", "canvasURL", defaultValOnMissing="https://jicanvas.com" ) token = promptConfigStr("Canvas access token", "token") courseCodesStr = promptConfigStr( From bd452339d706c01c7ac4fc222ae00b840fce2ea0 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Sep 2022 15:22:40 +0800 Subject: [PATCH 06/43] allow retry in function json --- canvassyncer/__main__.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 17e19bc..0ce627b 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -57,14 +57,20 @@ async def downloadMany(self, infos, totalSize=0): print(text) async def json(self, *args, **kwargs): + retryTimes=0 checkError = bool(kwargs.pop("checkError", False)) - async with self.sem: - resp = await self.client.get(*args, **kwargs) - res = resp.json() - if checkError and isinstance(res, dict) and res.get("errors"): - errMsg = res["errors"][0].get("message", "unknown error.") - print(f"\nError: {errMsg}") - exit(1) + while (retryTimes<=5): + try: + async with self.sem: + resp = await self.client.get(*args, **kwargs) + res = resp.json() + if checkError and isinstance(res, dict) and res.get("errors"): + errMsg = res["errors"][0].get("message", "unknown error.") + print(f"\nError: {errMsg}") + exit(1) + return res + except Exception: + retryTimes+=1 return res async def head(self, *args, **kwargs): From 41ab2861548e45a63f5036b31082e8c4c4c1327e Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Sep 2022 15:39:37 +0800 Subject: [PATCH 07/43] reformat --- canvassyncer/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 78bc203..5e8f3d1 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -57,9 +57,9 @@ async def downloadMany(self, infos, totalSize=0): print(text) async def json(self, *args, **kwargs): - retryTimes=0 + retryTimes = 0 checkError = bool(kwargs.pop("checkError", False)) - while (retryTimes<=5): + while (retryTimes <= 5): try: async with self.sem: resp = await self.client.get(*args, **kwargs) @@ -70,7 +70,7 @@ async def json(self, *args, **kwargs): exit(1) return res except Exception: - retryTimes+=1 + retryTimes += 1 return res async def head(self, *args, **kwargs): From afe8f7e2c5990df1d9d80f521e00fc982b24d4d6 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Sep 2022 15:01:37 +0800 Subject: [PATCH 08/43] change from umjicanvas.com to jicanvas.com --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 0ce627b..78bc203 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -353,7 +353,7 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): print("Generating new config file...") url = promptConfigStr( - "Canvas url", "canvasURL", defaultValOnMissing="https://umjicanvas.com" + "Canvas url", "canvasURL", defaultValOnMissing="https://jicanvas.com" ) token = promptConfigStr("Canvas access token", "token") courseCodesStr = promptConfigStr( From 20e2516d847399d2dd4bde5396b7e4ab1e7ac20d Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Sep 2022 15:39:37 +0800 Subject: [PATCH 09/43] reformat --- canvassyncer/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 78bc203..5e8f3d1 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -57,9 +57,9 @@ async def downloadMany(self, infos, totalSize=0): print(text) async def json(self, *args, **kwargs): - retryTimes=0 + retryTimes = 0 checkError = bool(kwargs.pop("checkError", False)) - while (retryTimes<=5): + while (retryTimes <= 5): try: async with self.sem: resp = await self.client.get(*args, **kwargs) @@ -70,7 +70,7 @@ async def json(self, *args, **kwargs): exit(1) return res except Exception: - retryTimes+=1 + retryTimes += 1 return res async def head(self, *args, **kwargs): From d597d6f4d92a24f03a0a9323f049a93dd8369d17 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 8 Sep 2022 17:20:34 +0800 Subject: [PATCH 10/43] give detail in debug mode --- canvassyncer/__main__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 5e8f3d1..bbb050d 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -1,5 +1,6 @@ import argparse import asyncio +from distutils.log import debug import json import ntpath import os @@ -59,6 +60,7 @@ async def downloadMany(self, infos, totalSize=0): async def json(self, *args, **kwargs): retryTimes = 0 checkError = bool(kwargs.pop("checkError", False)) + debugMode = bool(kwargs.pop("debug", False)) while (retryTimes <= 5): try: async with self.sem: @@ -69,8 +71,10 @@ async def json(self, *args, **kwargs): print(f"\nError: {errMsg}") exit(1) return res - except Exception: + except Exception as e: retryTimes += 1 + if debugMode: + print(f"{e.__class__.__name__}. Retry. {retryTimes} times.") return res async def head(self, *args, **kwargs): @@ -141,7 +145,7 @@ def prepareLocalFiles(self, courseID, folders): async def getCourseFoldersWithIDHelper(self, page, courseID): res = {} url = f"{self.baseUrl}/courses/{courseID}/folders?page={page}" - folders = await self.client.json(url) + folders = await self.client.json(url,debug=self.config["debug"]) for folder in folders: if folder["full_name"].startswith("course files"): folder["full_name"] = folder["full_name"][len("course files") :] @@ -154,7 +158,7 @@ async def getCourseFoldersWithIDHelper(self, page, courseID): async def getCourseFilesHelper(self, page, courseID, folders): files = {} url = f"{self.baseUrl}/courses/{courseID}/files?page={page}" - canvasFiles = await self.client.json(url) + canvasFiles = await self.client.json(url, debug=self.config["debug"]) if not canvasFiles or isinstance(canvasFiles, dict): return files for f in canvasFiles: @@ -176,7 +180,7 @@ async def getCourseFiles(self, courseID): async def getCourseIdByCourseCodeHelper(self, page, lowerCourseCodes): res = {} url = f"{self.baseUrl}/courses?page={page}" - courses = await self.client.json(url, checkError=True) + courses = await self.client.json(url, checkError=True, debug=self.config["debug"]) if not courses: return res for course in courses: @@ -193,7 +197,7 @@ async def getCourseIdByCourseCode(self): async def getCourseCodeByCourseIDHelper(self, courseID): url = f"{self.baseUrl}/courses/{courseID}" - clientRes = await self.client.json(url) + clientRes = await self.client.json(url, debug=self.config["debug"]) if clientRes.get("course_code") is None: return self.courseCode[courseID] = clientRes["course_code"] From a66eb0ceaf331f07070b4e9fe7723c2d80bfd100 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 8 Sep 2022 17:34:10 +0800 Subject: [PATCH 11/43] remove useless () --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index bbb050d..fac598b 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -61,7 +61,7 @@ async def json(self, *args, **kwargs): retryTimes = 0 checkError = bool(kwargs.pop("checkError", False)) debugMode = bool(kwargs.pop("debug", False)) - while (retryTimes <= 5): + while retryTimes <= 5: try: async with self.sem: resp = await self.client.get(*args, **kwargs) From 30420f700f5536a6da5a21e05885b75b00c69b8f Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 8 Sep 2022 22:38:06 +0800 Subject: [PATCH 12/43] remove useless distutils.log.debug --- canvassyncer/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index fac598b..2248dc8 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -1,6 +1,5 @@ import argparse import asyncio -from distutils.log import debug import json import ntpath import os From 61e7c79a6ed2655e63fdaa5722f1c680d159f9a4 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 8 Sep 2022 22:46:58 +0800 Subject: [PATCH 13/43] format --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 2248dc8..d6bb4a8 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -144,7 +144,7 @@ def prepareLocalFiles(self, courseID, folders): async def getCourseFoldersWithIDHelper(self, page, courseID): res = {} url = f"{self.baseUrl}/courses/{courseID}/folders?page={page}" - folders = await self.client.json(url,debug=self.config["debug"]) + folders = await self.client.json(url, debug=self.config["debug"]) for folder in folders: if folder["full_name"].startswith("course files"): folder["full_name"] = folder["full_name"][len("course files") :] From 033ed79d0c197afd4f1096ccb1720a32c6e2e9dd Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 9 Sep 2022 15:35:04 +0800 Subject: [PATCH 14/43] allow remove old config --- canvassyncer/__main__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 5037edd..d914bfc 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -352,6 +352,11 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): res = input(f"{promptStr}{tipStr}: ").strip() if not res: res = defaultVal + elif res == "remove": + if defaultValOnMissing is not None: + res = defaultValOnMissing + else: + res = "" return res print("Generating new config file...") From b9e41f2710f54eff3f7a2b655671c26abd4c85f4 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 15 Sep 2022 14:31:05 +0800 Subject: [PATCH 15/43] add hint message --- canvassyncer/__main__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index d914bfc..b05f001 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -348,15 +348,17 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): elif isinstance(defaultVal, list): defaultVal = " ".join((str(val) for val in defaultVal)) defaultVal = str(defaultVal) + if defaultValOnMissing is not None: + defaultValOnRemove = defaultValOnMissing + else: + defaultValOnRemove = "" tipStr = f"(Default: {defaultVal})" if defaultVal else "" - res = input(f"{promptStr}{tipStr}: ").strip() + tipRemove = f"(If you input remove, value will change to {defaultValOnRemove})" if defaultValOnRemove != "" else f"(If you input remove, value will change to empty)" + res = input(f"{promptStr}{tipStr}{tipRemove}: ").strip() if not res: res = defaultVal elif res == "remove": - if defaultValOnMissing is not None: - res = defaultValOnMissing - else: - res = "" + res = defaultValOnRemove return res print("Generating new config file...") From 7330c96e259c7d25f9d703a77f69b479d02a9adf Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 15 Sep 2022 14:39:32 +0800 Subject: [PATCH 16/43] rewrite logic --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index b05f001..3771190 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -353,7 +353,7 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): else: defaultValOnRemove = "" tipStr = f"(Default: {defaultVal})" if defaultVal else "" - tipRemove = f"(If you input remove, value will change to {defaultValOnRemove})" if defaultValOnRemove != "" else f"(If you input remove, value will change to empty)" + tipRemove = f"(If you input remove, value will change to " + (f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)") res = input(f"{promptStr}{tipStr}{tipRemove}: ").strip() if not res: res = defaultVal From 4295b07376166e7958903f47fe8721592bc8c25b Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 15 Sep 2022 14:45:02 +0800 Subject: [PATCH 17/43] rewrite logic --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 3771190..1ac4bc4 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -353,7 +353,7 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): else: defaultValOnRemove = "" tipStr = f"(Default: {defaultVal})" if defaultVal else "" - tipRemove = f"(If you input remove, value will change to " + (f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)") + tipRemove = "(If you input remove, value will change to " + (f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)") res = input(f"{promptStr}{tipStr}{tipRemove}: ").strip() if not res: res = defaultVal From e3e13f5c874e736a70a14723dc49af36d1d862a4 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 15 Sep 2022 16:59:02 +0800 Subject: [PATCH 18/43] check file size in downloading --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 1ac4bc4..5f7b592 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -229,7 +229,7 @@ async def getCourseTaskInfoHelper( self.downloadDir, f"{self.courseCode[courseID]}{fileName}" ) path = path.replace("\\", "/").replace("//", "/") - if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path): + if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path) and os.path.getsize(path) == fileName: return response = await self.client.head(fileUrl) fileSize = int(response.get("content-length", 0)) From 7e236e0dff957b294da7395968245c9160579517 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 15 Sep 2022 17:08:42 +0800 Subject: [PATCH 19/43] check file size in downloading --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 5037edd..c569d14 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -229,7 +229,7 @@ async def getCourseTaskInfoHelper( self.downloadDir, f"{self.courseCode[courseID]}{fileName}" ) path = path.replace("\\", "/").replace("//", "/") - if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path): + if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path) and os.path.getsize(path) == fileName: return response = await self.client.head(fileUrl) fileSize = int(response.get("content-length", 0)) From ff5a2b0d8a0ce678bc9b4f28cff54e35ad4572e2 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Sat, 17 Sep 2022 13:17:27 +0800 Subject: [PATCH 20/43] change hint --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 1ac4bc4..b057bba 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -353,7 +353,7 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): else: defaultValOnRemove = "" tipStr = f"(Default: {defaultVal})" if defaultVal else "" - tipRemove = "(If you input remove, value will change to " + (f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)") + tipRemove = "(If you input remove, value will become " + (f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)") res = input(f"{promptStr}{tipStr}{tipRemove}: ").strip() if not res: res = defaultVal From 1806d722dda6113109fcb9136ce03f1c8fcb9b4d Mon Sep 17 00:00:00 2001 From: wznmickey Date: Sat, 17 Sep 2022 13:29:50 +0800 Subject: [PATCH 21/43] use temp file in downloading --- canvassyncer/__main__.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index c569d14..61fdd70 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -36,13 +36,20 @@ async def downloadOne(self, src, dst): if res.status_code != 200: return self.failures.append(f"{src} => {dst}") num_bytes_downloaded = res.num_bytes_downloaded - async with aiofiles.open(dst, "+wb") as f: - async for chunk in res.aiter_bytes(): - await f.write(chunk) - self.tqdm.update( - res.num_bytes_downloaded - num_bytes_downloaded - ) - num_bytes_downloaded = res.num_bytes_downloaded + dst_temp = dst+".temp" + try: + async with aiofiles.open(dst_temp, "+wb") as f: + async for chunk in res.aiter_bytes(): + await f.write(chunk) + self.tqdm.update( + res.num_bytes_downloaded - num_bytes_downloaded + ) + num_bytes_downloaded = res.num_bytes_downloaded + except Exception as e: + print(e.__class__.__name__) + os.remove(dst_temp) + return + os.rename(dst_temp,dst) async def downloadMany(self, infos, totalSize=0): self.tqdm = tqdm(total=totalSize, unit="B", unit_scale=True) @@ -229,7 +236,7 @@ async def getCourseTaskInfoHelper( self.downloadDir, f"{self.courseCode[courseID]}{fileName}" ) path = path.replace("\\", "/").replace("//", "/") - if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path) and os.path.getsize(path) == fileName: + if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path): return response = await self.client.head(fileUrl) fileSize = int(response.get("content-length", 0)) From 15d3cd890c82abbf43c0be6327ee8274762ddd8e Mon Sep 17 00:00:00 2001 From: wznmickey Date: Sat, 17 Sep 2022 19:04:37 +0800 Subject: [PATCH 22/43] use Black to format --- canvassyncer/__main__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 61fdd70..6b8f5ed 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -36,7 +36,7 @@ async def downloadOne(self, src, dst): if res.status_code != 200: return self.failures.append(f"{src} => {dst}") num_bytes_downloaded = res.num_bytes_downloaded - dst_temp = dst+".temp" + dst_temp = dst + ".temp" try: async with aiofiles.open(dst_temp, "+wb") as f: async for chunk in res.aiter_bytes(): @@ -49,7 +49,7 @@ async def downloadOne(self, src, dst): print(e.__class__.__name__) os.remove(dst_temp) return - os.rename(dst_temp,dst) + os.rename(dst_temp, dst) async def downloadMany(self, infos, totalSize=0): self.tqdm = tqdm(total=totalSize, unit="B", unit_scale=True) @@ -186,7 +186,9 @@ async def getCourseFiles(self, courseID): async def getCourseIdByCourseCodeHelper(self, page, lowerCourseCodes): res = {} url = f"{self.baseUrl}/courses?page={page}" - courses = await self.client.json(url, checkError=True, debug=self.config["debug"]) + courses = await self.client.json( + url, checkError=True, debug=self.config["debug"] + ) if not courses: return res for course in courses: From 9364a8dea4568d93b682d625e5b2f77116babd17 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Wed, 31 May 2023 17:44:34 +0800 Subject: [PATCH 23/43] feat: allow filter files by type --- canvassyncer/.canvassyncer_.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 canvassyncer/.canvassyncer_.json diff --git a/canvassyncer/.canvassyncer_.json b/canvassyncer/.canvassyncer_.json new file mode 100644 index 0000000..ef0256c --- /dev/null +++ b/canvassyncer/.canvassyncer_.json @@ -0,0 +1,26 @@ +{ + "canvasURL": "https://jicanvas.com", + "token": "cuDdhSH2aIbs9oAEfM7WdwFPiI1hHv1OnYSWapKtuqEEyQuMdmGyg2GkuK87N5ju", + "courseCodes": [], + "courseIDs": [ + 349, + 177, + 333, + 406, + 411, + 186, + 124, + 112, + 108, + 26, + 91, + 90, + 150, + 156 + ], + "downloadDir": "/home/wznmickey/fromCanvas", + "filesizeThresh": 250.0, + "allowAudio": true, + "allowVideo": true, + "allowImage": true +} \ No newline at end of file From 71be38ac968f0c8463f0e229372d3365d19e5237 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Jun 2023 13:11:21 +0800 Subject: [PATCH 24/43] feat: allow filter files by type --- canvassyncer/__main__.py | 57 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 8ff8e73..44910ab 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -8,7 +8,7 @@ import traceback import platform from datetime import datetime, timezone - +import mimetypes import aiofiles import httpx from tqdm import tqdm @@ -298,7 +298,7 @@ def checkLaterFiles(self): return print(f"Start to download {len(self.laterInfo)} file(s)!") laterFiles = [] - for (fileUrl, path) in self.laterFiles: + for fileUrl, path in self.laterFiles: localCreatedTimeStamp = int(os.path.getctime(path)) try: newPath = os.path.join( @@ -319,6 +319,38 @@ def checkLaterFiles(self): print(f"{e.__class__.__name__}! Skipped: {path}") self.laterFiles = laterFiles + def checkAllowDownload(self, st): + try: + isAudio = (mimetypes.guess_type(st))[0].split("/")[0] == "audio" + except Exception as e: + isAudio = False + try: + isVideo = mimetypes.guess_type(st)[0].split("/")[0] == "video" + except Exception as e: + isVideo = False + try: + isImage = mimetypes.guess_type(st)[0].split("/")[0] == "image" + except Exception as e: + isImage = False + if (not isAudio) or (self.config["allowAudio"]): + if (not isVideo) or (self.config["allowVideo"]): + if (not isImage) or (self.config["allowImage"]): + return True + print(f"remove {st} because of its file type.") + return False + + def checkFilesType(self): + self.laterFiles = [ + (fileUrl, path) + for (fileUrl, path) in self.laterFiles + if self.checkAllowDownload(path) + ] + self.newFiles = [ + (fileUrl, path) + for (fileUrl, path) in self.newFiles + if self.checkAllowDownload(path) + ] + async def sync(self): print("Getting course IDs...") await self.getCourseID() @@ -335,6 +367,7 @@ async def sync(self): return print("All local files are synced!") self.checkNewFiles() self.checkLaterFiles() + self.checkFilesType() await self.client.downloadMany( self.newFiles + self.laterFiles, self.downloadSize + self.laterDownloadSize ) @@ -362,7 +395,9 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): else: defaultValOnRemove = "" tipStr = f"(Default: {defaultVal})" if defaultVal else "" - tipRemove = "(If you input remove, value will become " + (f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)") + tipRemove = "(If you input remove, value will become " + ( + f"{defaultValOnRemove})" if defaultValOnRemove != "" else "empty)" + ) res = input(f"{promptStr}{tipStr}{tipRemove}: ").strip() if not res: res = defaultVal @@ -391,10 +426,23 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): filesizeThreshStr = promptConfigStr( "Maximum file size to download(MB)", "filesizeThresh", defaultValOnMissing=250 ) + allowAudio = promptConfigStr( + "Whether allow downloading audios", "allowAudio", defaultValOnMissing=True + ) + allowVideo = promptConfigStr( + "Whether allow downloading videos", "allowVideo", defaultValOnMissing=True + ) + allowImage = promptConfigStr( + "Whether allow downloading images", "allowImage", defaultValOnMissing=True + ) + try: filesizeThresh = float(filesizeThreshStr) except Exception: filesizeThresh = 250 + allowAudio = (allowAudio == "True") or (allowAudio == "true") + allowVideo = (allowVideo == "True") or (allowVideo == "true") + allowImage = (allowImage == "True") or (allowImage == "true") return { "canvasURL": url, "token": token, @@ -402,6 +450,9 @@ def promptConfigStr(promptStr, key, *, defaultValOnMissing=None): "courseIDs": courseIDs, "downloadDir": downloadDir, "filesizeThresh": filesizeThresh, + "allowAudio": allowAudio, + "allowVideo": allowVideo, + "allowImage": allowImage, } From a829992472cfdaf1249ee1461f33b39ea4a78f09 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 1 Jun 2023 13:37:18 +0800 Subject: [PATCH 25/43] fix: remove unused var --- canvassyncer/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 44910ab..16ed1cd 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -322,15 +322,15 @@ def checkLaterFiles(self): def checkAllowDownload(self, st): try: isAudio = (mimetypes.guess_type(st))[0].split("/")[0] == "audio" - except Exception as e: + except Exception: isAudio = False try: isVideo = mimetypes.guess_type(st)[0].split("/")[0] == "video" - except Exception as e: + except Exception: isVideo = False try: isImage = mimetypes.guess_type(st)[0].split("/")[0] == "image" - except Exception as e: + except Exception: isImage = False if (not isAudio) or (self.config["allowAudio"]): if (not isVideo) or (self.config["allowVideo"]): From fa3575e4f3368e6e32dfe81d2f52078eacd1bd1b Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 6 Jun 2023 13:49:10 +0800 Subject: [PATCH 26/43] refactor: checkAllowDownload --- canvassyncer/__main__.py | 44 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 16ed1cd..74325f2 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -13,7 +13,7 @@ import httpx from tqdm import tqdm -__version__ = "2.0.10" +__version__ = "2.0.11" CONFIG_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), ".canvassyncer.json" ) @@ -319,25 +319,29 @@ def checkLaterFiles(self): print(f"{e.__class__.__name__}! Skipped: {path}") self.laterFiles = laterFiles - def checkAllowDownload(self, st): - try: - isAudio = (mimetypes.guess_type(st))[0].split("/")[0] == "audio" - except Exception: - isAudio = False - try: - isVideo = mimetypes.guess_type(st)[0].split("/")[0] == "video" - except Exception: - isVideo = False - try: - isImage = mimetypes.guess_type(st)[0].split("/")[0] == "image" - except Exception: - isImage = False - if (not isAudio) or (self.config["allowAudio"]): - if (not isVideo) or (self.config["allowVideo"]): - if (not isImage) or (self.config["allowImage"]): - return True - print(f"remove {st} because of its file type.") - return False + def checkAllowDownload(self, filename): + type = (mimetypes.guess_type(filename))[0] + if type is None: + return True + if not self.config["allowAudio"]: + if type.split("/")[0] == "audio": + print( + f"Remove {filename} from the download list because of its file type: audio." + ) + return False + if not self.config["allowVideo"]: + if type.split("/")[0] == "video": + print( + f"Remove {filename} the download list because of its file type: video." + ) + return False + if not self.config["allowImage"]: + if type.split("/")[0] == "image": + print( + f"Remove {filename} the download list because of its file type: image." + ) + return False + return True def checkFilesType(self): self.laterFiles = [ From b4652798246eb2310e73ba520081ccea3e908272 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 6 Jun 2023 13:56:26 +0800 Subject: [PATCH 27/43] fix: allow Old config --- canvassyncer/__main__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 74325f2..324ae06 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -512,6 +512,13 @@ def getConfig(): config["connection_count"] = args.connection config["no_keep_older_version"] = args.no_keep_older_version config["debug"] = args.debug + if not "allowAudio" in config: + config["allowAudio"] = True + if not "allowVideo" in config: + config["allowVideo"] = True + if not "allowImage" in config: + config["allowImage"] = True + return config From ceb91d9a0ccc21aa66ddcdcbeda3722e35151e43 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 6 Jun 2023 16:33:13 +0800 Subject: [PATCH 28/43] fix: avoid Redefining built-in 'type' --- canvassyncer/__main__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 324ae06..a515b91 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -320,23 +320,23 @@ def checkLaterFiles(self): self.laterFiles = laterFiles def checkAllowDownload(self, filename): - type = (mimetypes.guess_type(filename))[0] - if type is None: + fileType = (mimetypes.guess_type(filename))[0] + if fileType is None: return True if not self.config["allowAudio"]: - if type.split("/")[0] == "audio": + if fileType.split("/")[0] == "audio": print( f"Remove {filename} from the download list because of its file type: audio." ) return False if not self.config["allowVideo"]: - if type.split("/")[0] == "video": + if fileType.split("/")[0] == "video": print( f"Remove {filename} the download list because of its file type: video." ) return False if not self.config["allowImage"]: - if type.split("/")[0] == "image": + if fileType.split("/")[0] == "image": print( f"Remove {filename} the download list because of its file type: image." ) From 6dad3ae7b1d14a25f301eb4d22ea25bdfd049118 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 30 Jun 2023 15:16:32 +0800 Subject: [PATCH 29/43] change back version --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index a515b91..e95bdd3 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -13,7 +13,7 @@ import httpx from tqdm import tqdm -__version__ = "2.0.11" +__version__ = "2.0.10" CONFIG_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), ".canvassyncer.json" ) From 28cd9d4e11ebf6e4063b16a9b6cd30ea1ba990d5 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Sun, 2 Jul 2023 16:02:50 +0800 Subject: [PATCH 30/43] Update .canvassyncer_.json --- canvassyncer/.canvassyncer_.json | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/canvassyncer/.canvassyncer_.json b/canvassyncer/.canvassyncer_.json index ef0256c..8b13789 100644 --- a/canvassyncer/.canvassyncer_.json +++ b/canvassyncer/.canvassyncer_.json @@ -1,26 +1 @@ -{ - "canvasURL": "https://jicanvas.com", - "token": "cuDdhSH2aIbs9oAEfM7WdwFPiI1hHv1OnYSWapKtuqEEyQuMdmGyg2GkuK87N5ju", - "courseCodes": [], - "courseIDs": [ - 349, - 177, - 333, - 406, - 411, - 186, - 124, - 112, - 108, - 26, - 91, - 90, - 150, - 156 - ], - "downloadDir": "/home/wznmickey/fromCanvas", - "filesizeThresh": 250.0, - "allowAudio": true, - "allowVideo": true, - "allowImage": true -} \ No newline at end of file + From b7d9b25691c32d0da4b028f6c7dab5a82a9e5803 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Sun, 2 Jul 2023 16:03:11 +0800 Subject: [PATCH 31/43] Delete .canvassyncer_.json --- canvassyncer/.canvassyncer_.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 canvassyncer/.canvassyncer_.json diff --git a/canvassyncer/.canvassyncer_.json b/canvassyncer/.canvassyncer_.json deleted file mode 100644 index 8b13789..0000000 --- a/canvassyncer/.canvassyncer_.json +++ /dev/null @@ -1 +0,0 @@ - From bc2e4ec6779bb23bda9871a979296f2986a43340 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 1 Aug 2023 20:54:43 +0800 Subject: [PATCH 32/43] fix:allow retry when failed to get folder --- canvassyncer/__main__.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index e95bdd3..8cb797d 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -151,15 +151,24 @@ def prepareLocalFiles(self, courseID, folders): async def getCourseFoldersWithIDHelper(self, page, courseID): res = {} url = f"{self.baseUrl}/courses/{courseID}/folders?page={page}" - folders = await self.client.json(url, debug=self.config["debug"]) - for folder in folders: - if folder["full_name"].startswith("course files"): - folder["full_name"] = folder["full_name"][len("course files") :] - res[folder["id"]] = folder["full_name"] - if not res[folder["id"]]: - res[folder["id"]] = "/" - res[folder["id"]] = re.sub(r"[\\\:\*\?\"\<\>\|]", "_", res[folder["id"]]) - return res + retryTimes = 0 + while retryTimes < 5: + try: + folders = await self.client.json(url, debug=self.config["debug"]) + for folder in folders: + if folder["full_name"].startswith("course files"): + folder["full_name"] = folder["full_name"][len("course files") :] + res[folder["id"]] = folder["full_name"] + if not res[folder["id"]]: + res[folder["id"]] = "/" + res[folder["id"]] = re.sub( + r"[\\\:\*\?\"\<\>\|]", "_", res[folder["id"]] + ) + return res + except Exception as e: + retryTimes = retryTimes + 1 + if self.config["debug"]: + print(str(retryTimes) + " time(s) error: " + str(e)) async def getCourseFilesHelper(self, page, courseID, folders): files = {} From ec168b78d5fbeb20d15484642dc883d0175583ae Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 30 Jun 2023 15:16:32 +0800 Subject: [PATCH 33/43] change back version --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index a515b91..e95bdd3 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -13,7 +13,7 @@ import httpx from tqdm import tqdm -__version__ = "2.0.11" +__version__ = "2.0.10" CONFIG_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), ".canvassyncer.json" ) From 15a0c01bd7132075d0703c7171cd3ac91e369cee Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 1 Aug 2023 20:54:43 +0800 Subject: [PATCH 34/43] fix:allow retry when failed to get folder --- canvassyncer/__main__.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index e95bdd3..8cb797d 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -151,15 +151,24 @@ def prepareLocalFiles(self, courseID, folders): async def getCourseFoldersWithIDHelper(self, page, courseID): res = {} url = f"{self.baseUrl}/courses/{courseID}/folders?page={page}" - folders = await self.client.json(url, debug=self.config["debug"]) - for folder in folders: - if folder["full_name"].startswith("course files"): - folder["full_name"] = folder["full_name"][len("course files") :] - res[folder["id"]] = folder["full_name"] - if not res[folder["id"]]: - res[folder["id"]] = "/" - res[folder["id"]] = re.sub(r"[\\\:\*\?\"\<\>\|]", "_", res[folder["id"]]) - return res + retryTimes = 0 + while retryTimes < 5: + try: + folders = await self.client.json(url, debug=self.config["debug"]) + for folder in folders: + if folder["full_name"].startswith("course files"): + folder["full_name"] = folder["full_name"][len("course files") :] + res[folder["id"]] = folder["full_name"] + if not res[folder["id"]]: + res[folder["id"]] = "/" + res[folder["id"]] = re.sub( + r"[\\\:\*\?\"\<\>\|]", "_", res[folder["id"]] + ) + return res + except Exception as e: + retryTimes = retryTimes + 1 + if self.config["debug"]: + print(str(retryTimes) + " time(s) error: " + str(e)) async def getCourseFilesHelper(self, page, courseID, folders): files = {} From af3f52ff326c69cab2759d8775248a568977ea5e Mon Sep 17 00:00:00 2001 From: wznmickey Date: Tue, 1 Aug 2023 21:14:52 +0800 Subject: [PATCH 35/43] fix:update version --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 8cb797d..4c0fe9b 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -13,7 +13,7 @@ import httpx from tqdm import tqdm -__version__ = "2.0.10" +__version__ = "2.0.11" CONFIG_PATH = os.path.join( os.path.dirname(os.path.abspath(__file__)), ".canvassyncer.json" ) From e5268802ee8d8e45434e32fe215472be2e5599e1 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Thu, 7 Sep 2023 16:39:14 +0800 Subject: [PATCH 36/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 856fc1c..aad2dfe 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CodeFactor](https://www.codefactor.io/repository/github/boyanzh/canvas-syncer/badge)](https://www.codefactor.io/repository/github/boyanzh/canvas-syncer) [![PyPi Version](https://img.shields.io/pypi/v/canvassyncer)](https://pypi.org/pypi/canvassyncer) -An async python script that synchronizes files and folders across Canvas Files and local, with extremely fast speed. +An async python script that synchronizes files and folders across Canvas LMS Files and local, with extremely fast speed. ## Installation From a117fb3b92860ff2b5a28e8afb5c2dd3d914999c Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 14:06:52 +0800 Subject: [PATCH 37/43] feat. use term folder --- canvassyncer/__main__.py | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 4c0fe9b..0bed46a 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -91,6 +91,20 @@ async def head(self, *args, **kwargs): async def aclose(self): await self.client.aclose() + async def getCourseList(self, *args, **kwargs): + async with self.sem: + resp = await self.client.get(*args, **kwargs) + res = resp.json() + courseMapTerm = {} + termMapDisplayname = {} + for i in res: + courseId = i["id"] + termId = i["enrollment_term_id"] + termName = i["term"]["name"] + courseMapTerm[courseId] = termId + termMapDisplayname[termId] = termName + return (courseMapTerm, termMapDisplayname) + class CanvasSyncer: def __init__(self, config): @@ -109,9 +123,14 @@ def __init__(self, config): self.laterInfo = [] self.skipfiles = [] self.totalFileCount = 0 + self.__courseMapTerm = {} + self.__termMapDisplayname = {} if not os.path.exists(self.downloadDir): os.mkdir(self.downloadDir) + async def getCourseList(self): + return await self.client.getCourseList(f"{self.baseUrl}/courses?include[]=term") + async def aclose(self): await self.client.aclose() @@ -133,12 +152,14 @@ async def dictFromPages(self, helperFunc, *args, **kwargs): def prepareLocalFiles(self, courseID, folders): localFiles = [] for folder in folders.values(): + path = os.path.join( + self.downloadDir, + self.__termMapDisplayname[self.__courseMapTerm[courseID]], + ) if self.config["no_subfolder"]: - path = os.path.join(self.downloadDir, folder[1:]) + path = os.path.join(path, folder[1:]) else: - path = os.path.join( - self.downloadDir, f"{self.courseCode[courseID]}{folder}" - ) + path = os.path.join(path, f"{self.courseCode[courseID]}{folder}") if not os.path.exists(path): os.makedirs(path) localFiles += [ @@ -240,12 +261,13 @@ async def getCourseTaskInfoHelper( ): if not fileUrl: return + path = os.path.join( + self.downloadDir, self.__termMapDisplayname[self.__courseMapTerm[courseID]] + ) if self.config["no_subfolder"]: - path = os.path.join(self.downloadDir, fileName[1:]) + path = os.path.join(path, fileName[1:]) else: - path = os.path.join( - self.downloadDir, f"{self.courseCode[courseID]}{fileName}" - ) + path = os.path.join(path, f"{self.courseCode[courseID]}{fileName}") path = path.replace("\\", "/").replace("//", "/") if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path): return @@ -367,6 +389,7 @@ def checkFilesType(self): async def sync(self): print("Getting course IDs...") await self.getCourseID() + (self.__courseMapTerm, self.__termMapDisplayname) = await self.getCourseList() print(f"Get {len(self.courseCode)} available courses!") print("Finding files on canvas...") await asyncio.gather( From 76dbb75b2cb78c31744927ee05ea29eae8d07d8d Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 14:24:52 +0800 Subject: [PATCH 38/43] Revert "Merge branch 'my/allowRetry' into dev" This reverts commit 99f5a918cd5c928af16d1efd69951f03354a770e, reversing changes made to ed1194b607a72e6c16a04054108ae8d91a199160. --- canvassyncer/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 9941c74..0bed46a 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -65,7 +65,6 @@ async def downloadMany(self, infos, totalSize=0): print(text) async def json(self, *args, **kwargs): - retryTimes = 0 retryTimes = 0 checkError = bool(kwargs.pop("checkError", False)) debugMode = bool(kwargs.pop("debug", False)) From a7b27451f6b3230632062416489550c4f2aa1b8d Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 14:28:50 +0800 Subject: [PATCH 39/43] Revert "Update README.md" This reverts commit e5268802ee8d8e45434e32fe215472be2e5599e1. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aad2dfe..856fc1c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CodeFactor](https://www.codefactor.io/repository/github/boyanzh/canvas-syncer/badge)](https://www.codefactor.io/repository/github/boyanzh/canvas-syncer) [![PyPi Version](https://img.shields.io/pypi/v/canvassyncer)](https://pypi.org/pypi/canvassyncer) -An async python script that synchronizes files and folders across Canvas LMS Files and local, with extremely fast speed. +An async python script that synchronizes files and folders across Canvas Files and local, with extremely fast speed. ## Installation From ad2e2084166505d4e905baedb60804d24dd276fc Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 14:30:51 +0800 Subject: [PATCH 40/43] Revert "fix:allow retry when failed to get folder" This reverts commit 15a0c01bd7132075d0703c7171cd3ac91e369cee. --- canvassyncer/__main__.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index d31ebc5..9b173c5 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -172,24 +172,15 @@ def prepareLocalFiles(self, courseID, folders): async def getCourseFoldersWithIDHelper(self, page, courseID): res = {} url = f"{self.baseUrl}/courses/{courseID}/folders?page={page}" - retryTimes = 0 - while retryTimes < 5: - try: - folders = await self.client.json(url, debug=self.config["debug"]) - for folder in folders: - if folder["full_name"].startswith("course files"): - folder["full_name"] = folder["full_name"][len("course files") :] - res[folder["id"]] = folder["full_name"] - if not res[folder["id"]]: - res[folder["id"]] = "/" - res[folder["id"]] = re.sub( - r"[\\\:\*\?\"\<\>\|]", "_", res[folder["id"]] - ) - return res - except Exception as e: - retryTimes = retryTimes + 1 - if self.config["debug"]: - print(str(retryTimes) + " time(s) error: " + str(e)) + folders = await self.client.json(url, debug=self.config["debug"]) + for folder in folders: + if folder["full_name"].startswith("course files"): + folder["full_name"] = folder["full_name"][len("course files") :] + res[folder["id"]] = folder["full_name"] + if not res[folder["id"]]: + res[folder["id"]] = "/" + res[folder["id"]] = re.sub(r"[\\\:\*\?\"\<\>\|]", "_", res[folder["id"]]) + return res async def getCourseFilesHelper(self, page, courseID, folders): files = {} From 3b73aac50f1a2841ab2a6253e29bf26b8a397d34 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 14:33:04 +0800 Subject: [PATCH 41/43] revert commit --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index f639c37..98d858f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,3 @@ __pycache__ courses/ upload.sh dist.py -*.json From bde0fac30b2018bba75a2e7d4d7cc18499b96e33 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 14:43:50 +0800 Subject: [PATCH 42/43] Revert "check file size in downloading" This reverts commit 7e236e0dff957b294da7395968245c9160579517. --- canvassyncer/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 9b173c5..6978af8 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -260,7 +260,7 @@ async def getCourseTaskInfoHelper( else: path = os.path.join(path, f"{self.courseCode[courseID]}{fileName}") path = path.replace("\\", "/").replace("//", "/") - if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path) and os.path.getsize(path) == fileName: + if fileName in localFiles and fileModifiedTimeStamp <= os.path.getctime(path): return response = await self.client.head(fileUrl) fileSize = int(response.get("content-length", 0)) From 7b00a2da3505b0c835f571e8b7a7fb2968ebfe56 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 8 Sep 2023 15:47:55 +0800 Subject: [PATCH 43/43] allow paging --- canvassyncer/__main__.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/canvassyncer/__main__.py b/canvassyncer/__main__.py index 6978af8..71dd37e 100644 --- a/canvassyncer/__main__.py +++ b/canvassyncer/__main__.py @@ -98,11 +98,14 @@ async def getCourseList(self, *args, **kwargs): courseMapTerm = {} termMapDisplayname = {} for i in res: - courseId = i["id"] - termId = i["enrollment_term_id"] - termName = i["term"]["name"] - courseMapTerm[courseId] = termId - termMapDisplayname[termId] = termName + try: + courseId = i["id"] + termId = i["enrollment_term_id"] + termName = i["term"]["name"] + courseMapTerm[courseId] = termId + termMapDisplayname[termId] = termName + except Exception: + print("get data error in" + str(i)) return (courseMapTerm, termMapDisplayname) @@ -129,7 +132,18 @@ def __init__(self, config): os.mkdir(self.downloadDir) async def getCourseList(self): - return await self.client.getCourseList(f"{self.baseUrl}/courses?include[]=term") + courseMapTerm = {} + termMapDisplayname = {} + i = 0 + while True: + i += 1 + res = await self.client.getCourseList( + f"{self.baseUrl}/courses?include[]=term&page={i}" + ) + courseMapTerm.update(res[0]) + termMapDisplayname.update(res[1]) + if res == ({}, {}): + return (courseMapTerm, termMapDisplayname) async def aclose(self): await self.client.aclose()