Beispiel #1
0
class Uploader:
    # 源码目录
    _folder_id_dnf_calc_code = "1989903"
    # 版本目录
    _folder_id_dnf_calc_publish = "1860455"

    def __init__(self, cookie):
        self.lzy = LanZouCloud()
        self.login_ok = self.lzy.login_by_cookie(cookie) == LanZouCloud.SUCCESS
        if self.login_ok:
            self._folder_dnf_calc_code = self.lzy.get_folder_info_by_id(self._folder_id_dnf_calc_code).folder
            self._folder_dnf_calc_publish = self.lzy.get_folder_info_by_id(self._folder_id_dnf_calc_publish).folder

    def upload_to_lanzouyun(self, filepath, target_folder):
        def on_uploaded(fid, is_file):
            if not is_file:
                return
            files = self.lzy.get_file_list(target_folder.id)
            self.lzy.move_file(fid, target_folder.id)

        # 上传到指定的文件夹中
        retCode = self.lzy.upload_file(filepath, -1, callback=self.show_progress, uploaded_handler=on_uploaded)
        if retCode != LanZouCloud.SUCCESS:
            # logger.error("上传失败,retCode={}".format(retCode))
            return False

        return True

    def show_progress(self, file_name, total_size, now_size):
        """显示进度的回调函数"""
        percent = now_size / total_size
        bar_len = 40  # 进度条长总度
        bar_str = '>' * round(bar_len * percent) + '=' * round(bar_len * (1 - percent))
        print('\r{:.2f}%\t[{}] {:.1f}/{:.1f}MB | {} '.format(
            percent * 100, bar_str, now_size / 1048576, total_size / 1048576, file_name), end='')
        if total_size == now_size:
            print('')  # 下载完成换行
    
    def clearOldVersion(self):
        # 清空版本目录
        path = self.lzy.get_file_list(self._folder_id_dnf_calc_code)
        for item in path:
            if item.name.startswith("源码"):                
                self.lzy.delete(item.id)
        path = self.lzy.get_file_list(self._folder_id_dnf_calc_publish)
        for item in path:
            if item.name.startswith("DNF计算器") or item.name.startswith("更新日志"): 
                self.lzy.delete(item.id)
Beispiel #2
0
class MainWindow(QMainWindow, Ui_MainWindow):
    __version__ = 'v0.0.5'

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.init_variable()
        self.init_menu()
        self.setWindowTitle("蓝奏云客户端 - {}".format(self.__version__))
        self.setWindowIcon(QIcon("./icon/lanzou-logo2.png"))

        self.set_window_at_center()
        self.init_extract_share_ui()
        self.init_disk_ui()
        self.call_login_luncher()

        self.create_left_menus()

        # print(QApplication.style().objectName())
        self.setObjectName("MainWindow")

        self.setStyleSheet(qssStyle)
        # self.disk_tab.setStyleSheet("* {background-color: rgba(255, 255, 255, 150);}")
        self.tabWidget.setStyleSheet("QTabBar{ background-color: #AEEEEE; }")

    def init_menu(self):
        self.login.triggered.connect(self.show_login_dialog)  # 登录
        self.login.setIcon(QIcon("./icon/login.ico"))
        self.login.setShortcut("Ctrl+L")
        self.toolbar.addAction(self.login)
        self.logout.triggered.connect(self.call_logout)  # 登出
        self.logout.setIcon(QIcon("./icon/logout.ico"))
        self.logout.setShortcut("Ctrl+Q")  # 登出快捷键
        self.download.setShortcut("Ctrl+J")  # 以下还未使用
        self.download.setIcon(QIcon("./icon/download.ico"))
        self.delete.setShortcut("Ctrl+D")
        self.delete.setIcon(QIcon("./icon/delete.ico"))
        # self.how.setShortcut("Ctrl+H")
        self.how.setIcon(QIcon("./icon/help.ico"))
        self.how.triggered.connect(self.open_wiki_url)
        # self.about.setShortcut("Ctrl+O")
        self.about.setIcon(QIcon("./icon/about.ico"))
        self.about.triggered.connect(self.about_dialog.exec)
        self.upload.setIcon(QIcon("./icon/upload.ico"))
        self.upload.setShortcut("Ctrl+U")  # 上传快捷键

    def init_variable(self):
        self._disk = LanZouCloud()
        self._config = "./config.pkl"
        self._folder_list = {}
        self._file_list = {}
        self._path_list = {}
        self._path_list_old = {}
        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()
        if os.name == 'nt':
            self._disk.set_rar_tool("./rar.exe")
        else:
            self._disk.set_rar_tool("/usr/bin/rar")
        # 登录器
        self.login_luncher = LoginLuncher(self._disk)
        self.login_luncher.code.connect(self.login_update_ui)
        # 下载器
        self.download_manager = DownloadManager(self._disk)
        self.download_manager.downloaders_msg.connect(self.show_status)
        self.download_manager.download_mgr_msg.connect(self.show_status)
        self.download_manager.finished.connect(
            lambda: self.show_status("所有下载任务已完成!", 7000))
        # 上传器,信号在登录更新界面设置
        self.upload_dialog = UploadDialog()
        self.upload_dialog.new_infos.connect(self.call_upload)
        # 文件描述更新器
        self.desc_fetcher = DescFetcher(self._disk)
        self.desc_fetcher.desc.connect(self.call_update_desc)
        # 设置 tab
        self.tabWidget.setCurrentIndex(0)
        self.tabWidget.removeTab(2)
        self.tabWidget.removeTab(1)
        self.disk_tab.setEnabled(False)
        self.rec_tab.setEnabled(False)
        # 状态栏
        self._msg_label = QLabel()
        self._msg_label.setObjectName("msg_label")
        self.statusbar.addWidget(self._msg_label)
        # 重命名、修改简介与新建文件夹对话框
        self.rename_dialog = RenameDialog()
        self.rename_dialog.out.connect(self.rename_set_desc_and_mkdir)
        # 菜单栏关于
        self.about_dialog = AboutDialog()
        self.about_dialog.set_values(self.__version__)

    def show_login_dialog(self):
        """显示登录对话框"""
        login_dialog = LoginDialog(self._config)
        login_dialog.clicked_ok.connect(self.call_login_luncher)
        login_dialog.setWindowModality(Qt.ApplicationModal)
        login_dialog.exec()

    def show_upload_dialog(self):
        """显示上传文件对话框"""
        self.upload_dialog.set_values(list(self._path_list.keys())[-1])
        self.upload_dialog.exec()

    def load_settings(self):
        try:
            with open(self._config, "rb") as _file:
                self.settings = load(_file)
        except Exception:
            dl_path = os.path.dirname(
                os.path.abspath(__file__)) + os.sep + "downloads"
            self.settings = {"user": "", "pwd": "", "path": dl_path}
            with open(self._config, "wb") as _file:
                dump(self.settings, _file)

    def call_downloader(self):
        tab_page = self.tabWidget.currentIndex()
        if tab_page == 0:
            listview = self.table_share
            model = self.model_share
        elif tab_page == 1:
            listview = self.table_disk
            model = self.model_disk
        indexes = []
        tasks = []
        _indexes = listview.selectionModel().selection().indexes()
        for i in _indexes:  # 获取所选行号
            indexes.append(i.row())
        indexes = set(indexes)
        save_path = self.settings["path"]
        for index in indexes:
            infos = model.item(index, 0).data()
            if not infos:
                continue
            # 查询 分享链接 以及 提取码
            if infos[0]:  # 从 disk 运行
                if infos[2]:  # 文件
                    _info = self._disk.get_share_info(infos[0], is_file=True)
                else:  # 文件夹
                    _info = self._disk.get_share_info(infos[0], is_file=False)
                infos[5] = _info['pwd']  # 将bool值改成 字符串
                infos.append(_info['url'])
            tasks.append([infos[1], infos[7], infos[5], save_path])
        self.download_manager.set_values(tasks, 3)
        self.download_manager.start()

    def call_logout(self):
        """菜单栏、工具栏登出"""
        self._disk.logout()
        self.toolbar.removeAction(self.logout)
        self.tabWidget.setCurrentIndex(0)
        self.disk_tab.setEnabled(False)
        self.rec_tab.setEnabled(False)
        self.tabWidget.removeTab(2)
        self.tabWidget.removeTab(1)
        self.toolbar.removeAction(self.logout)  # 登出工具
        self.logout.setEnabled(False)
        self.toolbar.removeAction(self.upload)  # 上传文件工具栏
        self.upload.setEnabled(False)
        self.upload.triggered.disconnect(self.show_upload_dialog)
        self.statusbar.showMessage("已经退出登录!", 4000)

    def login_update_ui(self, success, msg, duration):
        """根据登录是否成功更新UI"""
        if success:
            self.show_status(msg, duration)
            self.tabWidget.insertTab(1, self.disk_tab, "我的蓝奏云")
            self.tabWidget.insertTab(2, self.rec_tab, "回收站")
            self.disk_tab.setEnabled(True)
            self.rec_tab.setEnabled(True)
            # 更新快捷键与工具栏
            self.toolbar.addAction(self.logout)  # 添加登出工具栏
            self.toolbar.addAction(self.upload)  # 添加上传文件工具栏
            # 菜单栏槽
            self.logout.setEnabled(True)
            self.upload.setEnabled(True)
            self.upload.triggered.connect(self.show_upload_dialog)
            # 设置当前显示 tab
            self.tabWidget.setCurrentIndex(1)
            QCoreApplication.processEvents()  # 重绘界面
            # 刷新文件列表
            self.refresh_dir(self._work_id)
        else:
            self.show_status(msg, duration)
            self.tabWidget.setCurrentIndex(0)
            self.tabWidget.removeTab(2)
            self.tabWidget.removeTab(1)
            self.disk_tab.setEnabled(False)
            self.rec_tab.setEnabled(False)
            # 更新快捷键与工具栏
            self.toolbar.removeAction(self.logout)  # 登出工具栏
            self.toolbar.removeAction(self.upload)  # 上传文件工具栏
            self.logout.setEnabled(False)
            self.upload.setEnabled(False)

    def call_login_luncher(self):
        """登录网盘"""
        self.load_settings()
        self._disk.logout()
        self.toolbar.removeAction(self.logout)
        try:
            username = self.settings["user"]
            password = self.settings["pwd"]
            cookie = self.settings["cookie"]
            self.login_luncher.set_values(username, password, cookie)
            self.login_luncher.start()
        except Exception as exp:
            print(exp)
            pass

    def set_file_icon(self, name):
        suffix = name.split(".")[-1]
        ico_path = "./icon/{}.gif".format(suffix)
        if os.path.isfile(ico_path):
            return QIcon(ico_path)
        else:
            return QIcon("./icon/file.ico")

    def show_file_and_folder_lists(self):
        """显示文件和文件夹列表"""
        self.model_disk.removeRows(0, self.model_disk.rowCount())  # 清理旧的内容
        file_count = len(self._file_list.keys())
        folder_count = len(self._folder_list.keys())
        name_header = [
            "文件夹{}个".format(folder_count),
        ] if folder_count else []
        if file_count:
            name_header.append("文件{}个".format(file_count))
        self.model_disk.setHorizontalHeaderLabels(
            ["/".join(name_header), "大小", "时间"])
        folder_ico = QIcon("./icon/folder.gif")
        pwd_ico = QIcon("./icon/keys.ico")
        # infos: ID/None,文件名,大小,日期,下载次数(dl_count),提取码(pwd),描述(desc),|链接(share-url),直链
        if self._work_id != -1:
            _back = QStandardItem(folder_ico, "..")
            _back.setToolTip("双击返回上层文件夹,选中无效")
            self.model_disk.appendRow(
                [_back, QStandardItem(""),
                 QStandardItem("")])
        for infos in self._folder_list.values():  # 文件夹
            name = QStandardItem(folder_ico, infos[1])
            name.setData(infos)
            tips = ""
            if infos[5] is not False:
                tips = "有提取码"
                if infos[6] is not False:
                    tips = tips + ",描述:" + str(infos[6])
            elif infos[6] is not False:
                tips = "描述:" + str(infos[6])
            name.setToolTip(tips)
            size_ = QStandardItem(pwd_ico, "") if infos[5] else QStandardItem(
                "")  # 提取码+size
            self.model_disk.appendRow([name, size_, QStandardItem("")])
        for infos in self._file_list.values():  # 文件
            name = QStandardItem(self.set_file_icon(infos[1]), infos[1])
            name.setData(infos)
            tips = ""
            if infos[5] is not False:
                tips = "有提取码"
                if infos[6] is not False:
                    tips = tips + ",描述:" + str(infos[6])
            elif infos[6] is not False:
                tips = "描述:" + str(infos[6])
            name.setToolTip(tips)
            size_ = QStandardItem(pwd_ico,
                                  infos[2]) if infos[5] else QStandardItem(
                                      infos[2])  # 提取码+size
            self.model_disk.appendRow([name, size_, QStandardItem(infos[3])])
        for r in range(self.model_disk.rowCount()):  # 右对齐
            self.model_disk.item(r, 1).setTextAlignment(Qt.AlignRight
                                                        | Qt.AlignVCenter)
            self.model_disk.item(r, 2).setTextAlignment(Qt.AlignRight
                                                        | Qt.AlignVCenter)

    def refresh_dir(self,
                    folder_id=-1,
                    r_files=True,
                    r_folders=True,
                    r_path=True):
        """更新目录列表,包括列表和路径指示器"""
        if r_files:
            info = {
                i['name']: [
                    i['id'], i['name'], i['size'], i['time'], i['downs'],
                    i['has_pwd'], i['has_des']
                ]
                for i in self._disk.get_file_list(folder_id)
            }
            self._file_list = {
                key: info.get(key)
                for key in sorted(info.keys())
            }  # {name-[id,...]}
        if r_folders:
            info = {
                i['name']:
                [i['id'], i['name'], "", "", "", i['has_pwd'], i['desc']]
                for i in self._disk.get_dir_list(folder_id)
            }
            self._folder_list = {
                key: info.get(key)
                for key in sorted(info.keys())
            }  # {name-[id,...]}
        self._path_list = self._disk.get_full_path(folder_id)
        current_folder = list(self._path_list.keys())[-1]
        self._work_id = self._path_list.get(current_folder, -1)
        if folder_id != -1:
            parent_folder_name = list(self._path_list.keys())[-2]
            self._parent_id = self._path_list.get(parent_folder_name, -1)
        self.show_file_and_folder_lists()
        if r_path:
            self.show_full_path()

    def config_tableview(self, tab):
        if tab == "share":
            model = self.model_share
            table = self.table_share
        elif tab == "disk":
            model = self.model_disk
            table = self.table_disk

        model.setHorizontalHeaderLabels(["文件名", "大小", "时间"])
        table.setModel(model)
        # 是否显示网格线
        table.setShowGrid(False)
        # 禁止编辑表格
        table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        # 隐藏水平表头
        table.verticalHeader().setVisible(False)
        # 设置表头可以自动排序
        table.setSortingEnabled(True)
        table.setMouseTracking(False)
        # 设置表头的背景色为绿色
        table.horizontalHeader().setStyleSheet(
            "QHeaderView::section{background:lightgray}")
        # 设置 不可选择单个单元格,只可选择一行。
        table.setSelectionBehavior(QAbstractItemView.SelectRows)
        # 设置第二三列的宽度
        table.horizontalHeader().resizeSection(1, 90)
        table.horizontalHeader().resizeSection(2, 80)
        # 设置第一列宽度自动调整,充满屏幕
        table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        table.setContextMenuPolicy(Qt.CustomContextMenu)  # 允许右键产生子菜单
        table.customContextMenuRequested.connect(self.generateMenu)  # 右键菜单

    def create_left_menus(self):
        self.left_menus = QMenu()
        self.left_menu_share_url = self.left_menus.addAction("外链分享地址")
        self.left_menu_share_url.setIcon(QIcon("./icon/share.ico"))
        self.left_menu_rename_set_desc = self.left_menus.addAction("修改文件夹名与描述")
        self.left_menu_rename_set_desc.setIcon(QIcon("./icon/desc.ico"))
        self.left_menu_set_pwd = self.left_menus.addAction("设置访问密码")
        self.left_menu_set_pwd.setIcon(QIcon("./icon/password.ico"))
        self.left_menu_move = self.left_menus.addAction("移动")
        self.left_menu_move.setIcon(QIcon("./icon/move.ico"))

    def rename_set_desc_and_mkdir(self, infos):
        """重命名、修改简介与新建文件夹"""
        action = infos[0]
        fid = infos[1]
        new_name = infos[2]
        new_desc = infos[3]
        if not fid:  # 新建文件夹
            fid = self._work_id
            if new_name in self._folder_list.keys():
                self.statusbar.showMessage("文件夹已存在:{}".format(new_name), 7000)
            else:
                res = self._disk.mkdir(self._work_id, new_name, new_desc)
                if res == LanZouCloud.MKDIR_ERROR:
                    self.statusbar.showMessage("创建文件夹失败:{}".format(new_name),
                                               7000)
                else:
                    sleep(1.5)  # 暂停一下,否则无法获取新建的文件夹
                    self.statusbar.showMessage("成功创建文件夹:{}".format(new_name),
                                               7000)
                    # 此处仅更新文件夹,并显示
                    self.refresh_dir(self._work_id, False, True, False)
        else:  # 重命名、修改简介
            if action == "file":  # 修改文件描述
                res = self._disk.set_desc(fid, str(new_desc), is_file=True)
            else:  # 修改文件夹,action == "folder"
                _res = self._disk.get_share_info(fid, is_file=False)
                if _res['code'] == LanZouCloud.SUCCESS:
                    res = self._disk._set_dir_info(fid, str(new_name),
                                                   str(new_desc))
                else:
                    res = _res['code']
            if res == LanZouCloud.SUCCESS:
                self.statusbar.showMessage("修改成功!", 4000)
            elif res == LanZouCloud.FAILED:
                self.statusbar.showMessage("失败:发生错误!", 4000)
            if action == "file":  # 只更新文件列表
                self.refresh_dir(self._work_id,
                                 r_files=True,
                                 r_folders=False,
                                 r_path=False)
            else:  # 只更新文件夹列表
                self.refresh_dir(self._work_id,
                                 r_files=False,
                                 r_folders=True,
                                 r_path=False)

    def set_passwd(self, infos):
        """设置文件(夹)提取码"""
        fid = infos[0]
        if not fid:
            print("ERROR : 文件(夹)不存在:{}".format(infos[0]))
            return None
        new_pass = infos[1]
        if 2 <= len(new_pass) <= 6 or new_pass == "":
            if infos[2]:
                is_file = True
            else:
                is_file = False
            res = self._disk.set_passwd(fid, new_pass, is_file)
            if res == LanZouCloud.SUCCESS:
                self.statusbar.showMessage("提取码变更成功!♬", 3000)
            elif res == LanZouCloud.NETWORK_ERROR:
                self.statusbar.showMessage("网络错误,稍后重试!☒", 4000)
            else:
                self.statusbar.showMessage("提取码变更失败❀╳❀:{}".format(res), 4000)
            self.refresh_dir(self._work_id,
                             r_files=is_file,
                             r_folders=not is_file,
                             r_path=False)
        else:
            self.statusbar.showMessage("提取码为2-6位字符,关闭请输入空!", 4000)

    def move_file(self, info):
        """移动文件至新的文件夹"""
        file_id = info[0]
        folder_id = info[1]
        if self._disk.move_file(file_id, folder_id) == LanZouCloud.SUCCESS:
            # 此处仅更新文件夹,并显示
            self.refresh_dir(self._work_id, False, True, False)
            self.statusbar.showMessage("{} 移动成功!".format(info[2]), 4000)
        else:
            self.statusbar.showMessage("移动文件{}失败!".format(info[2]), 4000)

    def call_mkdir(self):
        """弹出新建文件夹对话框"""
        self.rename_dialog.set_values(None)
        self.rename_dialog.exec()

    def remove_files(self, infos):
        if not infos:
            return
        for i in infos:
            if i[1]:
                is_file = True
            else:
                is_file = False
            self._disk.delete(i[0], is_file)
        self.refresh_dir(self._work_id)

    def call_remove_files(self):
        indexs = []
        infos = []
        _indexs = self.table_disk.selectionModel().selection().indexes()
        if not _indexs:
            return
        for i in _indexs:  # 获取所选行号
            indexs.append(i.row())
        indexs = set(indexs)
        for index in indexs:
            info = self.model_disk.item(index, 0).data()  # 用于提示删除的文件名
            if info:
                infos.append(info[:3])
        delete_dialog = DeleteDialog(infos)
        delete_dialog.new_infos.connect(self.remove_files)
        delete_dialog.exec()

    def generateMenu(self, pos):
        """右键菜单"""
        row_num = self.sender().selectionModel().selection().indexes()
        if not row_num:  # 如果没有选中行,什么也不做
            return
        # row_num = row_num[0].row()
        _model = self.sender().model()
        infos = _model.item(row_num[0].row(), 0).data()
        if not infos:
            return
        # 通过是否有文件 ID 判断是登录界面还是提取界面
        if infos[0]:
            self.left_menu_rename_set_desc.setEnabled(True)
            self.left_menu_set_pwd.setEnabled(True)
            # 通过infos第3个字段 size 判断是否为文件夹,文件夹不能移动,设置不同的显示菜单名
            if infos[2]:
                self.left_menu_rename_set_desc.setText("修改文件描述")
                self.left_menu_move.setEnabled(True)
            else:
                self.left_menu_rename_set_desc.setText("修改文件夹名与描述")
                self.left_menu_move.setDisabled(True)
        else:
            self.left_menu_rename_set_desc.setDisabled(True)
            self.left_menu_move.setDisabled(True)
            self.left_menu_set_pwd.setDisabled(True)

        action = self.left_menus.exec_(self.sender().mapToGlobal(pos))
        infos = self.get_more_infomation(infos)  # 点击菜单项后更新信息
        if action == self.left_menu_share_url:
            info_dialog = InfoDialog(infos)
            info_dialog.setWindowModality(Qt.ApplicationModal)
            info_dialog.exec()
        elif action == self.left_menu_move:
            all_dirs_dict = self._disk.get_folder_id_list()
            move_file_dialog = MoveFileDialog(infos, all_dirs_dict)
            move_file_dialog.new_infos.connect(self.move_file)
            move_file_dialog.exec()
        elif action == self.left_menu_set_pwd:
            set_pwd_dialog = SetPwdDialog(infos)
            set_pwd_dialog.new_infos.connect(self.set_passwd)
            set_pwd_dialog.exec()
        elif action == self.left_menu_rename_set_desc:
            self.desc_fetcher.set_values(infos)
            self.desc_fetcher.start()  # 启动后台更新描述
            self.rename_dialog.set_values(infos)
            self.rename_dialog.exec()

    def call_update_desc(self, desc, infos):
        infos[6] = desc  # 更新 desc
        self.rename_dialog.set_values(infos)

    def get_more_infomation(self, infos):
        """获取文件直链、文件(夹)提取码描述"""
        if self._work_name == "Recovery":
            print("ERROR : 回收站模式下无法使用此操作")
            return None
        # infos: ID/None,文件名,大小,日期,下载次数(dl_count),提取码(pwd),描述(desc),|链接(share-url),直链
        if infos[0]:  # 从 disk 运行
            if infos[2]:  # 文件
                _info = self._disk.get_share_info(infos[0], is_file=True)
            else:  # 文件夹
                _info = self._disk.get_share_info(infos[0], is_file=False)
            infos[5] = _info['pwd']
            infos.append(_info['url'])
        if infos[2]:  # 文件
            res = self._disk.get_file_info_by_url(infos[-1], infos[5])
            if res["code"] == LanZouCloud.SUCCESS:
                infos.append("{}".format(res["durl"] or "无"))  # 下载直链
            elif res["code"] == LanZouCloud.NETWORK_ERROR:
                infos.append("网络错误!获取失败")  # 下载直链
            else:
                infos.append("其它错误!")  # 下载直链
        else:
            infos.append("无")  # 下载直链
        infos[5] = infos[5] or "无"  # 提取码
        return infos

    def chang_dir(self, dir_name):
        """双击切换工作目录"""
        dir_name = self.model_disk.item(dir_name.row(), 0).text()  # 文件夹名
        if self._work_name == "Recovery" and dir_name not in [".", ".."]:
            return None
        if dir_name == "..":  # 返回上级路径
            self.refresh_dir(self._parent_id)
        elif dir_name in self._folder_list.keys():
            folder_id = self._folder_list[dir_name][0]
            self.refresh_dir(folder_id)
        else:
            self.show_status("ERROR : 该文件夹不存在: {}".format(dir_name))

    def call_change_dir(self, folder_id=-1):
        """按钮调用"""
        def callfunc():
            self.refresh_dir(folder_id)

        return callfunc

    def call_upload(self, infos):
        """上传文件(夹)"""
        if self._work_name == 'Recovery':
            print('ERROR : 回收站模式下无法使用此操作')
            return None
        self._old_work_id = self._work_id  # 记录上传文件夹id
        self.upload_worker.set_values(self._disk, infos, self._old_work_id)
        self.upload_worker.start()

    def show_full_path(self):
        """路径框显示当前路径"""
        index = 1
        for name in self._path_list_old.items():
            self._locs[index].clicked.disconnect()
            self.disk_loc.removeWidget(self._locs[index])
            self._locs[index].deleteLater()
            self._locs[index] = None
            del self._locs[index]
            index += 1
        index = 1
        for name, id in self._path_list.items():
            self._locs[index] = QPushButton(name, self.disk_tab)
            self._locs[index].setIcon(QIcon("./icon/folder.gif"))
            self._locs[index].setStyleSheet(
                "QPushButton {border: none; background:transparent;}")
            self.disk_loc.insertWidget(index, self._locs[index])
            self._locs[index].clicked.connect(self.call_change_dir(id))
            index += 1
        self._path_list_old = self._path_list

    def select_all_btn(self, action="reverse"):
        """默认反转按钮状态"""
        page = self.tabWidget.currentIndex()
        if page == 0:
            btn = self.btn_share_select_all
            table = self.table_share
        elif page == 1:
            btn = self.btn_disk_select_all
            table = self.table_disk
        elif page == 2:
            return
        else:
            return
        if btn.isEnabled():
            if action == "reverse":
                if btn.text() == "全选":
                    table.selectAll()
                    btn.setText("取消")
                    btn.setIcon(QIcon("./icon/select-none.ico"))
                elif btn.text() == "取消":
                    table.clearSelection()
                    btn.setText("全选")
                    btn.setIcon(QIcon("./icon/select-all.ico"))
            elif action == "cancel":  # 点击列表其中一个就表示放弃全选
                btn.setText("全选")
                btn.setIcon(QIcon("./icon/select-all.ico"))
            else:
                table.selectAll()
                btn.setText("取消")
                btn.setIcon(QIcon("./icon/select-none.ico"))

    def finished_upload(self):
        """上传完成调用"""
        if self._old_work_id == self._work_id:
            self.refresh_dir(self._work_id, True, True, False)
        else:
            self._old_work_id = self._work_id
        self.show_status("上传完成!", 7000)

    def init_disk_ui(self):
        self.model_disk = QStandardItemModel(1, 3)
        self.config_tableview("disk")
        self.btn_disk_delete.setIcon(QIcon("./icon/delete.ico"))
        self.btn_disk_dl.setIcon(QIcon("./icon/downloader.ico"))
        self.btn_disk_select_all.setIcon(QIcon("./icon/select-all.ico"))
        self.btn_disk_select_all.setToolTip("按下 Ctrl/Alt + A 全选或则取消全选")
        self.btn_disk_select_all.clicked.connect(
            lambda: self.select_all_btn("reverse"))
        self.table_disk.clicked.connect(lambda: self.select_all_btn("cancel"))
        self.btn_disk_dl.clicked.connect(self.call_downloader)
        self.btn_disk_mkdir.setIcon(QIcon("./icon/add-folder.ico"))
        self.btn_disk_mkdir.clicked.connect(self.call_mkdir)
        self.btn_disk_delete.clicked.connect(self.call_remove_files)

        self.table_disk.doubleClicked.connect(self.chang_dir)
        # 上传器
        self.upload_worker = UploadWorker()
        self.upload_worker.finished.connect(self.finished_upload)
        self.upload_worker.code.connect(self.show_status)

    def show_status(self, msg, duration=0):
        self._msg_label.setText(msg)
        # self.statusbar.showMessage(msg, duration)
        QCoreApplication.processEvents()  # 重绘界面
        if duration != 0:
            QTimer.singleShot(duration, lambda: self._msg_label.setText(""))

    # shared url
    def call_get_shared_info_worker(self):
        line_share_text = self.line_share_url.text().strip()
        pat = r"(https?://(www\.)?lanzous.com/[bi][a-z0-9]+)[^0-9a-z]*([a-z0-9]+)?"
        for share_url, _, pwd in re.findall(pat, line_share_text):
            pass
        if self._disk.is_file_url(share_url):  # 链接为文件
            is_file = True
            is_folder = False
            self.show_status("正在获取文件链接信息……")
        elif self._disk.is_folder_url(share_url):  # 链接为文件夹
            is_folder = True
            is_file = False
            self.show_status("正在获取文件夹链接信息,可能需要几秒钟,请稍后……")
        else:
            self.show_status("{} 为非法链接!".format(share_url))
            self.btn_extract.setEnabled(True)
            self.line_share_url.setEnabled(True)
            return
        self.model_share.removeRows(0, self.model_share.rowCount())
        QCoreApplication.processEvents()  # 重绘界面

        self.get_shared_info_thread.set_values(self._disk, share_url, pwd,
                                               is_file, is_folder)
        self.get_shared_info_thread.start()

    def call_get_shared_info(self):
        if not self.get_shared_info_thread.isRunning():  # 防止阻塞主进程
            self.line_share_url.setEnabled(False)
            self.btn_extract.setEnabled(False)
            self.call_get_shared_info_worker()

    def show_share_url_file_lists(self, infos):
        if infos["code"] == LanZouCloud.SUCCESS:
            file_count = len(infos["info"].keys())
            self.model_share.setHorizontalHeaderLabels(
                ["文件{}个".format(file_count), "大小", "时间"])
            for infos in infos["info"].values():
                name = QStandardItem(self.set_file_icon(infos[1]), infos[1])
                name.setData(infos)
                self.model_share.appendRow(
                    [name,
                     QStandardItem(infos[2]),
                     QStandardItem(infos[3])])
            for r in range(self.model_share.rowCount()):  # 右对齐
                self.model_share.item(r, 1).setTextAlignment(Qt.AlignRight
                                                             | Qt.AlignVCenter)
                self.model_share.item(r, 2).setTextAlignment(Qt.AlignRight
                                                             | Qt.AlignVCenter)
            self.table_share.setDisabled(False)
            self.btn_share_select_all.setDisabled(False)
            self.btn_share_select_all.setToolTip("按下 Ctrl/Alt + A 全选或则取消全选")
            self.btn_share_dl.setDisabled(False)
        else:
            self.btn_share_select_all.setDisabled(True)
            self.btn_share_select_all.setToolTip("")
            self.btn_share_dl.setDisabled(True)
            self.table_share.setDisabled(True)

    def set_download_path(self):
        """设置下载路径"""
        dl_path = QFileDialog.getExistingDirectory()
        if dl_path == self.settings["path"]:
            return
        if dl_path == "":
            dl_path = os.path.dirname(
                os.path.abspath(__file__)) + os.sep + "downloads"
            up_info = {"path": dl_path}
        else:
            up_info = {"path": dl_path}
        update_settings(self._config, up_info)
        self.load_settings()
        self.line_dl_path.setText(self.settings["path"])

    def init_extract_share_ui(self):
        self.btn_share_select_all.setDisabled(True)
        self.btn_share_dl.setDisabled(True)
        self.table_share.setDisabled(True)
        self.model_share = QStandardItemModel(1, 3)
        self.config_tableview("share")
        self.get_shared_info_thread = GetSharedInfo()
        self.get_shared_info_thread.code.connect(self.show_status)  # 状态码
        self.get_shared_info_thread.infos.connect(
            self.show_share_url_file_lists)  # 信息
        self.get_shared_info_thread.finished.connect(
            lambda: self.btn_extract.setEnabled(True))
        self.get_shared_info_thread.finished.connect(
            lambda: self.line_share_url.setEnabled(True))
        self.line_share_url.setPlaceholderText(
            "蓝奏云链接,如有提取码,放后面,空格或汉字等分割,回车键提取")
        self.line_share_url.returnPressed.connect(self.call_get_shared_info)
        self.btn_extract.clicked.connect(self.call_get_shared_info)
        self.btn_share_dl.clicked.connect(self.call_downloader)
        self.btn_share_dl.setIcon(QIcon("./icon/downloader.ico"))
        self.btn_share_select_all.setIcon(QIcon("./icon/select-all.ico"))
        self.btn_share_select_all.clicked.connect(
            lambda: self.select_all_btn("reverse"))
        self.table_share.clicked.connect(lambda: self.select_all_btn("cancel"))

        # 添加文件下载路径选择器
        self.line_dl_path = MyLineEdit(self.share_tab)
        self.line_dl_path.setObjectName("line_dl_path")
        self.horizontalLayout_share_2.insertWidget(2, self.line_dl_path)
        self.line_dl_path.setText(self.settings["path"])
        self.line_dl_path.clicked.connect(self.set_download_path)

        # QSS
        self.label_share_url.setStyleSheet(
            "#label_share_url {color: rgb(255,255,60);}")
        self.label_dl_path.setStyleSheet(
            "#label_dl_path {color: rgb(255,255,60);}")

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_A:  # Ctrl/Alt + A 全选
            if e.modifiers() and Qt.ControlModifier:
                self.select_all_btn()

    def set_window_at_center(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        new_left = int((screen.width() - size.width()) / 2)
        new_top = int((screen.height() - size.height()) / 2)
        self.move(new_left, new_top)

    def open_wiki_url(self):
        # 打开使用说明页面
        url = QUrl('https://github.com/rachpt/lanzou-gui/wiki')
        if not QDesktopServices.openUrl(url):
            self.show_status('Could not open wiki page!', 5000)
Beispiel #3
0
class Uploader:
    default_sub_domain = "fzls"
    default_main_domain = "lanzouo"
    default_domain = f"{default_sub_domain}.{default_main_domain}.com"

    folder_dnf_calc = Folder("魔改计算器", "1810329",
                             f"https://{default_domain}/s/dnf-calc", "")
    folder_djc_helper = Folder("蚊子腿小助手", "2290618",
                               f"https://{default_domain}/s/djc-helper", "")
    folder_history_files = Folder("历史版本", "2303716",
                                  f"https://{default_domain}/b01bp17zg", "")
    folder_djc_helper_tools = Folder("蚊子腿小助手相关工具", "2291287",
                                     f"https://{default_domain}/s/djc-tools",
                                     "")
    folder_online_files = Folder("在线文件存储-v2", "3828082",
                                 f"https://{default_domain}/s/myfiles-v2",
                                 "3jte")
    folder_online_files_history_files = Folder(
        "历史版本-v2", "3828089", f"https://{default_domain}/myfiles-v2-history",
        "fwqi")

    history_version_prefix = "DNF蚊子腿小助手_v"
    history_patches_prefix = "DNF蚊子腿小助手_增量更新文件_"
    history_dlc_version_prefix = "auto_updater.exe"

    regex_version = r'DNF蚊子腿小助手_v(.+)_by风之凌殇.7z'
    regex_patches = r'DNF蚊子腿小助手_增量更新文件_v(.+)_to_v(.+).7z'

    # 保存购买了自动更新工具的用户信息
    buy_auto_updater_users_filename = "buy_auto_updater_users.txt"

    # 保存用户的付费信息
    user_monthly_pay_info_filename = "user_monthly_pay_info.txt"

    # 卡密操作的付费信息
    cs_used_card_secrets = "_used_card_secrets.txt"
    cs_buy_auto_updater_users_filename = "cs_buy_auto_updater_users.txt"
    cs_user_monthly_pay_info_filename = "cs_user_monthly_pay_info.txt"

    # 直接充值的付费信息
    all_jiaoyile_orders_filename = "_all_jiaoyile_orders_filepath.txt"

    # 压缩版本的前后缀
    compressed_version_prefix = "compressed_"
    compressed_version_suffix = ".7z"

    def __init__(self):
        self.lzy = LanZouCloud()
        self.login_ok = False

    def login(self, cookie):
        # 仅上传需要登录
        self.login_ok = self.lzy.login_by_cookie(cookie) == LanZouCloud.SUCCESS

    def upload_to_lanzouyun(self,
                            filepath: str,
                            target_folder: Folder,
                            history_file_prefix="",
                            delete_history_file=False,
                            also_upload_compressed_version=False,
                            only_upload_compressed_version=False) -> bool:
        if not self.login_ok:
            logger.info("未登录,不能上传文件")
            return False

        if history_file_prefix == "":
            # 未设置历史文件前缀,默认为当前文件名
            history_file_prefix = os.path.basename(filepath)

        if not only_upload_compressed_version:
            ok = self._upload_to_lanzouyun(filepath, target_folder,
                                           history_file_prefix,
                                           delete_history_file)
            if not ok:
                return False

        if also_upload_compressed_version:
            filename = os.path.basename(filepath)
            compressed_filepath = os.path.join(
                compressed_temp_dir,
                self.get_compressed_version_filename(filename))
            compressed_history_file_prefix = f"{self.compressed_version_prefix}{history_file_prefix}"

            logger.info(
                color("bold_green") + f"创建压缩版本并上传 {compressed_filepath}")
            # 创建压缩版本
            compress_file_with_lzma(filepath, compressed_filepath)
            # 上传
            return self._upload_to_lanzouyun(compressed_filepath,
                                             target_folder,
                                             compressed_history_file_prefix,
                                             delete_history_file)

        return True

    def _upload_to_lanzouyun(self,
                             filepath: str,
                             target_folder: Folder,
                             history_file_prefix,
                             delete_history_file=False) -> bool:
        if history_file_prefix == "":
            logger.error("未设置history_file_prefix")
            return False

        filename = os.path.basename(filepath)
        logger.warning(f"开始上传 {filename} 到 {target_folder.name}")
        run_start_time = datetime.now()

        def on_uploaded(fid, is_file):
            if not is_file:
                return

            logger.info(f"上传完成,fid={fid}")

            folder_history_files = self.folder_history_files
            if target_folder.id == self.folder_online_files.id:
                folder_history_files = self.folder_online_files_history_files

            files = self.lzy.get_file_list(target_folder.id)
            for file in files:
                if file.name.startswith(history_file_prefix):
                    if not delete_history_file:
                        self.lzy.move_file(file.id, folder_history_files.id)
                        logger.info(
                            f"将{file.name}移动到目录({folder_history_files.name})")
                    else:
                        self.lzy.delete(file.id, True)
                        logger.info(f"移除旧版本的{file.name}")

            logger.info(f"将文件移到目录({target_folder.name})中")
            self.lzy.move_file(fid, target_folder.id)

        # 上传到指定的文件夹中
        retCode = self.lzy.upload_file(filepath,
                                       -1,
                                       callback=self.show_progress,
                                       uploaded_handler=on_uploaded)
        if retCode != LanZouCloud.SUCCESS:
            logger.error(f"上传失败,retCode={retCode}")
            return False

        filesize = os.path.getsize(filepath)
        logger.warning(
            color("bold_yellow") +
            f"上传文件 {filename}({human_readable_size(filesize)}) 总计耗时{datetime.now() - run_start_time}"
        )

        return True

    def get_compressed_version_filename(self, filename: str) -> str:
        return f"{self.compressed_version_prefix}{filename}{self.compressed_version_suffix}"

    def latest_version(self) -> str:
        """
        返回形如"1.0.0"的最新版本信息
        """
        latest_version_file = self.find_latest_version()

        return self.parse_version_from_djc_helper_file_name(
            latest_version_file.name)

    def parse_version_from_djc_helper_file_name(self, filename: str) -> str:
        """
        从小助手压缩包文件名中提取版本信息
        DNF蚊子腿小助手_v4.6.6_by风之凌殇.7z => v4.6.6
        """
        match = re.search(self.regex_version, filename)
        if match is None:
            # 保底返回1.0.0
            return "1.0.0"

        return match.group(1)

    def download_latest_version(self, download_dir) -> str:
        """
        下载最新版本压缩包到指定目录,并返回最终压缩包的完整路径
        """
        # note: 如果哪天蓝奏云不可用了,可以尝试使用github的release,对于国内情况,使用其镜像来下载
        #   官网: https://github.com/fzls/djc_helper/releases/download/latest/djc_helper.7z
        #   镜像: https://download.fastgit.org/fzls/djc_helper/releases/download/latest/djc_helper.7z
        return self.download_file(self.find_latest_version(), download_dir)

    def find_latest_version(self) -> FileInFolder:
        """
        查找最新版本,如找到,返回lanzouyun提供的file信息,否则抛出异常
        """
        folder_info = self.get_folder_info_by_url(self.folder_djc_helper.url)
        for file in folder_info.files:
            if file.name.startswith(self.history_version_prefix):
                return file

        raise FileNotFoundError("latest version not found")

    def latest_patches_range(self):
        """
        返回形如("1.0.0", "1.1.2")的补丁范围
        """
        latest_patches_file = self.find_latest_patches()
        # DNF蚊子腿小助手_增量更新文件_v4.6.5_to_v4.6.6.7z
        match = re.search(self.regex_patches, latest_patches_file.name)
        if match is not None:
            version_left, version_right = match.group(1), match.group(2)
            return (version_left, version_right)

        # 保底返回
        return ("1.0.0", "1.0.0")

    def download_latest_patches(self, download_dir) -> str:
        """
        下载最新版本压缩包到指定目录,并返回最终压缩包的完整路径
        """
        return self.download_file(self.find_latest_patches(), download_dir)

    def find_latest_patches(self) -> FileInFolder:
        """
        查找最新版本的补丁,如找到,返回lanzouyun提供的file信息,否则抛出异常
        """
        folder_info = self.get_folder_info_by_url(self.folder_djc_helper.url)
        for file in folder_info.files:
            if file.name.startswith(self.history_patches_prefix):
                return file

        raise FileNotFoundError("latest patches not found")

    def download_latest_dlc_version(self, download_dir) -> str:
        """
        下载最新版本dlc压缩包到指定目录,并返回最终压缩包的完整路径
        """
        return self.download_file(self.find_latest_dlc_version(), download_dir)

    def find_latest_dlc_version(self) -> FileInFolder:
        """
        查找最新版本dlc,如找到,返回lanzouyun提供的file信息,否则抛出异常
        """
        folder_info = self.get_folder_info_by_url(self.folder_djc_helper.url)
        for file in folder_info.files:
            if file.name.startswith(self.history_dlc_version_prefix):
                return file

        raise FileNotFoundError("latest version not found")

    def download_file_in_folder(
            self,
            folder: Folder,
            name: str,
            download_dir: str,
            overwrite=True,
            show_log=True,
            try_compressed_version_first=False,
            cache_max_seconds=600,
            download_only_if_server_version_is_newer=True) -> str:
        """
        下载网盘指定文件夹的指定文件到本地指定目录,并返回最终本地文件的完整路径
        """
        def _download(fname: str) -> str:
            return with_cache(
                cache_name_download,
                os.path.join(folder.name, fname),
                cache_max_seconds=cache_max_seconds,
                cache_miss_func=lambda: self.
                download_file(self.find_file(folder, fname),
                              download_dir,
                              overwrite=overwrite,
                              show_log=show_log,
                              download_only_if_server_version_is_newer=
                              download_only_if_server_version_is_newer),
                cache_validate_func=lambda target_path: os.path.isfile(
                    target_path),
            )

        if try_compressed_version_first:
            # 先尝试获取压缩版本
            compressed_filename = self.get_compressed_version_filename(name)
            try:
                get_log_func(logger.info,
                             show_log)(color("bold_green") +
                                       f"尝试优先下载压缩版本 {compressed_filename}")

                # 记录下载前的最近修改时间
                before_download_last_modify_time = None
                old_compressed_filepath = os.path.join(download_dir,
                                                       compressed_filename)
                if os.path.isfile(old_compressed_filepath):
                    before_download_last_modify_time = parse_timestamp(
                        os.stat(old_compressed_filepath).st_mtime)

                # 下载压缩版本
                compressed_filepath = _download(compressed_filename)

                # 记录下载完成后的最近修改时间
                after_download_last_modify_time = parse_timestamp(
                    os.stat(compressed_filepath).st_mtime)

                # 解压缩
                dirname = os.path.dirname(compressed_filepath)
                target_path = os.path.join(dirname, name)

                need_decompress = True
                if before_download_last_modify_time is not None and before_download_last_modify_time == after_download_last_modify_time and os.path.exists(
                        target_path):
                    # 如果前后修改时间没有变动,说明没有实际发生下载,比如网盘版本与当前本地版本一致,如果此时目标文件已经解压过,将不再尝试解压
                    need_decompress = False

                if need_decompress:
                    decompress_file_with_lzma(compressed_filepath, target_path)
                else:
                    get_log_func(logger.info, show_log)(
                        f"{compressed_filepath}未发生改变,且目标文件已存在,无需尝试解压缩")
                # 返回解压缩的文件路径
                return target_path
            except Exception as e:
                get_log_func(logger.error, show_log)(
                    f"下载压缩版本 {compressed_filename} 失败,将尝试普通版本~", exc_info=e)

        # 下载普通版本
        return _download(name)

    def find_file(self, folder, name) -> FileInFolder:
        """
        在对应目录查找指定名称的文件,如找到,返回lanzouyun提供的file信息,否则抛出异常
        """
        folder_info = self.get_folder_info_by_url(folder.url, folder.password)
        for file in folder_info.files:
            if file.name == name:
                return file

        raise FileNotFoundError(
            f"file={name} not found in folder={folder.name}")

    def download_file(self,
                      fileinfo: FileInFolder,
                      download_dir: str,
                      overwrite=True,
                      show_log=True,
                      download_only_if_server_version_is_newer=True) -> str:
        """
        下载最新版本压缩包到指定目录,并返回最终压缩包的完整路径
        """
        make_sure_dir_exists(download_dir)

        download_dir = os.path.realpath(download_dir)
        target_path = StrWrapper(os.path.join(download_dir, fileinfo.name))

        if download_only_if_server_version_is_newer and os.path.isfile(
                target_path.value):
            # 仅在服务器版本比本地已有文件要新的时候才重新下载
            # 由于蓝奏云时间显示不精确,将其往前一分钟,避免同一文件下次检查时其蓝奏云时间显示为xx分钟前,解析后会有最多一分钟内的误差,而导致不必要的重新下载
            # 比如本次是x分y秒检查并更新,下次检查时是x+6分y+10秒,此时解析蓝奏云时间得到上传时间为x分y+10秒,就会产生额外的不必要下载
            server_version_upload_time = parse_time(
                fileinfo.time) - timedelta(minutes=1)
            local_version_last_modify_time = parse_timestamp(
                os.stat(target_path.value).st_mtime)

            get_log_func(
                logger.info, show_log
            )(f"{fileinfo.name} 本地修改时间为:{local_version_last_modify_time} 网盘版本上传时间为:{server_version_upload_time}"
              )

            if server_version_upload_time <= local_version_last_modify_time:
                # 暂无最新版本,无需重试
                get_log_func(
                    logger.info, show_log
                )(color("bold_cyan") +
                  f"当前设置了对比修改时间参数,网盘中最新版本 {fileinfo.name} 上传于{server_version_upload_time}左右,在当前版本{local_version_last_modify_time}之前,无需重新下载"
                  )
                return target_path.value

        def after_downloaded(file_name):
            """下载完成后的回调函数"""
            target_path.value = file_name
            get_log_func(logger.info, show_log)(f"最终下载文件路径为 {file_name}")

        get_log_func(logger.info, show_log)(f"即将开始下载 {target_path.value}")
        callback = None
        if show_log: callback = self.show_progress
        retCode = self.down_file_by_url(fileinfo.url,
                                        "",
                                        download_dir,
                                        callback=callback,
                                        downloaded_handler=after_downloaded,
                                        overwrite=overwrite)
        if retCode != LanZouCloud.SUCCESS:
            get_log_func(logger.error, show_log)(f"下载失败,retCode={retCode}")
            if retCode == LanZouCloud.NETWORK_ERROR:
                get_log_func(
                    logger.warning, show_log
                )(color("bold_yellow") +
                  ("蓝奏云api返回网络错误,这很可能是由于dns的问题导致的\n"
                   "分别尝试在浏览器中访问下列两个网页,是否一个打的开一个打不开?\n"
                   "https://fzls.lanzoux.com/s/djc-helper\n"
                   "https://fzls.lanzous.com/s/djc-helper\n"
                   "\n"
                   "如果是这样,请按照下面这个链接,修改本机的dns,使用阿里、腾讯、百度、谷歌dns中的任意一个应该都可以解决。\n"
                   "https://www.ypojie.com/9830.html\n"
                   "\n"
                   "如果两个都打不开,大概率是蓝奏云挂了-。-可选择忽略后面的弹框,继续运行旧版本,或者手动去QQ群或github下载最新版本"
                   ))
            raise Exception("下载失败")

        return target_path.value

    def show_progress(self, file_name, total_size, now_size):
        """显示进度的回调函数"""
        percent = now_size / total_size
        bar_len = 40  # 进度条长总度
        bar_str = '>' * round(bar_len * percent) + '=' * round(bar_len *
                                                               (1 - percent))
        show_percent = percent * 100
        now_mb = now_size / 1048576
        total_mb = total_size / 1048576
        print(
            f'\r{show_percent:.2f}%\t[{bar_str}] {now_mb:.2f}/{total_mb:.2f}MB | {file_name} ',
            end='')
        if total_size == now_size:
            print('')  # 下载完成换行

    def get_folder_info_by_url(self,
                               share_url,
                               dir_pwd='',
                               get_this_page=0) -> FolderDetail:
        for possiable_url in self.all_possiable_urls(share_url):
            try:
                folder_info = self.lzy.get_folder_info_by_url(
                    possiable_url, dir_pwd, get_this_page=get_this_page)
            except Exception as e:
                folder_info = FolderDetail(LanZouCloud.NETWORK_ERROR)
                logger.debug(f"get_folder_info_by_url {possiable_url} 出异常了",
                             exc_info=e)

            if folder_info.code != LanZouCloud.SUCCESS:
                logger.debug(f"请求{possiable_url}失败,将尝试下一个")
                continue

            return folder_info

        return FolderDetail(LanZouCloud.FAILED)

    def down_file_by_url(self,
                         share_url,
                         pwd='',
                         save_path='./Download',
                         *,
                         callback=None,
                         overwrite=False,
                         downloaded_handler=None) -> int:
        for possiable_url in self.all_possiable_urls(share_url):
            try:
                retCode = self.lzy.down_file_by_url(
                    possiable_url,
                    pwd,
                    save_path,
                    callback=callback,
                    overwrite=overwrite,
                    downloaded_handler=downloaded_handler)
            except Exception as e:
                retCode = LanZouCloud.NETWORK_ERROR
                logger.debug(f"down_file_by_url {possiable_url} 出异常了",
                             exc_info=e)

            if retCode != LanZouCloud.SUCCESS:
                logger.debug(f"请求{possiable_url}失败,将尝试下一个")
                continue

            return retCode

        return LanZouCloud.FAILED

    def all_possiable_urls(self, lanzouyun_url: str) -> List[str]:
        return self.lzy._all_possible_urls(lanzouyun_url)