Example #1
0
 def get_dir_list(self, folder_id=-1) -> FolderList:
     """获取子文件夹列表"""
     folder_list = FolderList()
     para = {
         'item': 'files',
         'action': 'index',
         'folder_node': 1,
         'folder_id': folder_id
     }
     html = self._get(self._mydisk_url, params=para)
     if not html:
         return folder_list
     info = re.findall(
         r'&nbsp;(.+?)</a>&nbsp;.+"folk(\d+)"(.*?)>.+#BBBBBB">\[?(.*?)\.*\]?</font>',
         html.text)
     for folder_name, fid, pwd_flag, desc in info:
         folder_list.append(
             Folder(
                 id=int(fid),
                 name=folder_name.replace('&amp;', '&'),  # 修复网页中的 &amp; 为 &
                 has_pwd=True if pwd_flag else
                 False,  # 有密码时 pwd_flag 值为 style="display:initial"
                 desc=desc  # 文件夹描述信息
             ))
     return folder_list
Example #2
0
 def get_rec_dir_list(self) -> FolderList:
     """获取回收站文件夹列表"""
     # 回收站中文件(夹)名只能显示前 17 个中文字符或者 34 个英文字符,如果这些字符相同,则在文件(夹)名后添加 (序号) ,以便区分
     html = self._get(self._mydisk_url,
                      params={
                          'item': 'recycle',
                          'action': 'files'
                      })
     if not html:
         return FolderList()
     dirs = re.findall(
         r'folder_id=(\d+).+?>&nbsp;(.+?)\.{0,3}</a>.*\n+.*<td.+?>(.+?)</td>.*\n.*<td.+?>(.+?)</td>',
         html.text)
     all_dir_list = FolderList()  # 文件夹信息列表
     dir_name_list = []  # 文件夹名列表d
     counter = 1  # 重复计数器
     for fid, name, size, time in dirs:
         if name in dir_name_list:  # 文件夹名前 17 个中文或 34 个英文重复
             counter += 1
             name = f'{name}({counter})'
         else:
             counter = 1
         dir_name_list.append(name)
         all_dir_list.append(RecFolder(name, int(fid), size, time, None))
     return all_dir_list
Example #3
0
 def init_variables(self):
     self._disk = LanZouCloud()
     self._config_file = "./config.pkl"
     self._folder_list = {}
     self._file_list = {}
     self._path_list = FolderList()
     self._path_list_old = FolderList()
     self._locs = {}
     self._parent_id = -1  # --> ..
     self._work_name = ""  # share disk rec, not use now
     self._work_id = -1  # disk folder id
     self._old_work_id = self._work_id  # 用于上传完成后判断是否需要更新disk界面
     self.load_settings()
Example #4
0
 def __init__(self):
     self._prompt = '> '
     self._disk = LanZouCloud()
     self._dir_list = FolderList()
     self._file_list = FileList()
     self._path_list = FolderList()
     self._parent_id = -1
     self._parent_name = ''
     self._work_name = ''
     self._work_id = -1
     self._last_work_id = -1
     self._reader_mode = config.reader_mode
     self._disk.set_max_size(config.max_size)
     self._is_login = False
Example #5
0
 def __init__(self):
     self._prompt = '> '
     self._disk = LanZouCloud()
     self._task_mgr = global_task_mgr
     self._dir_list = FolderList()
     self._file_list = FileList()
     self._path_list = FolderList()
     self._parent_id = -1
     self._parent_name = ''
     self._work_name = ''
     self._work_id = -1
     self._last_work_id = -1
     self._reader_mode = config.reader_mode
     self._disk.set_max_size(config.max_size)
     self._disk.set_captcha_handler(captcha_handler)
Example #6
0
    def get_move_paths(self) -> List[FolderList]:
        """获取所有文件夹的绝对路径(耗时长)"""
        result = []
        root = FolderList()
        root.append(FolderId('LanZouCloud', -1))
        result.append(root)
        resp = self._post(self._doupload_url, data={"task": 19, "file_id": -1})
        if not resp or resp.json()['zt'] != 1:  # 获取失败或者网络异常
            return result

        ex = ThreadPoolExecutor()  # 线程数 min(32, os.cpu_count() + 4)
        id_list = [int(folder['folder_id']) for folder in resp.json()['info']]
        task_list = [ex.submit(self.get_full_path, fid) for fid in id_list]
        for task in as_completed(task_list):
            result.append(task.result())
        return sorted(result)
