优化变量

This commit is contained in:
limil 2024-10-29 01:20:57 +08:00
parent 9088677f94
commit 115bf2a3e2
2 changed files with 156 additions and 161 deletions

168
main.py
View File

@ -4,7 +4,7 @@ from functools import wraps
import logging import logging
import threading import threading
import colorlog import colorlog
from pikpakFs import VirtFsNode, DirNode, FileNode, PKVirtFs from pikpakFs import VirtFsNode, DirNode, FileNode, PKVirtFs, IsDir, IsFile
import os import os
def RunSyncInLoop(loop): def RunSyncInLoop(loop):
@ -30,7 +30,6 @@ def ProvideDecoratorSelfArgs(decorator, argsProvider):
namespace = args[0] namespace = args[0]
return decorator(argsProvider(namespace))(func)(*args, **kwargs) return decorator(argsProvider(namespace))(func)(*args, **kwargs)
return decorated return decorated
return wrapper return wrapper
class PikpakConsole(cmd2.Cmd): class PikpakConsole(cmd2.Cmd):
@ -39,7 +38,7 @@ class PikpakConsole(cmd2.Cmd):
RunSync = ProvideDecoratorSelfArgs(RunSyncInLoop, LoopProvider) RunSync = ProvideDecoratorSelfArgs(RunSyncInLoop, LoopProvider)
def _SetupLogging(self): def _setup_logging(self):
formatter = 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',
@ -52,20 +51,12 @@ class PikpakConsole(cmd2.Cmd):
'CRITICAL': 'red,bg_white', 'CRITICAL': 'red,bg_white',
} }
) )
handler = logging.StreamHandler() handler = logging.StreamHandler()
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger = logging.getLogger() logger = logging.getLogger()
logger.addHandler(handler) logger.addHandler(handler)
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
def __init__(self):
super().__init__()
self._SetupLogging()
self.client = PKVirtFs("token.json", proxy="http://127.0.0.1:7897")
def IOWorker(self, loop): def IOWorker(self, loop):
self.terminal_lock.acquire() # 我看cmdloop是这么做的所以我也这么做 self.terminal_lock.acquire() # 我看cmdloop是这么做的所以我也这么做
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
@ -86,6 +77,66 @@ class PikpakConsole(cmd2.Cmd):
future = asyncio.run_coroutine_threadsafe(PrintOuput(output), self.ioLoop) future = asyncio.run_coroutine_threadsafe(PrintOuput(output), self.ioLoop)
await asyncio.wrap_future(future) await asyncio.wrap_future(future)
def ParserProvider(self):
return cmd2.Cmd2ArgumentParser()
def AddPathParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("path", help="path", default="", nargs="?", type=RunSyncInLoop(self.loop)(self.client.PathToNode))
return parser
return PathParserProvider
def AddFatherAndSonParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("path", help="path", default="", nargs="?", type=RunSyncInLoop(self.loop)(self.client.PathToFatherNodeAndNodeName))
return parser
return PathParserProvider
def AddUrlParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("url", help="url")
return parser
return PathParserProvider
def AddUsernamePasswordParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("username", help="username", nargs="?")
parser.add_argument("password", help="password", nargs="?")
return parser
return PathParserProvider
async def PathCompleter(self, text, line, begidx, endidx, filterFiles):
father, sonName = await self.client.PathToFatherNodeAndNodeName(text)
if not IsDir(father):
return []
matches = []
matchesNode = []
for childId in father.childrenId:
child = self.client.GetNodeById(childId)
if filterFiles and IsFile(child):
continue
if child.name.startswith(sonName):
self.display_matches.append(child.name)
if sonName == "":
matches.append(text + child.name)
elif text.endswith(sonName):
matches.append(text[:text.rfind(sonName)] + child.name)
matchesNode.append(child)
if len(matchesNode) == 1 and IsDir(matchesNode[0]):
matches[0] += "/"
self.allow_appended_space = False
self.allow_closing_quote = False
return matches
def __init__(self):
super().__init__()
self._setup_logging()
self.client = PKVirtFs("token.json", proxy="http://127.0.0.1:7897")
async def Run(self): async def Run(self):
# 1. 设置忽略SIGINT # 1. 设置忽略SIGINT
import signal import signal
@ -121,6 +172,8 @@ class PikpakConsole(cmd2.Cmd):
self.ioLoop.call_soon_threadsafe(self.ioLoop.stop) self.ioLoop.call_soon_threadsafe(self.ioLoop.stop)
thread.join() thread.join()
# commands #
def do_debug(self, args): def do_debug(self, args):
""" """
Enable debug mode Enable debug mode
@ -134,57 +187,15 @@ class PikpakConsole(cmd2.Cmd):
""" """
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
logging.info("Debug mode disabled") logging.info("Debug mode disabled")
login_parser = cmd2.Cmd2ArgumentParser()
login_parser.add_argument("username", help="username", nargs="?")
login_parser.add_argument("password", help="password", nargs="?")
@RunSync @RunSync
@cmd2.with_argparser(login_parser) @ProvideDecoratorSelfArgs(cmd2.with_argparser, AddUsernamePasswordParser(ParserProvider))
async def do_login(self, args): async def do_login(self, args):
""" """
Login to pikpak Login to pikpak
""" """
await self.client.Login(args.username, args.password) await self.client.Login(args.username, args.password)
await self.aoutput("Logged in successfully") await self.aoutput("Logged in successfully")
def ParserProvider(self):
return cmd2.Cmd2ArgumentParser()
def AddPathParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("path", help="path", default="", nargs="?", type=RunSyncInLoop(self.loop)(self.client.PathToNode))
return parser
return PathParserProvider
async def PathCompleter(self, text, line, begidx, endidx, filterFiles):
father, sonName = await self.client.PathToFatherNodeAndNodeName(text)
fatherDir = self.client.ToDir(father)
if fatherDir is None:
return []
matches = []
matchesNode = []
for childId in fatherDir.childrenId:
node = self.client.nodes[childId]
if filterFiles and isinstance(node, FileNode):
continue
if node.name.startswith(sonName):
self.display_matches.append(node.name)
if sonName == "":
matches.append(text + node.name)
elif text.endswith(sonName):
matches.append(text[:text.rfind(sonName)] + node.name)
matchesNode.append(node)
if len(matchesNode) == 1 and self.client.ToDir(matchesNode[0]) is not None:
matches[0] += "/"
self.allow_appended_space = False
self.allow_closing_quote = False
return matches
@RunSync @RunSync
async def complete_ls(self, text, line, begidx, endidx): async def complete_ls(self, text, line, begidx, endidx):
@ -196,13 +207,14 @@ class PikpakConsole(cmd2.Cmd):
""" """
List files in a directory List files in a directory
""" """
if isinstance(args.path, DirNode): node = args.path
for childId in args.path.childrenId: if IsDir(node):
node = self.client.nodes[childId] for childId in node.childrenId:
await self.aoutput(node.name) child = self.client.GetNodeById(childId)
elif isinstance(args.path, FileNode): await self.aoutput(child.name)
await self.client.UpdateDownloadUrl(args.path) elif IsFile(node):
await self.aoutput(f"{args.path.name}: {args.path.url}") await self.client.Refresh(node)
await self.aoutput(f"{node.name}: {node.url}")
else: else:
await self.aoutput("Invalid path") await self.aoutput("Invalid path")
@ -216,10 +228,11 @@ class PikpakConsole(cmd2.Cmd):
""" """
Change directory Change directory
""" """
if self.client.ToDir(args.path) is None: node = args.path
if not IsDir(node):
await self.aoutput("Invalid directory") await self.aoutput("Invalid directory")
return return
self.client.currentLocation = args.path self.client.currentLocation = node
@RunSync @RunSync
async def do_cwd(self, args): async def do_cwd(self, args):
@ -250,13 +263,6 @@ class PikpakConsole(cmd2.Cmd):
async def complete_mkdir(self, text, line, begidx, endidx): async def complete_mkdir(self, text, line, begidx, endidx):
return await self.PathCompleter(text, line, begidx, endidx, filterFiles = True) return await self.PathCompleter(text, line, begidx, endidx, filterFiles = True)
def AddFatherAndSonParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("path", help="path", default="", nargs="?", type=RunSyncInLoop(self.loop)(self.client.PathToFatherNodeAndNodeName))
return parser
return PathParserProvider
@RunSync @RunSync
@ProvideDecoratorSelfArgs(cmd2.with_argparser, AddFatherAndSonParser(ParserProvider)) @ProvideDecoratorSelfArgs(cmd2.with_argparser, AddFatherAndSonParser(ParserProvider))
async def do_mkdir(self, args): async def do_mkdir(self, args):
@ -264,23 +270,14 @@ class PikpakConsole(cmd2.Cmd):
Create a directory Create a directory
""" """
father, sonName = args.path father, sonName = args.path
fatherDir = self.client.ToDir(father) if not IsDir(father) or sonName == "" or sonName == None:
if fatherDir == None or sonName == "" or sonName == None:
await self.aoutput("Invalid path") await self.aoutput("Invalid path")
return return
childNode = self.client.FindChildInDirByName(fatherDir, sonName) child = self.client.FindChildInDirByName(father, sonName)
if childNode is not None: if child is not None:
await self.aoutput("Path already exists") await self.aoutput("Path already exists")
return return
for i in range(1, 10): await self.client.MakeDir(father, sonName)
await self.client.MakeDir(fatherDir, sonName + str(i))
def AddUrlParser(parserProvider):
def PathParserProvider(self):
parser = parserProvider(self)
parser.add_argument("url", help="url")
return parser
return PathParserProvider
@RunSync @RunSync
@ProvideDecoratorSelfArgs(cmd2.with_argparser, AddPathParser(AddUrlParser(ParserProvider))) @ProvideDecoratorSelfArgs(cmd2.with_argparser, AddPathParser(AddUrlParser(ParserProvider)))
@ -288,10 +285,11 @@ class PikpakConsole(cmd2.Cmd):
""" """
Download a file Download a file
""" """
if self.client.ToDir(args.path) is None: node = args.path
if not IsDir(node):
await self.aoutput("Invalid directory") await self.aoutput("Invalid directory")
return return
await self.client.Download(args.url, args.path) await self.client.Download(args.url, node)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -42,6 +42,12 @@ class FileNode(VirtFsNode):
super().__init__(id, name, fatherId) super().__init__(id, name, fatherId)
self.url : str = None self.url : str = None
def IsDir(node : VirtFsNode) -> bool:
return isinstance(node, DirNode)
def IsFile(node : VirtFsNode) -> bool:
return isinstance(node, FileNode)
class PikpakToken: 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
@ -67,16 +73,16 @@ class PKVirtFs:
self.loginCachePath = loginCachePath self.loginCachePath = loginCachePath
self.proxyConfig = proxy self.proxyConfig = proxy
self.client : PikPakApi = None self.client : PikPakApi = None
self.__TryLoginFromCache() self._try_login_from_cache()
def __InitClientByToken(self, token : PikpakToken): def _init_client_by_token(self, token : PikpakToken):
self.__InitClientByUsernamePassword(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
self.client.user_id = token.user_id self.client.user_id = token.user_id
self.client.encode_token() self.client.encode_token()
def __InitClientByUsernamePassword(self, username : str, password : str): def _init_client_by_username_and_password(self, username : str, password : str):
httpx_client_args = None httpx_client_args = None
if self.proxyConfig != None: if self.proxyConfig != None:
httpx_client_args = { httpx_client_args = {
@ -89,7 +95,7 @@ class PKVirtFs:
password = password, password = password,
httpx_client_args=httpx_client_args) httpx_client_args=httpx_client_args)
def __TryLoginFromCache(self): def _try_login_from_cache(self):
if self.loginCachePath is None: if self.loginCachePath is None:
return return
if not os.path.exists(self.loginCachePath): if not os.path.exists(self.loginCachePath):
@ -97,10 +103,10 @@ class PKVirtFs:
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 = PikpakToken.from_json(content) token = PikpakToken.from_json(content)
self.__InitClientByToken(token) self._init_client_by_token(token)
logging.info("successfully load login info from cache") logging.info("successfully load login info from cache")
def __DumpLoginInfo(self): def _dump_login_info(self):
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:
@ -108,31 +114,26 @@ class PKVirtFs:
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")
def __IsAncestorsOf(self, nodeA : VirtFsNode, nodeB : VirtFsNode) -> bool: def _is_ancestors_of(self, nodeA : VirtFsNode, nodeB : VirtFsNode) -> bool:
if nodeB is nodeA: if nodeB is nodeA:
return False return False
if nodeA is self.root: if nodeA is self.root:
return True return True
while nodeB.fatherId != None: while nodeB.fatherId != self.root.id:
nodeB = self.nodes[nodeB.fatherId] nodeB = self.nodes[nodeB.fatherId]
if nodeB is nodeA: if nodeB is nodeA:
return True return True
return False return False
def ToDir(self, node : VirtFsNode) -> DirNode: def GetNodeById(self, id : str) -> VirtFsNode:
if isinstance(node, DirNode): if id == self.root.id:
return node return self.root
return None return self.nodes[id]
def ToFile(self, node : VirtFsNode) -> FileNode:
if isinstance(node, FileNode):
return node
return None
def GetFatherNode(self, node : VirtFsNode) -> VirtFsNode: def GetFatherNode(self, node : VirtFsNode) -> VirtFsNode:
if node is self.root or node.fatherId == self.root.id: if node is self.root:
return self.root return self.root
return self.nodes[node.fatherId] return self.GetNodeById(node.fatherId)
def FindChildInDirByName(self, dir : DirNode, name : str): def FindChildInDirByName(self, dir : DirNode, name : str):
if dir is self.root and name == "": if dir is self.root and name == "":
@ -143,39 +144,46 @@ class PKVirtFs:
return node return node
return None return None
async def RefreshDirectory(self, dirNode : DirNode): async def Refresh(self, node : VirtFsNode):
next_page_token = None if node.lastUpdate != None:
nodes = [] return
while True:
dirInfo = await self.client.file_list(parent_id = dirNode.id, next_page_token=next_page_token)
next_page_token = dirInfo["next_page_token"]
currentPageNodes = dirInfo["files"]
nodes.extend(currentPageNodes)
if next_page_token is None or next_page_token == "":
break
dirNode.childrenId.clear()
for node in nodes: if IsDir(node):
child : VirtFsNode = None next_page_token = None
id = node["id"] childrenInfo = []
name = node["name"] while True:
fatherId = dirNode.id dirInfo = await self.client.file_list(parent_id = node.id, next_page_token=next_page_token)
if id in self.nodes: next_page_token = dirInfo["next_page_token"]
child = self.nodes[id] currentPageNodes = dirInfo["files"]
else: childrenInfo.extend(currentPageNodes)
child = DirNode(id, name, fatherId) if node["kind"].endswith("folder") else FileNode(id, name, fatherId) if next_page_token is None or next_page_token == "":
self.nodes[id] = child break
child.name = name node.childrenId.clear()
child.fatherId = fatherId
dirNode.childrenId.append(id) for childInfo in childrenInfo:
dirNode.lastUpdate = datetime.now() child : VirtFsNode = None
id = childInfo["id"]
name = childInfo["name"]
fatherId = node.id
if id in self.nodes:
child = self.nodes[id]
else:
child = DirNode(id, name, fatherId) if childInfo["kind"].endswith("folder") else FileNode(id, name, fatherId)
self.nodes[id] = child
child.name = name
child.fatherId = fatherId
node.childrenId.append(id)
elif IsFile(node):
result = await self.client.get_download_url(node.id)
node.url = result["web_content_link"]
node.lastUpdate = datetime.now()
async def PathToNode(self, pathStr : str) -> VirtFsNode: async def PathToNode(self, pathStr : str) -> VirtFsNode:
father, sonName = await self.PathToFatherNodeAndNodeName(pathStr) father, sonName = await self.PathToFatherNodeAndNodeName(pathStr)
if sonName == "": if sonName == "":
return father return father
fatherDir = self.ToDir(father) if not IsDir(father):
if fatherDir is None:
return None return None
return self.FindChildInDirByName(father, sonName) return self.FindChildInDirByName(father, sonName)
@ -193,21 +201,18 @@ class PKVirtFs:
current = self.GetFatherNode(current) current = self.GetFatherNode(current)
continue continue
father = current father = current
currentDir = self.ToDir(current) if not IsDir(current):
if currentDir is None:
current = None current = None
continue continue
if currentDir.lastUpdate is None: await self.Refresh(current)
await self.RefreshDirectory(currentDir)
if spot == ".": if spot == ".":
continue continue
sonName = spot sonName = spot
current = self.FindChildInDirByName(currentDir, spot) current = self.FindChildInDirByName(current, spot)
if current != None: if current != None:
currentDir = self.ToDir(current) if IsDir(current):
if currentDir != None and currentDir.lastUpdate is None: await self.Refresh(current)
await self.RefreshDirectory(currentDir)
father = self.GetFatherNode(current) father = self.GetFatherNode(current)
sonName = current.name sonName = current.name
@ -218,19 +223,13 @@ class PKVirtFs:
return "/" return "/"
spots : list[str] = [] spots : list[str] = []
current = node current = node
while current.id != None: while current is not self.root:
spots.append(current.name) spots.append(current.name)
if current.fatherId is None: current = self.GetFatherNode(current)
break
current = self.nodes[current.fatherId]
spots.append("") spots.append("")
return "/".join(reversed(spots)) return "/".join(reversed(spots))
async def MakeDir(self, node : DirNode, name : str) -> DirNode: # commands #
await self.client.create_folder(name, node.id)
await self.RefreshDirectory(node)
return self.ToDir(self.FindChildInDirByName(node, name))
async def Login(self, username : str = None, password : str = None) -> None: async def Login(self, username : str = None, password : str = None) -> None:
if self.client != None and username is None and password is None: if self.client != None and username is None and password is None:
username = self.client.username username = self.client.username
@ -239,28 +238,26 @@ class PKVirtFs:
if username == None and password == None: if username == None and password == None:
raise Exception("Username and password are required") raise Exception("Username and password are required")
self.__InitClientByUsernamePassword(username, password) self._init_client_by_username_and_password(username, password)
await self.client.login() await self.client.login()
self.__DumpLoginInfo() self._dump_login_info()
async def UpdateDownloadUrl(self, file : FileNode) -> None: async def MakeDir(self, node : DirNode, name : str) -> DirNode:
result = await self.client.get_download_url(file.id) await self.client.create_folder(name, node.id)
file.url = result["web_content_link"] await self.Refresh(node)
return self.FindChildInDirByName(node, name)
async def Download(self, url : str, dirNode : DirNode) -> None : async def Download(self, url : str, dirNode : DirNode) -> None :
# 默认创建在当前目录下 # 默认创建在当前目录下
# todo: 完善离线下载task相关 # todo: 完善离线下载task相关
if dirNode is None:
dirNode = self.currentLocation
await self.client.offline_download(url, dirNode.id) await self.client.offline_download(url, dirNode.id)
async def Delete(self, node : VirtFsNode) -> None: async def Delete(self, node : VirtFsNode) -> None:
father = self.GetFatherNode(node) father = self.GetFatherNode(node)
fatherDir = self.ToDir(father) if not IsDir(father):
if fatherDir is None:
raise Exception('Failed to locate') raise Exception('Failed to locate')
if self.currentLocation is node or self.__IsAncestorsOf(node, self.currentLocation): if self.currentLocation is node or self._is_ancestors_of(node, self.currentLocation):
raise Exception('Delete self or ancestor is not allowed') raise Exception('Delete self or ancestor is not allowed')
await self.client.delete_to_trash([node.id]) await self.client.delete_to_trash([node.id])
await self.RefreshDirectory(fatherDir) await self.Refresh(father)