import inspect import shutil import os from PyQt5.QtCore import QSize from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QFileDialog, \ QMessageBox, QInputDialog, QLineEdit, QPushButton, QHBoxLayout import ftplib from qgis.PyQt import QtWidgets from PyQt5 import QtCore QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) from PyQt5.QtWebEngineWidgets import * from PyQt5.QtCore import QUrl from typing import Optional from PyQt5.QtWebEngineWidgets import QWebEngineView from .FtpUitl import FtpOper # from .ModelViewer import ModelWebView class ModelWebView(QWebEngineView): def __init__( self, weburi: str, title: Optional[str] = "自定义窗口", windowW: Optional[int] = 1200, windowH: Optional[int] = 800, ): super().__init__() self.uri = weburi self.resize(windowW, windowH) self.setWindowTitle(title) self.page = QWebEnginePage() self.page.load(QUrl(self.uri)) self.setPage(self.page) class FtpManage(QtWidgets.QDockWidget): def __init__(self, iface): super().__init__() # 连接到FTP服务器 self.ftpHost = '127.0.0.1' self.ftpPort = 2021 self.ftpUsername = 'user1' self.ftpPassword = 'password' self.modelViewerUri = 'http://127.0.0.1:8091/website/#model=' self.modelBaseUri = 'http://127.0.0.1:8090' self.ftpOper = FtpOper() self.ftpOper.ftp.connect(self.ftpHost, self.ftpPort) self.ftpOper.ftp.login(self.ftpUsername, self.ftpPassword) self.fileItem = None self.ftpItem = None self.setupUi() self.iface = iface def setupUi(self): current_directory = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # 获取当前路径 # 构建布局 self.mainLayout = QVBoxLayout() self.setLayout(self.mainLayout) self.layoutH = QHBoxLayout() # 按钮布局 self.layoutV = QVBoxLayout() # 树结构的布局 self.mainLayout.addLayout(self.layoutH) self.mainLayout.addLayout(self.layoutV) # 为窗体添加布局 self.resize(600, 500) self.setWindowTitle("文件服务器") self.setWindowIcon(QIcon(os.path.join(current_directory + "\\icon", "logo.png"))) # 功能按钮区域 self.uploadFileButton = QPushButton(self) self.uploadFileButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件上传.png"))) self.uploadFileButton.setIconSize(QSize(32, 32)) self.uploadFileButton.setFixedSize(36, 36) self.uploadFileButton.setToolTip("上传文件") self.uploadFileButton.clicked.connect(self.actionUploadHandler) self.layoutH.addWidget(self.uploadFileButton) self.uploadFolderButton = QPushButton(self) self.uploadFolderButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件夹上传.png"))) self.uploadFolderButton.setIconSize(QSize(32, 32)) self.uploadFolderButton.setFixedSize(36, 36) self.uploadFolderButton.setToolTip("上传文件夹") self.uploadFolderButton.clicked.connect(self.actionUploadDirHandler) self.layoutH.addWidget(self.uploadFolderButton) self.downloadButton = QPushButton(self) self.downloadButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件下载.png"))) self.downloadButton.setIconSize(QSize(32, 32)) self.downloadButton.setFixedSize(36, 36) self.downloadButton.setToolTip("下载") self.downloadButton.clicked.connect(self.actionDownloadHandler) self.layoutH.addWidget(self.downloadButton) self.createDirButton = QPushButton(self) self.createDirButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "新建文件夹.png"))) self.createDirButton.setIconSize(QSize(32, 32)) self.createDirButton.setFixedSize(36, 36) self.createDirButton.setToolTip("新建目录") self.createDirButton.clicked.connect(self.actionCreateHandler) self.layoutH.addWidget(self.createDirButton) self.renameButton = QPushButton(self) self.renameButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件重命名.png"))) self.renameButton.setIconSize(QSize(32, 32)) self.renameButton.setFixedSize(36, 36) self.renameButton.setToolTip("重命名") self.renameButton.clicked.connect(self.actionRenameHandler) self.layoutH.addWidget(self.renameButton) self.deleteButton = QPushButton(self) self.deleteButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件删除.png"))) self.deleteButton.setIconSize(QSize(32, 32)) self.deleteButton.setFixedSize(36, 36) self.deleteButton.setToolTip("删除") self.deleteButton.clicked.connect(self.actionDeleteHandler) self.layoutH.addWidget(self.deleteButton) self.modelViewButton = QPushButton(self) self.modelViewButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "模型查看.png"))) self.modelViewButton.setIconSize(QSize(32, 32)) self.modelViewButton.setFixedSize(36, 36) self.modelViewButton.setToolTip("查看模型") self.modelViewButton.clicked.connect(self.action3DHandler) self.layoutH.addWidget(self.modelViewButton) self.openFileButton = QPushButton(self) self.openFileButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件查看.png"))) self.openFileButton.setIconSize(QSize(32, 32)) self.openFileButton.setFixedSize(36, 36) self.openFileButton.setToolTip("查看文件") self.openFileButton.clicked.connect(self.actionFileOpenHandler) self.layoutH.addWidget(self.openFileButton) # 目录树结构区 self.treeWidget = QTreeWidget() # QTreeWidget组件定义 self.treeWidget.headerItem().setText(0, "列表") # FTP服务器节点树 self.treeWidget.headerItem().setText(1, "类型") # 节点对应的类型:文件,文件夹 self.treeWidget.headerItem().setText(2, "路径") # 当前节点在FTP服务器中的路径,方便文件操作时获取服务器中路径,为隐藏列 self.treeWidget.setColumnWidth(0, 375) # 给第1列设置列宽 self.rootItem = QTreeWidgetItem() self.rootItem.setText(0, str(self.ftpOper.ftp.host)) # 给根节点增加文本 self.treeWidget.addTopLevelItem(self.rootItem) # 增加根节点 self.layoutV.addWidget(self.treeWidget) # 将组件添加到布局中 self.treeWidget.setColumnHidden(2, True) # 隐藏路径列 def actionUploadHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点 self.ftp_path = self.currentItem.parent().text(2) self.ftpItem = self.currentItem.parent() elif self.currentItem.text(1) == '文件夹': self.ftp_path = self.currentItem.text(2) self.ftpItem = self.currentItem file_paths, _ = QFileDialog.getOpenFileNames(None, '选择文件', '', 'All Files (*)') if len(file_paths) > 0: for file_path in file_paths: self.file_name = os.path.basename(file_path) self.ftpOper.uploadfile(file_path, self.ftp_path) self.fileItem = QTreeWidgetItem() self.fileItem.setText(0, self.file_name) self.fileItem.setText(1, '文件') self.fileItem.setText(2, self.ftp_path + '/' + self.file_name) # 已有同名节点不再增加此名称的节点 havesame = self.findTreeSameNode(self.ftpItem, self.file_name) if not havesame: self.ftpItem.addChild(self.fileItem) QMessageBox.information(self, "提示信息", "文件上传完成") else: QMessageBox.information(self, "提示信息", "请选择树节点") # 文件上传时查找目录树此节点的子节点中是否已有此名称 def findTreeSameNode(self, thisitem, filename): for i in range(thisitem.childCount()): child = thisitem.child(i) if child.text(0) == filename: return True else: return False # 文件夹上传操作 def actionUploadDirHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点 self.ftp_path = self.currentItem.parent().text(2) self.ftpItem = self.currentItem.parent() else: self.ftp_path = self.currentItem.text(2) self.ftpItem = self.currentItem dir_path = QFileDialog.getExistingDirectory(None, "选择文件夹", "") if dir_path: self.ftpOper.uploaddir(dir_path, self.ftp_path) dirname = os.path.split(dir_path)[-1] # 文件夹名称 rootchildItem = QTreeWidgetItem() rootchildItem.setText(0, dirname) rootchildItem.setText(1, '文件夹') rootchildItem.setText(2, self.ftp_path + '/' + dirname) self.ftpItem.addChild(rootchildItem) self.traverseLocalDirectory(dir_path, rootchildItem, self.ftp_path + '/' + dirname) QMessageBox.information(self, "提示信息", "文件夹上传完成") else: QMessageBox.information(self, "提示信息", "请选择树节点") # 遍历本地目录,构建可视化树结构 def traverseLocalDirectory(self, dirpath, item, ftppath): for file in os.listdir(dirpath): src = os.path.join(dirpath, file) childItem = QTreeWidgetItem() childItem.setText(0, file) if os.path.isfile(src): childItem.setText(1, '文件') childItem.setText(2, ftppath + '/' + file) item.addChild(childItem) elif os.path.isdir(src): childItem.setText(1, '文件夹') childItem.setText(2, ftppath + '/' + file) item.addChild(childItem) # 递归调用自身 self.traverseLocalDirectory(src, childItem, ftppath + '/' + file) # 文件下载操作 def actionDownloadHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() dir_path = QFileDialog.getExistingDirectory(None, "选择文件夹", "") if dir_path: if self.currentItem.text(1) == '文件': localfilepath = dir_path + '/' + self.currentItem.text(0) reomtefilepath = self.currentItem.text(2) self.ftpOper.downloadfile(localfilepath, reomtefilepath) else: localdirpath = dir_path + '/' + self.currentItem.text(0) remotedirpath = self.currentItem.text(2) self.ftpOper.downloaddir(localdirpath, remotedirpath) QMessageBox.information(self, "提示信息", "下载完成") else: QMessageBox.information(self, "提示信息", "请选择树节点") # 文件删除操作 def actionDeleteHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() questiontext = '是否要删除所选中的:【' + self.currentItem.text(0) + '】?' question = QMessageBox.question(None, '删除确认', questiontext) if question == QMessageBox.Yes: reomtefilepath = self.currentItem.text(2) if self.currentItem.text(1) == '文件': remotepath, remotefile_name = os.path.split(reomtefilepath) self.ftpOper.deletfile(remotefile_name, remotepath) # 删除FTP服务端文件 self.currentItem.parent().removeChild(self.currentItem) # 删除目录树对应的节点 else: self.ftpOper.deletedir(reomtefilepath) self.currentItem.parent().removeChild(self.currentItem) # 删除目录树对应的节点 else: QMessageBox.information(self, "提示信息", "请选择树节点") # 创建文件夹操作 def actionCreateHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() dirname, ok = QInputDialog.getText(QWidget(), '新建文件夹', '请输入文件夹名称:') if ok: if self.currentItem.text(1): if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点 self.dirpath = self.currentItem.parent().text(2) self.ftpItem = self.currentItem.parent() else: self.dirpath = self.currentItem.text(2) self.ftpItem = self.currentItem else: self.dirpath = '' self.ftpItem = self.currentItem self.newdirpath = self.dirpath + '/' + dirname self.rusulttext = self.ftpOper.makedir(dirname, self.dirpath, self.newdirpath) if self.rusulttext == '文件夹创建成功': self.fileItem = QTreeWidgetItem() self.fileItem.setText(0, dirname) self.fileItem.setText(1, '文件夹') self.fileItem.setText(2, self.newdirpath) self.ftpItem.addChild(self.fileItem) else: QMessageBox.information(self, "提示信息", "请选择树节点") # 重命名操作 def actionRenameHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() extension = None oldname = None oldpath = None if self.currentItem.text(1) == '文件': oldname, oldextension = os.path.splitext(self.currentItem.text(0)) elif self.currentItem.text(1) == '文件夹': oldname = self.currentItem.text(0) newname, ok = QInputDialog.getText(QWidget(), '重命名', '请输入新名称', QLineEdit.Normal, oldname) if ok: if self.currentItem.text(1) == '文件': oldpath = self.currentItem.text(2) pathparent, name1 = os.path.split(oldpath) name2, extension = os.path.splitext(name1) newpath = pathparent + '/' + newname + extension else: oldpath = self.currentItem.text(2) if not (self.currentItem.parent().text(1)): # 根目录 newpath = '/' + newname else: pathparent, name1 = os.path.split(oldpath) newpath = pathparent + '/' + newname result = self.ftpOper.rename(oldpath, newpath) if result: if extension: self.currentItem.setText(0, newname + extension) else: self.currentItem.setText(0, newname) self.currentItem.setText(2, newpath) else: QMessageBox.information(self, "提示信息", "请选择树节点") # 查看三维模型操作 def action3DHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() if (self.currentItem.text(1) == '文件'): modelUrl = self.modelViewerUri + self.modelBaseUri + self.currentItem.text(2) self.modelWebView = ModelWebView(weburi=modelUrl, title='数管系统三维模型浏览') self.modelWebView.show() else: QMessageBox.information(self, "提示信息", "请选择文件类型树节点") else: QMessageBox.information(self, "提示信息", "请选择树节点") # 打开文件操作 def actionFileOpenHandler(self): if self.treeWidget.currentItem(): self.currentItem = self.treeWidget.currentItem() if (self.currentItem.text(1) == '文件'): localdir = os.path.abspath(os.curdir) localtempdir = localdir + '/temp' if os.path.exists(localtempdir): if self.currentItem.text(1) == '文件': localfilepath = localtempdir + '/' + self.currentItem.text(0) reomtefilepath = self.currentItem.text(2) if os.path.exists(localfilepath): try: os.startfile(localfilepath) # 打开本地文件 except Exception as e: print(e) else: self.ftpOper.downloadfile(localfilepath, reomtefilepath) # 下载到本地临时文件夹 try: os.startfile(localfilepath) # 打开本地文件 except Exception as e: print(e) else: QMessageBox.information(self, "提示信息", "请选择文件类型树节点") else: QMessageBox.information(self, "提示信息", "请选择树节点") # 创建本地文件临时文件夹 def createTempDir(self): localdir = os.path.abspath(os.curdir) localtempdir = localdir + '/temp' if not (os.path.exists(localtempdir)): # 如不存在,创建本地临时文件夹 os.makedirs(localtempdir) else: # 如存在,删除后重新创建本地临时文件夹 shutil.rmtree(localtempdir) os.mkdir(localtempdir) # 遍历FTP服务端目录,构建可视化树结构 def traverseFTPDirectory(self, ftp, path, item): ftp.cwd(path) file_list = ftp.nlst() for file in file_list: try: ftp.cwd(path + '/' + file) # 尝试切换到子目录 # 添加子节点 childItem = QTreeWidgetItem() childItem.setText(0, file) childItem.setText(1, '文件夹') childItem.setText(2, (path + '/' + file).replace('//', '/')) item.addChild(childItem) # 递归调用自身 self.traverseFTPDirectory(ftp, (path + '/' + file).replace('//', '/'), childItem) ftp.cwd('..') # 返回到上一级目录 except ftplib.error_perm: # 添加子节点 childItem = QTreeWidgetItem() childItem.setText(0, file) childItem.setText(1, '文件') childItem.setText(2, (path + '/' + file).replace('//', '/')) item.addChild(childItem)