进一步调整

This commit is contained in:
limil 2024-11-02 17:38:47 +08:00
parent 0762ec9952
commit ec8c28ee43
4 changed files with 205 additions and 185 deletions

87
main.py
View File

@ -4,12 +4,11 @@ from functools import wraps
import logging import logging
import threading import threading
import colorlog import colorlog
from pikpakFs import PKFs, IsDir, IsFile, PKTaskStatus from PikPakFs import PikPakFs, IsDir, IsFile, TaskStatus
import os import os
import json import keyboard
def setup_logging(): LogFormatter = colorlog.ColoredFormatter(
formatter = colorlog.ColoredFormatter(
"%(log_color)s%(asctime)s - %(levelname)s - %(name)s - %(message)s", "%(log_color)s%(asctime)s - %(levelname)s - %(name)s - %(message)s",
datefmt='%Y-%m-%d %H:%M:%S', datefmt='%Y-%m-%d %H:%M:%S',
reset=True, reset=True,
@ -21,22 +20,19 @@ def setup_logging():
'CRITICAL': 'red,bg_white', 'CRITICAL': 'red,bg_white',
} }
) )
handler = logging.StreamHandler()
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
def setup_logging():
file_handler = logging.FileHandler('app.log') file_handler = logging.FileHandler('app.log')
file_handler.setFormatter(formatter) file_handler.setFormatter(LogFormatter)
file_handler.setLevel(logging.DEBUG) file_handler.setLevel(logging.DEBUG)
logger = logging.getLogger() logger = logging.getLogger()
logger.addHandler(handler)
logger.addHandler(file_handler) logger.addHandler(file_handler)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
setup_logging() setup_logging()
MainLoop : asyncio.AbstractEventLoop = None MainLoop : asyncio.AbstractEventLoop = None
Client = PKFs("token.json", proxy="http://127.0.0.1:7897") Client = PikPakFs("token.json", proxy="http://127.0.0.1:7897")
def RunSync(func): def RunSync(func):
@wraps(func) @wraps(func)
@ -57,20 +53,24 @@ class Console(cmd2.Cmd):
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop.run_forever() loop.run_forever()
async def AsyncInput(self, prompt): async def Input(self, prompt):
async def _input(prompt): async def _input(prompt):
return self._read_command_line(prompt) return self._read_command_line(prompt)
future = asyncio.run_coroutine_threadsafe(_input(prompt), self.inputLoop) future = asyncio.run_coroutine_threadsafe(_input(prompt), self.ioLoop)
return await asyncio.wrap_future(future) return await asyncio.wrap_future(future)
async def AsyncPrint(self, *args, **kwargs): async def Print(self, *args, **kwargs):
async def _print(*args, **kwargs): async def _print(*args, **kwargs):
print(*args, **kwargs) print(*args, **kwargs)
future = asyncio.run_coroutine_threadsafe(_print(*args, **kwargs), self.outputLoop) future = asyncio.run_coroutine_threadsafe(_print(*args, **kwargs), self.ioLoop)
await asyncio.wrap_future(future) await asyncio.wrap_future(future)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.log_handler = logging.StreamHandler()
self.log_handler.setFormatter(LogFormatter)
self.log_handler.setLevel(logging.CRITICAL)
logging.getLogger().addHandler(self.log_handler)
def preloop(self): def preloop(self):
# 1. 设置忽略SIGINT # 1. 设置忽略SIGINT
@ -80,13 +80,9 @@ class Console(cmd2.Cmd):
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
# 2. 创建IO线程处理输入输出 # 2. 创建IO线程处理输入输出
self.inputLoop = asyncio.new_event_loop() self.ioLoop = asyncio.new_event_loop()
self.inputThread = threading.Thread(target=self._io_worker, args=(self.inputLoop,)) self.ioThread = threading.Thread(target=self._io_worker, args=(self.ioLoop,))
self.inputThread.start() self.ioThread.start()
self.outputLoop = asyncio.new_event_loop()
self.outputThread = threading.Thread(target=self._io_worker, args=(self.outputLoop,))
self.outputThread.start()
# 3. 设置console # 3. 设置console
self.saved_readline_settings = None self.saved_readline_settings = None
@ -101,32 +97,29 @@ class Console(cmd2.Cmd):
# 2. 停止IO线程 # 2. 停止IO线程
# https://stackoverflow.com/questions/51642267/asyncio-how-do-you-use-run-forever # https://stackoverflow.com/questions/51642267/asyncio-how-do-you-use-run-forever
self.inputLoop.call_soon_threadsafe(self.inputLoop.stop) self.ioLoop.call_soon_threadsafe(self.ioLoop.stop)
self.inputThread.join() self.ioThread.join()
self.outputLoop.call_soon_threadsafe(self.outputLoop.stop)
self.outputThread.join()
# commands # # commands #
def do_logging_off(self, args): def do_logging_off(self, args):
""" """
Disable logging Disable logging
""" """
logging.getLogger().setLevel(logging.CRITICAL) self.log_handler.setLevel(logging.CRITICAL)
logging.critical("Logging disabled") logging.critical("Logging disabled")
def do_logging_debug(self, args): def do_logging_debug(self, args):
""" """
Enable debug mode Enable debug mode
""" """
logging.getLogger().setLevel(logging.DEBUG) self.log_handler.setLevel(logging.DEBUG)
logging.debug("Debug mode enabled") logging.debug("Debug mode enabled")
def do_logging_info(self, args): def do_logging_info(self, args):
""" """
Enable info mode Enable info mode
""" """
logging.getLogger().setLevel(logging.INFO) self.log_handler.setLevel(logging.INFO)
logging.info("Info mode enabled") logging.info("Info mode enabled")
login_parser = cmd2.Cmd2ArgumentParser() login_parser = cmd2.Cmd2ArgumentParser()
@ -139,7 +132,7 @@ class Console(cmd2.Cmd):
Login to pikpak Login to pikpak
""" """
await Client.Login(args.username, args.password) await Client.Login(args.username, args.password)
await self.AsyncPrint("Logged in successfully") await self.Print("Logged in successfully")
async def _path_completer(self, text, line, begidx, endidx, filterfiles): async def _path_completer(self, text, line, begidx, endidx, filterfiles):
father, sonName = await Client.PathToFatherNodeAndNodeName(text) father, sonName = await Client.PathToFatherNodeAndNodeName(text)
@ -178,15 +171,15 @@ class Console(cmd2.Cmd):
""" """
node = args.path node = args.path
if node is None: if node is None:
await self.AsyncPrint("Invalid path") await self.Print("Invalid path")
return return
await Client.Refresh(node) await Client.Refresh(node)
if IsDir(node): if IsDir(node):
for childId in node.childrenId: for childId in node.childrenId:
child = Client.GetNodeById(childId) child = Client.GetNodeById(childId)
await self.AsyncPrint(child.name) await self.Print(child.name)
elif IsFile(node): elif IsFile(node):
await self.AsyncPrint(f"{node.name}: {node.url}") await self.Print(f"{node.name}: {node.url}")
@RunSync @RunSync
async def complete_cd(self, text, line, begidx, endidx): async def complete_cd(self, text, line, begidx, endidx):
@ -202,7 +195,7 @@ class Console(cmd2.Cmd):
""" """
node = args.path node = args.path
if not IsDir(node): if not IsDir(node):
await self.AsyncPrint("Invalid directory") await self.Print("Invalid directory")
return return
Client.currentLocation = node Client.currentLocation = node
@ -211,7 +204,7 @@ class Console(cmd2.Cmd):
""" """
Print current working directory Print current working directory
""" """
await self.AsyncPrint(Client.NodeToPath(Client.currentLocation)) await self.Print(Client.NodeToPath(Client.currentLocation))
def do_clear(self, args): def do_clear(self, args):
""" """
@ -247,11 +240,11 @@ class Console(cmd2.Cmd):
""" """
father, sonName = args.path_and_son father, sonName = args.path_and_son
if not IsDir(father) or sonName == "" or sonName == None: if not IsDir(father) or sonName == "" or sonName == None:
await self.AsyncPrint("Invalid path") await self.Print("Invalid path")
return return
child = Client.FindChildInDirByName(father, sonName) child = Client.FindChildInDirByName(father, sonName)
if child is not None: if child is not None:
await self.AsyncPrint("Path already exists") await self.Print("Path already exists")
return return
await Client.MakeDir(father, sonName) await Client.MakeDir(father, sonName)
@ -266,28 +259,27 @@ class Console(cmd2.Cmd):
""" """
node = args.path node = args.path
if not IsDir(node): if not IsDir(node):
await self.AsyncPrint("Invalid directory") await self.Print("Invalid directory")
return return
task = await Client.Download(args.url, node) task = await Client.Download(args.url, node)
await self.AsyncPrint(f"Task {task.id} created") await self.Print(f"Task {task.id} created")
query_parser = cmd2.Cmd2ArgumentParser() query_parser = cmd2.Cmd2ArgumentParser()
query_parser.add_argument("-f", "--filter", help="filter", nargs="?", choices=[member.value for member in PKTaskStatus]) query_parser.add_argument("-f", "--filter", help="filter", nargs="?", choices=[member.value for member in TaskStatus])
@RunSync @RunSync
@cmd2.with_argparser(query_parser) @cmd2.with_argparser(query_parser)
async def do_query(self, args): async def do_query(self, args):
""" """
Query All Tasks Query All Tasks
""" """
tasks = await Client.QueryTasks(PKTaskStatus(args.filter) if args.filter is not None else None) tasks = await Client.QueryPikPakTasks(TaskStatus(args.filter) if args.filter is not None else None)
# 格式化输出所有task信息idstatuslastStatus的信息输出表格 # 格式化输出所有task信息idstatuslastStatus的信息输出表格
await self.AsyncPrint("id\tstatus\tlastStatus") await self.Print("tstatus\tdetails\tid")
for task in tasks: for task in tasks:
await self.AsyncPrint(f"{task.id}\t{task.status.value}\t{task.recoverStatus.value}") await self.Print(f"{task._status.value}\t{task.status.value}\t{task.id}")
retry_parser = cmd2.Cmd2ArgumentParser() retry_parser = cmd2.Cmd2ArgumentParser()
retry_parser.add_argument("taskId", help="taskId", type=int) retry_parser.add_argument("taskId", help="taskId")
@RunSync @RunSync
@cmd2.with_argparser(retry_parser) @cmd2.with_argparser(retry_parser)
async def do_retry(self, args): async def do_retry(self, args):
@ -299,17 +291,18 @@ class Console(cmd2.Cmd):
async def mainLoop(): async def mainLoop():
global MainLoop, Client global MainLoop, Client
MainLoop = asyncio.get_running_loop() MainLoop = asyncio.get_running_loop()
Client.Start() clientWorker = Client.Start()
console = Console() console = Console()
console.preloop() console.preloop()
try: try:
stop = False stop = False
while not stop: while not stop:
line = await console.AsyncInput(console.prompt) line = await console.Input(console.prompt)
stop = console.onecmd_plus_hooks(line) stop = console.onecmd_plus_hooks(line)
finally: finally:
console.postloop() console.postloop()
clientWorker.cancel()
if __name__ == "__main__": if __name__ == "__main__":
nest_asyncio.apply() nest_asyncio.apply()