Example #7
0
 def get_rec_all(self):
     """获取整理后回收站的所有信息"""
     root_files = self.get_rec_file_list()  # 回收站根目录文件列表
     folder_list = FolderList()  # 保存整理后的文件夹列表
     for folder in self.get_rec_dir_list():  # 遍历所有子文件夹
         this_folder = RecFolder(folder.name, folder.id, folder.size, folder.time, FileList())
         for file in self.get_rec_file_list(folder.id):  # 文件夹内的文件属性: name,id,type,size
             if root_files.find_by_id(file.id):  # 根目录存在同名文件
                 file_time = root_files.pop_by_id(file.id).time  # 从根目录删除, time 信息用来补充文件夹中的文件
                 file = file._replace(time=file_time)  # 不能直接更新 namedtuple, 需要 _replace
                 this_folder.files.append(file)
             else:  # 根目录没有同名文件(用户手动删了),文件还在文件夹中,只是根目录不显示,time 信息无法补全了
                 file = file._replace(time=folder.time)  # 那就设置时间为文件夹的创建时间
                 this_folder.files.append(file)
         folder_list.append(this_folder)
     return root_files, folder_list
Example #8
0
 def __init__(self):
     self._prompt = '> '
     self._disk = LanZouCloud()
     # self._disk.ignore_limits()
     self._task_mgr = global_task_mgr
     self._dir_list = FolderList()
     self._file_list = FileList()
     self._path_list = FolderList()
     self._parent_id = -1
     self._parent_name = ''
     self._work_name = ''
     self._work_id = -1
     self._last_work_id = -1
     self._reader_mode = config.reader_mode
     self._default_dir_pwd = config.default_dir_pwd
     self._disk.set_max_size(config.max_size)
     self._disk.set_upload_delay(config.upload_delay)
Example #9
0
 def get_full_path(self, folder_id=-1) -> FolderList:
     """获取文件夹完整路径"""
     path_list = FolderList()
     path_list.append(FolderId('LanZouCloud', -1))
     html = self._get(self._mydisk_url, params={'item': 'files', 'action': 'index', 'folder_id': folder_id})
     if not html:
         return path_list
     html = remove_notes(html.text)
     path = re.findall(r'&raquo;&nbsp;.+?folder_id=(\d+)">.+?&nbsp;(.+?)</a>', html)
     for fid, name in path:
         path_list.append(FolderId(name, int(fid)))
     # 获取当前文件夹名称
     if folder_id != -1:
         current_folder = re.search(r'align="(top|absmiddle)" />&nbsp;(.+?)\s<(span|font)', html).group(2).replace(
             '&amp;', '&')
         path_list.append(FolderId(current_folder, folder_id))
     return path_list
Example #10
0
    def logout(self):
        """注销"""
        clear_screen()
        self._prompt = '> '
        self._disk.logout()
        self._file_list.clear()
        self._dir_list.clear()
        self._path_list = FolderList()
        self._parent_id = -1
        self._work_id = -1
        self._last_work_id = -1
        self._parent_name = ''
        self._work_name = ''

        config.cookie = None
Example #11
0
 def get_move_folders(self) -> FolderList:
     """获取全部文件夹 id-name 列表,用于移动文件至新的文件夹"""
     # 这里 file_id 可以为任意值,不会对结果产生影响
     result = FolderList()
     result.append(FolderId(name='LanZouCloud', id=-1))
     resp = self._post(self._doupload_url, data={"task": 19, "file_id": -1})
     if not resp or resp.json()['zt'] != 1:  # 获取失败或者网络异常
         return result
     for folder in resp.json()['info']:
         folder_id, folder_name = int(folder['folder_id']), folder['folder_name']
         result.append(FolderId(folder_name, folder_id))
     return result
Example #12
0
class Commander:
    """蓝奏网盘命令行"""
    def __init__(self):
        self._prompt = '> '
        self._disk = LanZouCloud()
        self._dir_list = FolderList()
        self._file_list = FileList()
        self._path_list = FolderList()
        self._parent_id = -1
        self._parent_name = ''
        self._work_name = ''
        self._work_id = -1
        self._last_work_id = -1
        self._reader_mode = config.reader_mode
        self._disk.set_max_size(config.max_size)
        self._is_login = False

    @staticmethod
    def clear():
        clear_screen()

    @staticmethod
    def help():
        print_help()

    @staticmethod
    def update():
        check_update()

    def rmode(self):
        """适用于屏幕阅读器用户的显示方式"""
        choice = input("以适宜屏幕阅读器的方式显示(y): ")
        if choice and choice.lower() == 'y':
            config.reader_mode = True
            self._reader_mode = True
            info("已启用 Reader Mode")
        else:
            config.reader_mode = False
            self._reader_mode = False
            info("已关闭 Reader Mode")

    def cdrec(self):
        """进入回收站模式"""
        rec = Recovery(self._disk)
        rec.run()
        self.refresh()

    def refresh(self, dir_id=None):
        """刷新当前文件夹和路径信息"""
        dir_id = self._work_id if dir_id is None else dir_id
        self._file_list = self._disk.get_file_list(dir_id)
        self._dir_list = self._disk.get_dir_list(dir_id)
        self._path_list = self._disk.get_full_path(dir_id)
        self._prompt = '/'.join(self._path_list.all_name) + ' > '
        self._last_work_id = self._work_id
        self._work_name = self._path_list[-1].name
        self._work_id = self._path_list[-1].id
        if dir_id != -1:  # 如果存在上级路径
            self._parent_name = self._path_list[-2].name
            self._parent_id = self._path_list[-2].id

    def login(self, username=None, password=None):
        """登录网盘"""
        if not config.cookie or self._disk.login_by_cookie(
                config.cookie) != LanZouCloud.SUCCESS:
            if username is None:
                username = input('输入用户名:')
            if password is None:
                password = getpass('输入密码:')
            code = self._disk.login(username, password)
            if code == LanZouCloud.NETWORK_ERROR:
                error("登录失败,网络连接异常")
                return self
            elif code == LanZouCloud.FAILED:
                error('登录失败,用户名或密码错误 :(')
                return self
            # 登录成功保存用户 cookie
            config.cookie = self._disk.get_cookie()
        self.refresh()
        self._is_login = True
        print("auth success!\n")
        return self

    def clogin(self):
        """使用 cookie 登录"""
        open_new_tab('https://pc.woozooo.com/')
        info("请设置 Cookie 内容:")
        ylogin = input("ylogin="******"phpdisk_info=")
        if not ylogin or not disk_info:
            error("请输入正确的 Cookie 信息")
            return None
        cookie = {"ylogin": str(ylogin), "phpdisk_info": disk_info}
        if self._disk.login_by_cookie(cookie) == LanZouCloud.SUCCESS:
            config.cookie = cookie
            self.refresh()
            self._is_login = True
        else:
            error("登录失败,请检查 Cookie 是否正确")

    def logout(self):
        """注销"""
        clear_screen()
        self._prompt = '> '
        self._disk.logout()
        self._file_list.clear()
        self._dir_list.clear()
        self._path_list = FolderList()
        self._parent_id = -1
        self._work_id = -1
        self._last_work_id = -1
        self._parent_name = ''
        self._work_name = ''
        self_is_login = False

        config.cookie = None

    def ls(self):
        """列出文件(夹)"""
        if self._reader_mode:  # 方便屏幕阅读器阅读
            for folder in self._dir_list:
                print(f"{folder.name}/  {folder.desc}")
            for file in self._file_list:
                print(f"{file.name}  大小:{file.size}  上传时间:{file.time}")
        else:  # 普通用户显示方式
            for folder in self._dir_list:
                pwd_str = '✦' if folder.has_pwd else '✧'
                print("#{0:<12}{1:<4}{2}{3}/".format(
                    folder.id, pwd_str, text_align(folder.desc, 24),
                    folder.name))
            for file in self._file_list:
                pwd_str = '✦' if file.has_pwd else '✧'
                print("#{0:<12}{1:<4}{2:<14}{3:<10}{4}".format(
                    file.id, pwd_str, file.time, file.size, file.name))

    def cd(self, dir_name):
        """切换工作目录"""
        if not dir_name:
            info('cd .. 返回上级路径, cd - 返回上次路径, cd / 返回根目录')
        elif dir_name == '..':
            self.refresh(self._parent_id)
        elif dir_name == '/':
            self.refresh(-1)
        elif dir_name == '-':
            self.refresh(self._last_work_id)
        elif dir_name == '.':
            pass
        elif folder := self._dir_list.find_by_name(dir_name):
            self.refresh(folder.id)
        else:
Example #13
0
class Commander:
    """蓝奏网盘命令行"""
    def __init__(self):
        self._prompt = '> '
        self._disk = LanZouCloud()
        # self._disk.ignore_limits()
        self._task_mgr = global_task_mgr
        self._dir_list = FolderList()
        self._file_list = FileList()
        self._path_list = FolderList()
        self._parent_id = -1
        self._parent_name = ''
        self._work_name = ''
        self._work_id = -1
        self._last_work_id = -1
        self._reader_mode = config.reader_mode
        self._default_dir_pwd = config.default_dir_pwd
        self._disk.set_max_size(config.max_size)
        self._disk.set_upload_delay(config.upload_delay)

    @staticmethod
    def clear():
        clear_screen()

    @staticmethod
    def help():
        print_help()

    @staticmethod
    def update():
        check_update()

    def bye(self):
        if self._task_mgr.has_alive_task():
            info(f"有任务在后台运行, 退出请直接关闭窗口")
        else:
            exit_cmd(0)

    def rmode(self):
        """适用于屏幕阅读器用户的显示方式"""
        choice = input("以适宜屏幕阅读器的方式显示(y): ")
        if choice and choice.lower() == 'y':
            config.reader_mode = True
            self._reader_mode = True
            info("已启用 Reader Mode")
        else:
            config.reader_mode = False
            self._reader_mode = False
            info("已关闭 Reader Mode")

    def cdrec(self):
        """进入回收站模式"""
        rec = Recovery(self._disk)
        rec.run()
        self.refresh()

    def xghost(self):
        """扫描并删除幽灵文件夹"""
        choice = input("需要清理幽灵文件夹吗(y): ")
        if choice and choice.lower() == 'y':
            self._disk.clean_ghost_folders()
            info("清理已完成")
        else:
            info("清理操作已取消")

    def refresh(self, dir_id=None):
        """刷新当前文件夹和路径信息"""
        dir_id = self._work_id if dir_id is None else dir_id
        self._file_list = self._disk.get_file_list(dir_id)
        self._dir_list = self._disk.get_dir_list(dir_id)
        self._path_list = self._disk.get_full_path(dir_id)
        self._prompt = '/'.join(self._path_list.all_name) + ' > '
        self._last_work_id = self._work_id
        self._work_name = self._path_list[-1].name
        self._work_id = self._path_list[-1].id
        if dir_id != -1:  # 如果存在上级路径
            self._parent_name = self._path_list[-2].name
            self._parent_id = self._path_list[-2].id

    def login(self):
        """使用 cookie 登录"""
        if not config.cookie or self._disk.login_by_cookie(
                config.cookie) != LanZouCloud.SUCCESS:
            open_new_tab('https://pc.woozooo.com/')
            info("请设置 Cookie 内容:")
            ylogin = input("ylogin="******"phpdisk_info=")
            if not ylogin or not disk_info:
                error("请输入正确的 Cookie 信息")
                return None
            cookie = {"ylogin": str(ylogin), "phpdisk_info": disk_info}
            if self._disk.login_by_cookie(cookie) == LanZouCloud.SUCCESS:
                config.cookie = cookie
            else:
                error("登录失败,请检查 Cookie 是否正确")
        self.refresh()

    def logout(self):
        """注销"""
        clear_screen()
        self._prompt = '> '
        self._disk.logout()
        self._file_list.clear()
        self._dir_list.clear()
        self._path_list = FolderList()
        self._parent_id = -1
        self._work_id = -1
        self._last_work_id = -1
        self._parent_name = ''
        self._work_name = ''

        config.cookie = None

    def ls(self):
        """列出文件(夹)"""
        if self._reader_mode:  # 方便屏幕阅读器阅读
            for folder in self._dir_list:
                pwd_str = '有提取码' if folder.has_pwd else ''
                print(f"{folder.name}/  {folder.desc}  {pwd_str}")
            for file in self._file_list:
                pwd_str = '有提取码' if file.has_pwd else ''
                print(
                    f"{file.name}  大小:{file.size}  上传时间:{file.time}  下载次数:{file.downs}  {pwd_str}"
                )
        else:  # 普通用户显示方式
            for folder in self._dir_list:
                pwd_str = '✦' if folder.has_pwd else '✧'
                print("#{0:<12}{1:<4}{2}{3}/".format(
                    folder.id, pwd_str, text_align(folder.desc, 28),
                    folder.name))
            for file in self._file_list:
                pwd_str = '✦' if file.has_pwd else '✧'
                print("#{0:<12}{1:<4}{2:<12}{3:>8}{4:>6}  {5}".format(
                    file.id, pwd_str, file.time, file.size, file.downs,
                    file.name))

    def cd(self, dir_name):
        """切换工作目录"""
        if not dir_name:
            info('cd .. 返回上级路径, cd - 返回上次路径, cd / 返回根目录')
        elif dir_name == '..':
            self.refresh(self._parent_id)
        elif dir_name == '/':
            self.refresh(-1)
        elif dir_name == '-':
            self.refresh(self._last_work_id)
        elif dir_name == '.':
            pass
        elif folder := self._dir_list.find_by_name(dir_name):
            self.refresh(folder.id)
        else:
Example #14
0
File = namedtuple(
    'File',
    ['name', 'id', 'time', 'size', 'type', 'downs', 'has_pwd', 'has_des'])
Folder = namedtuple('Folder', ['name', 'id', 'has_pwd', 'desc'])
FolderId = namedtuple('FolderId', ['name', 'id', 'desc', 'now'])
RecFile = namedtuple('RecFile', ['name', 'id', 'type', 'size', 'time'])
RecFolder = namedtuple('RecFolder', ['name', 'id', 'size', 'time', 'files'])
FileDetail = namedtuple(
    'FileDetail',
    ['code', 'name', 'size', 'type', 'time', 'desc', 'pwd', 'url', 'durl'],
    defaults=(0, *[''] * 8))
ShareInfo = namedtuple('ShareInfo',
                       ['code', 'name', 'url', 'pwd', 'desc', 'time', 'size'],
                       defaults=(0, *[''] * 6))
DirectUrlInfo = namedtuple('DirectUrlInfo', ['code', 'name', 'durl'])
FolderInfo = namedtuple(
    'Folder',
    ['name', 'id', 'pwd', 'time', 'desc', 'url', 'size', 'size_int', 'count'],
    defaults=(*('', ) * 7, 0, 0))
FileInFolder = namedtuple('FileInFolder',
                          ['name', 'time', 'size', 'type', 'url', 'pwd'],
                          defaults=('', ) * 6)
FolderDetail = namedtuple('FolderDetail',
                          ['code', 'folder', 'files', 'sub_folders'],
                          defaults=(0, FolderInfo(), FileInFolder(),
                                    FolderList()))

# gui 提取界面 name.setData
ShareItem = namedtuple('ShareItem', ['item', 'all', 'count', 'parrent'],
                       defaults=('', None, 1, []))
