diff --git a/main.py b/main.py index 283d02c..1778cd9 100644 --- a/main.py +++ b/main.py @@ -36,7 +36,7 @@ def setup_logging(): setup_logging() MainLoop : asyncio.AbstractEventLoop = None -Client = PKFs("token.json", proxy="http://127.0.0.1:7890") +Client = PKFs("token.json", proxy="http://127.0.0.1:7897") def RunSync(func): @wraps(func) @@ -108,19 +108,26 @@ class Console(cmd2.Cmd): self.outputThread.join() # commands # - def do_debug(self, args): + def do_logging_off(self, args): + """ + Disable logging + """ + logging.getLogger().setLevel(logging.CRITICAL) + logging.critical("Logging disabled") + + def do_logging_debug(self, args): """ Enable debug mode """ logging.getLogger().setLevel(logging.DEBUG) logging.debug("Debug mode enabled") - def do_debugoff(self, args): + def do_logging_info(self, args): """ - Disable debug mode + Enable info mode """ logging.getLogger().setLevel(logging.INFO) - logging.info("Debug mode disabled") + logging.info("Info mode enabled") login_parser = cmd2.Cmd2ArgumentParser() login_parser.add_argument("username", help="username", nargs="?") @@ -273,15 +280,21 @@ class Console(cmd2.Cmd): Query All Tasks """ tasks = await Client.QueryTasks(PKTaskStatus(args.filter) if args.filter is not None else None) + # 格式化输出所有task信息id,status,lastStatus的信息,输出表格 + await self.AsyncPrint("id\tstatus\tlastStatus") for task in tasks: - await self.AsyncPrint(f"{task.id}: {task.status.name}") + await self.AsyncPrint(f"{task.id}\t{task.status.value}\t{task.recoverStatus.value}") - def print_debug(self, jsonObject): - logging.debug(json.dumps(jsonObject, indent=4)) + retry_parser = cmd2.Cmd2ArgumentParser() + retry_parser.add_argument("taskId", help="taskId", type=int) @RunSync - async def do_test(self, args): - self.print_debug(await Client.client.offline_list()) + @cmd2.with_argparser(retry_parser) + async def do_retry(self, args): + """ + Retry a task + """ + await Client.RetryTask(args.taskId) async def mainLoop(): global MainLoop, Client diff --git a/pikpakFs.py b/pikpakFs.py index 45fec2e..c57c1b3 100644 --- a/pikpakFs.py +++ b/pikpakFs.py @@ -1,5 +1,5 @@ import httpx -from pikpakapi import PikPakApi +from pikpakapi import PikPakApi, DownloadStatus from typing import Dict from datetime import datetime import json @@ -10,21 +10,25 @@ import asyncio class PKTaskStatus(Enum): - pending = "pending" - offline_downloading = "offline_downloading" + pending_offline_download = "pending" + offline_downloading = "remote_downloading" + pending_download = "pending_for_download" downloading = "downloading" done = "done" error = "error" class PkTask: id = 0 - def __init__(self, torrent : str, toDirId : str, status : PKTaskStatus = PKTaskStatus.pending): + def __init__(self, torrent : str, toDirId : str, status : PKTaskStatus = PKTaskStatus.pending_offline_download): PkTask.id += 1 self.taskId = PkTask.id self.status = status + self.recoverStatus = status + self.name : str = "" self.runningTask : asyncio.Task = None self.toDirId = toDirId + self.nodeId : str = None self.torrent = torrent self.url = None self.pkTaskId = None @@ -86,24 +90,70 @@ class PkToken: return cls(**data) class PKFs: + async def RetryTask(self, taskId : int): + task = self.tasks[taskId] + if task == None or task.status != PKTaskStatus.error: + return + task.status = task.recoverStatus + self.RunTask(task) + async def _task_pending(self, task : PkTask): pkTask = await self.client.offline_download(task.torrent, task.toDirId) task.pkTaskId = pkTask["task"]["id"] + task.nodeId = pkTask["task"]["file_id"] + task.name = pkTask["task"]["name"] task.status = PKTaskStatus.offline_downloading async def _task_offline_downloading(self, task : PkTask): - waitTime = 1 - await asyncio.sleep(waitTime) - # status = await self.client.get_task_status(task.pkTaskId) + waitTime = 3 + while True: + await asyncio.sleep(waitTime) + status = await self.client.get_task_status(task.pkTaskId, task.nodeId) + if status == DownloadStatus.not_found or status == DownloadStatus.not_found or status == DownloadStatus.error: + task.recoverStatus = PKTaskStatus.pending_offline_download + task.status = PKTaskStatus.error + break + elif status == DownloadStatus.done: + fileInfo = await self.client.offline_file_info(file_id=task.nodeId) + if self.GetNodeById(task.nodeId) is not None: + oldFather = self.GetFatherNode(task.nodeId) + if oldFather is not None: + oldFather.childrenId.remove(task.nodeId) + + task.toDirId = fileInfo["parent_id"] + task.name = fileInfo["name"] + type = fileInfo["kind"] + if type.endswith("folder"): + self.nodes[task.nodeId] = DirNode(task.nodeId, task.name, task.toDirId) + else: + self.nodes[task.nodeId] = FileNode(task.nodeId, task.name, task.toDirId) + father = self.GetNodeById(task.toDirId) + if father.id is not None: + father.childrenId.append(task.nodeId) + task.status = PKTaskStatus.pending_download + break + waitTime = waitTime * 1.5 async def _task_worker(self, task : PkTask): while task.status != PKTaskStatus.done and task.status != PKTaskStatus.error: - if task.status == PKTaskStatus.pending: - await self._task_pending(task) - if task.status == PKTaskStatus.offline_downloading: - await self._task_offline_downloading(task) - break - + try: + if task.status == PKTaskStatus.pending_offline_download: + await self._task_pending(task) + continue + + if task.status == PKTaskStatus.offline_downloading: + await self._task_offline_downloading(task) + continue + + if task.status == PKTaskStatus.pending_download: + task.status = PKTaskStatus.done + pass + + break + except Exception as e: + logging.error(f"task failed, exception occured: {e}") + task.recoverStatus = task.status + task.status = PKTaskStatus.error def RunTask(self, task : PkTask): self.tasks.append(task) @@ -174,6 +224,8 @@ class PKFs: def GetNodeById(self, id : str) -> FsNode: if id == self.root.id: return self.root + if id not in self.nodes: + return None return self.nodes[id] def GetFatherNode(self, node : FsNode) -> FsNode: @@ -191,9 +243,9 @@ class PKFs: return None async def Refresh(self, node : FsNode): - if node.lastUpdate != None: - return if IsDir(node): + if node.lastUpdate != None: + return next_page_token = None childrenInfo = [] while True: diff --git a/readme.md b/readme.md index 2f562ec..8281d2e 100644 --- a/readme.md +++ b/readme.md @@ -99,6 +99,60 @@ Todo: 3. offline_list ```json + +{ + "tasks": [ + { + "kind": "drive#task", + "id": "VOASrVEVIQmaCBjEu8Y1VDb7o1", + "name": "[LoliHouse] Mahoutsukai ni Narenakatta Onnanoko no Hanashi - 04 [WebRip 1080p HEVC-10bit AAC SRTx2].mkv", + "type": "offline", + "user_id": "ZEBRT8Wc1IzU1rfZ", + "statuses": [], + "status_size": 1, + "params": { + "age": "0", + "mime_type": "video/x-matroska", + "predict_speed": "73300775185", + "predict_type": "3", + "url": "magnet:?xt=urn:btih:02816d3bd51f9e3ac72c986cc65f3f7a2b201b5b" + }, + "file_id": "VOASrVFTIQmaCBjEu8Y1VDbAo1", + "file_name": "[LoliHouse] Mahoutsukai ni Narenakatta Onnanoko no Hanashi - 04 [WebRip 1080p HEVC-10bit AAC SRTx2].mkv", + "file_size": "726857457", + "message": "Saving", + "created_time": "2024-10-30T22:39:27.712+08:00", + "updated_time": "2024-10-30T22:39:27.712+08:00", + "third_task_id": "", + "phase": "PHASE_TYPE_RUNNING", + "progress": 90, + "icon_link": "https://static.mypikpak.com/39998a187e280e2ee9ceb5f58315a1bcc744fa64", + "callback": "", + "reference_resource": { + "@type": "type.googleapis.com/drive.ReferenceFile", + "kind": "drive#file", + "id": "VOASrVFTIQmaCBjEu8Y1VDbAo1", + "parent_id": "VNTQEPvYTRlbqP1pB2YGZorwo1", + "name": "[LoliHouse] Mahoutsukai ni Narenakatta Onnanoko no Hanashi - 04 [WebRip 1080p HEVC-10bit AAC SRTx2].mkv", + "size": "726857457", + "mime_type": "video/x-matroska", + "icon_link": "https://static.mypikpak.com/39998a187e280e2ee9ceb5f58315a1bcc744fa64", + "hash": "", + "phase": "PHASE_TYPE_RUNNING", + "thumbnail_link": "", + "params": {}, + "space": "", + "medias": [], + "starred": false, + "tags": [] + }, + "space": "" + } + ], + "next_page_token": "", + "expires_in": 3 +} + { "tasks": [], "next_page_token": "",