View File

@ -7,63 +7,90 @@ import os
import logging import logging
from enum import Enum from enum import Enum
import asyncio import asyncio
import uuid
from utils import PathWalker
from typing import Callable, Awaitable
class DownloadTaskStatus(Enum): class TaskStatus(Enum):
pending = "pending" PENDING = "pending"
downloading = "downloading" RUNNING = "running"
done = "done" DONE = "done"
error = "error" ERROR = "error"
stopped = "stopped" PAUSED = "paused"
class PKTaskStatus(Enum): class PikPakTaskStatus(Enum):
pending = "pending" PENDING = "pending"
remote_downloading = "remote_downloading" REMOTE_DOWNLOADING = "remote downloading"
downloading = "downloading" LOCAL_DOWNLOADING = "local downloading"
done = "done"
error = "error"
stopped = "stopped"
class PkTask: class FileDownloadTaskStatus(Enum):
_id = 0 PENDING = "pending"
DOWNLOADING = "downloading"
def __init__(self, torrent : str, toDirId : str, status : PKTaskStatus = PKTaskStatus.pending): class UnRecoverableError(Exception):
PkTask._id += 1 def __init__(self, message):
self.id = PkTask._id super().__init__(message)
self.status = PKTaskStatus.pending class TaskBase:
self.recoverStatus = status def __init__(self, id : str, tag : str = "", maxConcurrentNumber = -1):
self.id : str = uuid.uuid4() if id is None else id
self.tag : str = tag
self.maxConcurrentNumber : int = maxConcurrentNumber
self.runningTask : asyncio.Task = None self._status : TaskStatus = TaskStatus.PENDING
self.name = ""
self.toDirId = toDirId self.worker : asyncio.Task = None
self.handler : Callable[..., Awaitable] = None
class PikPakTask(TaskBase):
TAG = "PikPakTask"
MAX_CONCURRENT_NUMBER = 5
def __init__(self, torrent : str, toDirId : str, id : str = None, status : PikPakTaskStatus = PikPakTaskStatus.PENDING):
super().__init__(id, PikPakTask.TAG, PikPakTask.MAX_CONCURRENT_NUMBER)
self.status : PikPakTaskStatus = status
self.toDirId : str = toDirId
self.nodeId : str = None self.nodeId : str = None
self.torrent = torrent # todo: 将torrent的附加参数去掉再加入 self.name : str = ""
self.torrent : str = torrent # todo: 将torrent的附加参数去掉再加入
self.remoteTaskId : str = None self.remoteTaskId : str = None
class DownloadTask: class FileDownloadTask(TaskBase):
def __init__(self, nodeId : str, pkTaskId : str, status : DownloadTaskStatus = DownloadTaskStatus.pending): TAG = "FileDownloadTask"
self.status = DownloadStatus.pending MAX_CONCURRENT_NUMBER = 5
self.recoverStatus = status
self.pkTaskId = pkTaskId
self.nodeId = nodeId
self.runningTask : asyncio.Task = None
class PathWalker(): def __init__(self, nodeId : str, PikPakTaskId : str, id : str = None, status : FileDownloadTaskStatus = FileDownloadTaskStatus.PENDING):
def __init__(self, pathStr : str, sep : str = "/"): super().__init__(id, FileDownloadTask.TAG, FileDownloadTask.MAX_CONCURRENT_NUMBER)
self.__pathSpots : list[str] = [] self.status : FileDownloadTaskStatus = status
if not pathStr.startswith(sep): self.PikPakTaskId : str = PikPakTaskId
self.__pathSpots.append(".") self.nodeId : str = nodeId
pathSpots = pathStr.split(sep)
self.__pathSpots.extend(pathSpots)
def IsAbsolute(self) -> bool: async def TaskWorker(task : TaskBase):
return len(self.__pathSpots) == 0 or self.__pathSpots[0] != "." try:
if task._status != TaskStatus.PENDING:
return
task._status = TaskStatus.RUNNING
await task.handler(task)
task._status = TaskStatus.DONE
except asyncio.CancelledError:
task._status = TaskStatus.PAUSED
except Exception as e:
logging.error(f"task failed, exception occurred: {e}")
task._status = TaskStatus.ERROR
def AppendSpot(self, spot): async def TaskManager(taskQueues : Dict[str, list[TaskBase]]):
self.__pathSpots.append(spot) # todo: 处理取消的情况
while True:
await asyncio.sleep(1)
for taskQueue in taskQueues.values():
notRunningTasks = [task for task in taskQueue if task.worker is None or task.worker.done()]
runningTasksNumber = len(taskQueue) - len(notRunningTasks)
for task in [task for task in notRunningTasks if task._status == TaskStatus.PENDING]:
if runningTasksNumber >= task.maxConcurrentNumber:
break
task.worker = asyncio.create_task(TaskWorker(task))
runningTasksNumber += 1
def Walk(self) -> list[str]:
return self.__pathSpots
class FsNode: class FsNode:
def __init__(self, id : str, name : str, fatherId : str): def __init__(self, id : str, name : str, fatherId : str):
@ -88,7 +115,7 @@ def IsDir(node : FsNode) -> bool:
def IsFile(node : FsNode) -> bool: def IsFile(node : FsNode) -> bool:
return isinstance(node, FileNode) return isinstance(node, FileNode)
class PkToken: class PikPakToken:
def __init__(self, username, password, access_token, refresh_token, user_id): def __init__(self, username, password, access_token, refresh_token, user_id):
self.username = username self.username = username
self.password = password self.password = password
@ -104,96 +131,88 @@ class PkToken:
data = json.loads(json_str) data = json.loads(json_str)
return cls(**data) return cls(**data)
class PKFs: class PikPakFs:
MAX_PIKPAK_TASKS = 5
MAX_DOWNLOAD_TASKS = 5
async def _pktask_pending(self, task : PkTask): async def _pikpak_task_pending(self, task : PikPakTask):
if task.recoverStatus != PKTaskStatus.pending: pikPakTaskInfo = await self.client.offline_download(task.torrent, task.toDirId)
task.status = task.recoverStatus task.remoteTaskId = pikPakTaskInfo["task"]["id"]
return task.nodeId = pikPakTaskInfo["task"]["file_id"]
pkTask = await self.client.offline_download(task.torrent, task.toDirId) task.status = PikPakTaskStatus.REMOTE_DOWNLOADING
task.remoteTaskId = pkTask["task"]["id"]
task.nodeId = pkTask["task"]["file_id"]
task.name = pkTask["task"]["name"]
task.status = PKTaskStatus.remote_downloading
async def _pktask_offline_downloading(self, task : PkTask): async def _pikpak_offline_downloading(self, task : PikPakTask):
waitTime = 3 waitTime = 3
while True: while True:
await asyncio.sleep(waitTime) await asyncio.sleep(waitTime)
status = await self.client.get_task_status(task.remoteTaskId, task.nodeId) status = await self.client.get_task_status(task.remoteTaskId, task.nodeId)
if status in {DownloadStatus.not_found, DownloadStatus.not_downloading, DownloadStatus.error}: if status in {DownloadStatus.not_found, DownloadStatus.not_downloading, DownloadStatus.error}:
task.recoverStatus = PKTaskStatus.pending self.status = PikPakTaskStatus.PENDING
task.status = PKTaskStatus.error raise Exception(f"remote download failed, status: {status}")
break
elif status == DownloadStatus.done: elif status == DownloadStatus.done:
fileInfo = await self.client.offline_file_info(file_id=task.nodeId) break
node = self.GetNodeById(task.nodeId) waitTime = waitTime * 1.5
if node is not None:
oldFather = self.GetFatherNode(node)
if oldFather is not None:
oldFather.childrenId.remove(node.id)
fileInfo = await self.client.offline_file_info(file_id=task.nodeId)
task.toDirId = fileInfo["parent_id"] task.toDirId = fileInfo["parent_id"]
task.name = fileInfo["name"] task.name = fileInfo["name"]
type = fileInfo["kind"] if fileInfo["kind"].endswith("folder"):
if type.endswith("folder"):
self.nodes[task.nodeId] = DirNode(task.nodeId, task.name, task.toDirId) self.nodes[task.nodeId] = DirNode(task.nodeId, task.name, task.toDirId)
else: else:
self.nodes[task.nodeId] = FileNode(task.nodeId, task.name, task.toDirId) self.nodes[task.nodeId] = FileNode(task.nodeId, task.name, task.toDirId)
father = self.GetNodeById(task.toDirId) father = self.GetNodeById(task.toDirId)
if father.id is not None: if father.id is not None and task.nodeId not in father.childrenId:
father.childrenId.append(task.nodeId) father.childrenId.append(task.nodeId)
task.status = PKTaskStatus.downloading task.status = PikPakTaskStatus.LOCAL_DOWNLOADING
break
waitTime = waitTime * 1.5
async def _pktask_worker(self, task : PkTask): async def _pikpak_task_handler(self, task : PikPakTask):
while task.status not in {PKTaskStatus.done, PKTaskStatus.error, PKTaskStatus.stopped}: while True:
try: if task.status == PikPakTaskStatus.PENDING:
if task.status == PKTaskStatus.pending: await self._pikpak_task_pending(task)
await self._pktask_pending(task) elif task.status == PikPakTaskStatus.REMOTE_DOWNLOADING:
elif task.status == PKTaskStatus.remote_downloading: await self._pikpak_offline_downloading(task)
await self._pktask_offline_downloading(task) elif task.status == PikPakTaskStatus.LOCAL_DOWNLOADING:
elif task.status == PKTaskStatus.downloading: break
task.status = PKTaskStatus.done
else: else:
break break
except asyncio.CancelledError:
task.recoverStatus = task.status
task.status = PKTaskStatus.stopped
except Exception as e:
logging.error(f"task failed, exception occurred: {e}")
task.recoverStatus = task.status
task.status = PKTaskStatus.error
async def _pktask_manager(self): async def _file_download_task_handler(self, task : FileDownloadTask):
while True: pass
await asyncio.sleep(1)
runningTasksNum = 0 def _add_task(self, task : TaskBase):
notRunningTasks = [task for task in self.tasks if task.runningTask is None or task.runningTask.done()] if self.taskQueues.get(task.tag) is None:
if len(self.tasks) - len(notRunningTasks) >= PKFs.MAX_PIKPAK_TASKS: self.taskQueues[task.tag] = []
continue self.taskQueues[task.tag].append(task)
for task in [task for task in notRunningTasks if task.status == PKTaskStatus.pending]:
task.runningTask = asyncio.create_task(self._pktask_worker(task)) async def StopTask(self, task : TaskBase):
runningTasksNum += 1 pass
if runningTasksNum >= PKFs.MAX_PIKPAK_TASKS:
async def ResumeTask(self, task : TaskBase):
pass
async def RetryTask(self, taskId : str):
if PikPakTask.TAG not in self.taskQueues:
return
for task in self.taskQueues[PikPakTask.TAG]:
if task.id == taskId and task._status == TaskStatus.ERROR:
task._status = TaskStatus.PENDING
break
elif task.id == taskId:
break break
def Start(self):
return asyncio.create_task(TaskManager(self.taskQueues))
def __init__(self, loginCachePath : str = None, proxy : str = None, rootId = None): def __init__(self, loginCachePath : str = None, proxy : str = None, rootId = None):
self.nodes : Dict[str, FsNode] = {} self.nodes : Dict[str, FsNode] = {}
self.root = DirNode(rootId, "", None) self.root = DirNode(rootId, "", None)
self.currentLocation = self.root self.currentLocation = self.root
self.tasks : list[PkTask] = [] self.taskQueues : Dict[str, list[TaskBase]] = {}
self.loginCachePath = loginCachePath self.loginCachePath = loginCachePath
self.proxyConfig = proxy self.proxyConfig = proxy
self.client : PikPakApi = None self.client : PikPakApi = None
self._try_login_from_cache() self._try_login_from_cache()
def _init_client_by_token(self, token : PkToken): def _init_client_by_token(self, token : PikPakToken):
self._init_client_by_username_and_password(token.username, token.password) self._init_client_by_username_and_password(token.username, token.password)
self.client.access_token = token.access_token self.client.access_token = token.access_token
self.client.refresh_token = token.refresh_token self.client.refresh_token = token.refresh_token
@ -220,7 +239,7 @@ class PKFs:
return return
with open(self.loginCachePath, 'r', encoding='utf-8') as file: with open(self.loginCachePath, 'r', encoding='utf-8') as file:
content = file.read() content = file.read()
token = PkToken.from_json(content) token = PikPakToken.from_json(content)
self._init_client_by_token(token) self._init_client_by_token(token)
logging.info("successfully load login info from cache") logging.info("successfully load login info from cache")
@ -228,7 +247,7 @@ class PKFs:
if self.loginCachePath is None: if self.loginCachePath is None:
return return
with open(self.loginCachePath, 'w', encoding='utf-8') as file: with open(self.loginCachePath, 'w', encoding='utf-8') as file:
token = PkToken(self.client.username, self.client.password, self.client.access_token, self.client.refresh_token, self.client.user_id) token = PikPakToken(self.client.username, self.client.password, self.client.access_token, self.client.refresh_token, self.client.user_id)
file.write(token.to_json()) file.write(token.to_json())
logging.info("successfully dump login info to cache") logging.info("successfully dump login info to cache")
@ -243,20 +262,6 @@ class PKFs:
return True return True
return False return False
async def StopTask(self, taskId : int):
pass
async def ResumeTask(self, taskId : int):
pass
async def RetryTask(self, taskId : int):
task = next((t for t in self.tasks if t.id == taskId), None)
if task and task.status == PKTaskStatus.error:
task.status = PKTaskStatus.pending
def Start(self):
asyncio.create_task(self._pktask_manager())
def GetNodeById(self, id : str) -> FsNode: def GetNodeById(self, id : str) -> FsNode:
if id == self.root.id: if id == self.root.id:
return self.root return self.root
@ -382,15 +387,19 @@ class PKFs:
node.childrenId.append(id) node.childrenId.append(id)
return newDir return newDir
async def Download(self, url : str, dirNode : DirNode) -> PkTask : async def Download(self, url : str, dirNode : DirNode) -> PikPakTask :
task = PkTask(url, dirNode.id) task = PikPakTask(url, dirNode.id)
self.tasks.append(task) task.handler = self._pikpak_task_handler
self._add_task(task)
return task return task
async def QueryTasks(self, filterByStatus : PKTaskStatus = None) -> list[PkTask]: async def QueryPikPakTasks(self, filterStatus : TaskStatus = None) -> list[PikPakTask]:
if filterByStatus is None: if PikPakTask.TAG not in self.taskQueues:
return self.tasks return []
return [task for task in self.tasks if task.status == filterByStatus] taskQueue = self.taskQueues[PikPakTask.TAG]
if filterStatus is None:
return taskQueue
return [task for task in taskQueue if task._status == filterStatus]
async def Delete(self, nodes : list[FsNode]) -> None: async def Delete(self, nodes : list[FsNode]) -> None:
nodeIds = [node.id for node in nodes] nodeIds = [node.id for node in nodes]

View File

@ -12,9 +12,11 @@ Todo:
- [ ] 实现本地下载队列(多文件,文件夹) - [ ] 实现本地下载队列(多文件,文件夹)
- [ ] 实现任务暂停、继续、恢复 - [ ] 实现任务暂停、继续、恢复
- [ ] 持久化数据 - [ ] 持久化数据
- [ ] 添加测试用例
- [ ] 完全类型化
### 记录 ### 协议结构
1. offline_download 1. offline_download
```json ```json
{ {

16
utils.py Normal file
View File

@ -0,0 +1,16 @@
class PathWalker():
def __init__(self, path : str, sep : str = "/"):
self._path_spots : list[str] = []
if not path.startswith(sep):
self._path_spots.append(".")
path_spots : list[str] = path.split(sep)
self._path_spots.extend(path_spots)
def IsAbsolute(self) -> bool:
return len(self._path_spots) == 0 or self._path_spots[0] != "."
def AppendSpot(self, spot):
self._path_spots.append(spot)
def Walk(self) -> list[str]:
return self._path_spots