Example #15
0
class Commander:
    """蓝奏网盘命令行"""
    def __init__(self):
        self._prompt = '> '
        self._disk = LanZouCloud()
        self._is_login = False
        # self._disk.ignore_limits()
        self._task_mgr = global_task_mgr
        self._dir_list = FolderList()
        self._file_list = FileList()
        self._path_list = FolderList()
        self._parent_id = -1
        self._parent_name = ''
        self._work_name = ''
        self._work_id = -1
        self._last_work_id = -1
        self._reader_mode = config.reader_mode
        self._default_dir_pwd = config.default_dir_pwd
        self._disk.set_max_size(config.max_size)
        self._disk.set_upload_delay(config.upload_delay)

    @staticmethod
    def clear():
        clear_screen()

    @staticmethod
    def help():
        print_help()

    @staticmethod
    def update():
        check_update()

    def bye(self):
        if self._task_mgr.has_alive_task():
            info(f"有任务在后台运行, 退出请直接关闭窗口")
        else:
            exit_cmd(0)

    def rmode(self):
        """适用于屏幕阅读器用户的显示方式"""
        choice = input("以适宜屏幕阅读器的方式显示(y): ")
        if choice and choice.lower() == 'y':
            config.reader_mode = True
            self._reader_mode = True
            info("已启用 Reader Mode")
        else:
            config.reader_mode = False
            self._reader_mode = False
            info("已关闭 Reader Mode")

    @require_login
    def cdrec(self):
        """进入回收站模式"""
        rec = Recovery(self._disk)
        rec.run()
        self.refresh()

    @require_login
    def xghost(self):
        """扫描并删除幽灵文件夹"""
        choice = input("需要清理幽灵文件夹吗(y): ")
        if choice and choice.lower() == 'y':
            self._disk.clean_ghost_folders()
            info("清理已完成")
        else:
            info("清理操作已取消")

    @require_login
    def refresh(self, dir_id=None):
        """刷新当前文件夹和路径信息"""
        dir_id = self._work_id if dir_id is None else dir_id
        self._file_list = self._disk.get_file_list(dir_id)
        self._dir_list = self._disk.get_dir_list(dir_id)
        self._path_list = self._disk.get_full_path(dir_id)
        self._prompt = '/'.join(self._path_list.all_name) + ' > '
        self._last_work_id = self._work_id
        self._work_name = self._path_list[-1].name
        self._work_id = self._path_list[-1].id
        if dir_id != -1:  # 如果存在上级路径
            self._parent_name = self._path_list[-2].name
            self._parent_id = self._path_list[-2].id

    def login(self):
        """使用 cookie 登录"""
        if config.cookie and self._disk.login_by_cookie(
                config.cookie) == LanZouCloud.SUCCESS:
            self._is_login = True
            self.refresh()
            return

        auto = input("自动读取浏览器 Cookie 登录(y): ") or "y"
        if auto != "y":
            info("请手动设置 Cookie 内容:")
            ylogin = input("ylogin: "******""
            disk_info = input("phpdisk_info: ") or ""
            cookie = {"ylogin": str(ylogin), "phpdisk_info": disk_info}
            if not ylogin or not disk_info:
                error("请输入正确的 Cookie 信息")
                return
        else:
            cookie, browser = load_with_keys('.woozooo.com',
                                             ['ylogin', 'phpdisk_info'])
            if cookie:
                print()
                info(f"从 {browser} 读取用户 Cookie 成功")
            else:
                info("请在浏览器端登录账号")
                open_new_tab('https://pc.woozooo.com/')
                info("浏览器可能等待几秒才将数据写入磁盘, 请稍等")
                counter = 0
                while not cookie:
                    sleep(1)
                    counter += 1
                    print('.', end='', flush=True)
                    cookie, browser = load_with_keys(
                        '.woozooo.com', ['ylogin', 'phpdisk_info'])

                    if cookie:
                        print()
                        info(f"从 {browser} 读取用户 Cookie 成功")
                        break

                    if counter == 10:  # 读取超时
                        ctn = input("\n暂未读取到浏览器数据, 继续扫描(y) :") or "y"
                        if ctn == "y":
                            counter = 0
                            continue
                        else:
                            error("自动读取 Cookie 失败")
                            return

        # 登录蓝奏云
        if self._disk.login_by_cookie(cookie) == LanZouCloud.SUCCESS:
            config.cookie = cookie
            self._is_login = True
            self.refresh()
        else:
            error("登录失败,请检查 Cookie 是否正确")

    @require_login
    def logout(self):
        """注销"""
        clear_screen()
        self._prompt = '> '
        self._disk.logout()
        self._is_login = False
        self._file_list.clear()
        self._dir_list.clear()
        self._path_list = FolderList()
        self._parent_id = -1
        self._work_id = -1
        self._last_work_id = -1
        self._parent_name = ''
        self._work_name = ''

        config.cookie = None
        info("本地 Cookie 已清除")

    @require_login
    def ls(self):
        """列出文件(夹)"""
        if self._reader_mode:  # 方便屏幕阅读器阅读
            for folder in self._dir_list:
                pwd_str = '有提取码' if folder.has_pwd else ''
                print(f"{folder.name}/  {folder.desc}  {pwd_str}")
            for file in self._file_list:
                pwd_str = '有提取码' if file.has_pwd else ''
                print(
                    f"{file.name}  大小:{file.size}  上传时间:{file.time}  下载次数:{file.downs}  {pwd_str}"
                )
        else:  # 普通用户显示方式
            for folder in self._dir_list:
                pwd_str = '◆' if folder.has_pwd else '◇'
                print("#{0:<12}{1:<2}{2}{3}/".format(
                    folder.id, pwd_str, text_align(folder.desc, 28),
                    folder.name))
            for file in self._file_list:
                pwd_str = '◆' if file.has_pwd else '◇'
                print("#{0:<12}{1:<2}{2:<12}{3:>8}{4:>6}↓  {5}".format(
                    file.id, pwd_str, file.time, file.size, file.downs,
                    file.name))

    @require_login
    def cd(self, dir_name):
        """切换工作目录"""
        if not dir_name:
            info('cd .. 返回上级路径, cd - 返回上次路径, cd / 返回根目录')
        elif dir_name == '..':
            self.refresh(self._parent_id)
        elif dir_name == '/':
            self.refresh(-1)
        elif dir_name == '-':
            self.refresh(self._last_work_id)
        elif dir_name == '.':
            pass
        elif folder := self._dir_list.find_by_name(dir_name):
            self.refresh(folder.id)
        else: