Ejemplo n.º 1
0
 def get_file_list(self, folder_id=-1) -> FileList:
     """获取文件列表"""
     page = 1
     file_list = FileList()
     while True:
         post_data = {'task': 5, 'folder_id': folder_id, 'pg': page}
         resp = self._post(self._doupload_url, post_data)
         if not resp:  # 网络异常,重试
             continue
         else:
             resp = resp.json()
         if resp["info"] == 0:
             break  # 已经拿到了全部的文件信息
         else:
             page += 1  # 下一页
         # 文件信息处理
         for file in resp["text"]:
             file_list.append(File(
                 id=int(file['id']),
                 name=file['name_all'],
                 time=file['time'],  # 上传时间
                 size=file['size'],  # 文件大小
                 type=file['name_all'].split('.')[-1],  # 文件类型
                 downs=int(file['downs']),  # 下载次数
                 has_pwd=True if int(file['onof']) == 1 else False,  # 是否存在提取码
                 has_des=True if int(file['is_des']) == 1 else False  # 是否存在描述
             ))
     return file_list
Ejemplo n.º 2
0
    def get_rec_file_list(self, folder_id=-1) -> FileList:
        """获取回收站文件列表"""
        if folder_id == -1:  # 列出回收站根目录文件
            # 回收站文件夹中的文件也会显示在根目录
            html = self._get(self._mydisk_url,
                             params={
                                 'item': 'recycle',
                                 'action': 'files'
                             })
            if not html:
                return FileList()
            html = remove_notes(html.text)
            files = re.findall(
                r'fl_sel_ids[^\n]+value="(\d+)".+?filetype/(\w+)\.gif.+?/>\s?(.+?)(?:\.{3})?</a>.+?<td.+?>([\d\-]+?)</td>',
                html, re.DOTALL)
            file_list = FileList()
            file_name_list = []
            counter = 1
            for fid, ftype, name, time in sorted(files, key=lambda x: x[2]):
                if not name.endswith(ftype):  # 防止文件名太长导致丢失了文件后缀
                    name = name + '.' + ftype

                if name in file_name_list:  # 防止长文件名前 17:34 个字符相同重名
                    counter += 1
                    name = f'{name}({counter})'
                else:
                    counter = 1
                    file_name_list.append(name)
                file_list.append(
                    RecFile(name, int(fid), ftype, size='', time=time))
            return file_list
        else:  # 列出回收站中文件夹内的文件,信息只有部分文件名和文件大小
            para = {
                'item': 'recycle',
                'action': 'folder_restore',
                'folder_id': folder_id
            }
            html = self._get(self._mydisk_url, params=para)
            if not html or '此文件夹没有包含文件' in html.text:
                return FileList()
            html = remove_notes(html.text)
            files = re.findall(
                r'com/(\d+?)".+?filetype/(\w+)\.gif.+?/>&nbsp;(.+?)(?:\.{3})?</a> <font color="#CCCCCC">\((.+?)\)</font>',
                html)
            file_list = FileList()
            file_name_list = []
            counter = 1
            for fid, ftype, name, size in sorted(files, key=lambda x: x[2]):
                if not name.endswith(ftype):  # 防止文件名太长丢失后缀
                    name = name + '.' + ftype
                if name in file_name_list:
                    counter += 1
                    name = f'{name}({counter})'  # 防止文件名太长且前17个字符重复
                else:
                    counter = 1
                    file_name_list.append(name)
                file_list.append(
                    RecFile(name, int(fid), ftype, size=size, time=''))
            return file_list
Ejemplo n.º 3
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
Ejemplo n.º 4
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)
Ejemplo n.º 5
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)
Ejemplo n.º 6
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
Ejemplo n.º 7
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:
Ejemplo n.º 8
0
    def get_folder_info_by_url(self, share_url, dir_pwd=''):
        """获取文件夹里所有文件的信息"""
        if is_file_url(share_url):
            return FolderDetail(LanZouCloud.URL_INVALID)
        try:
            html = requests.get(share_url, headers=self._headers).text
        except requests.RequestException:
            return FolderDetail(LanZouCloud.NETWORK_ERROR)
        if '文件不存在' in html:
            return FolderDetail(LanZouCloud.FILE_CANCELLED)
        if '请输入密码' in html and len(dir_pwd) == 0:
            return FolderDetail(LanZouCloud.LACK_PASSWORD)
        try:
            # 获取文件需要的参数
            html = remove_notes(html)
            lx = re.findall(r"'lx':'?(\d)'?,", html)[0]
            t = re.findall(r"var [0-9a-z]{6} = '(\d{10})';", html)[0]
            k = re.findall(r"var [0-9a-z]{6} = '([0-9a-z]{15,})';", html)[0]
            # 文件夹的信息
            folder_id = re.findall(r"'fid':'?(\d+)'?,", html)[0]
            folder_name = re.findall(r"var.+?='(.+?)';\n.+document.title", html)[0]
            folder_time = re.findall(r'class="rets">([\d\-]+?)<a', html)[0]  # 日期不全 %m-%d
            folder_desc = re.findall(r'id="filename">(.+?)</span>', html)  # 无描述时无法完成匹配
            folder_desc = folder_desc[0] if len(folder_desc) == 1 else ''
        except IndexError:
            return FolderDetail(LanZouCloud.FAILED)

        page = 1
        files = FileList()
        while True:
            try:
                # 这里不用封装好的 post 函数是为了支持未登录的用户通过 URL 下载, 无密码时设置 pwd 字段也不影响
                post_data = {'lx': lx, 'pg': page, 'k': k, 't': t, 'fid': folder_id, 'pwd': dir_pwd}
                resp = requests.post(self._host_url + '/filemoreajax.php', data=post_data, headers=self._headers).json()
            except requests.RequestException:
                return FolderDetail(LanZouCloud.NETWORK_ERROR)
            if resp['zt'] == 1:  # 成功获取一页文件信息
                for f in resp["text"]:
                    files.append(FileInFolder(
                        name=f["name_all"],  # 文件名
                        time=f["time"],  # 上传时间
                        size=f["size"],  # 文件大小
                        type=f["name_all"].split('.')[-1],  # 文件格式
                        url=self._host_url + "/" + f["id"]  # 文件分享链接
                    ))
                page += 1  # 下一页
                continue
            elif resp['zt'] == 2:  # 已经拿到全部的文件信息
                break
            elif resp['zt'] == 3:  # 提取码错误
                return FolderDetail(LanZouCloud.PASSWORD_ERROR)
            elif resp["zt"] == 4:
                continue
            else:
                return FolderDetail(LanZouCloud.FAILED)  # 其它未知错误
        # 通过文件的时间信息补全文件夹的年份(如果有文件的话)
        if files:  # 最后一个文件上传时间最早,文件夹的创建年份与其相同
            folder_time = files[-1].time.split('-')[0] + '-' + folder_time
        else:  # 可恶,没有文件,日期就设置为今年吧
            folder_time = datetime.today().strftime('%Y-%m-%d')
        return FolderDetail(LanZouCloud.SUCCESS,
                            FolderInfo(folder_name, folder_id, dir_pwd, folder_time, folder_desc, share_url),
                            files)
Ejemplo n.º 9
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:
Ejemplo n.º 10
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: