FtpClient.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. import inspect
  2. import shutil
  3. import os
  4. from PyQt5.QtCore import QSize
  5. from PyQt5.QtGui import QIcon
  6. from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTreeWidget, QTreeWidgetItem, QFileDialog, \
  7. QMessageBox, QInputDialog, QLineEdit, QPushButton, QHBoxLayout
  8. import ftplib
  9. from qgis.PyQt import QtWidgets
  10. from PyQt5 import QtCore
  11. QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
  12. from PyQt5.QtWebEngineWidgets import *
  13. from PyQt5.QtCore import QUrl
  14. from typing import Optional
  15. from PyQt5.QtWebEngineWidgets import QWebEngineView
  16. from .FtpUitl import FtpOper
  17. # from .ModelViewer import ModelWebView
  18. class ModelWebView(QWebEngineView):
  19. def __init__(
  20. self,
  21. weburi: str,
  22. title: Optional[str] = "自定义窗口",
  23. windowW: Optional[int] = 1200,
  24. windowH: Optional[int] = 800,
  25. ):
  26. super().__init__()
  27. self.uri = weburi
  28. self.resize(windowW, windowH)
  29. self.setWindowTitle(title)
  30. self.page = QWebEnginePage()
  31. self.page.load(QUrl(self.uri))
  32. self.setPage(self.page)
  33. class FtpManage(QtWidgets.QDockWidget):
  34. def __init__(self, iface):
  35. super().__init__()
  36. # 连接到FTP服务器
  37. self.ftpHost = '127.0.0.1'
  38. self.ftpPort = 2021
  39. self.ftpUsername = 'user1'
  40. self.ftpPassword = 'password'
  41. self.modelViewerUri = 'http://127.0.0.1:8091/website/#model='
  42. self.modelBaseUri = 'http://127.0.0.1:8090'
  43. self.ftpOper = FtpOper()
  44. self.ftpOper.ftp.connect(self.ftpHost, self.ftpPort)
  45. self.ftpOper.ftp.login(self.ftpUsername, self.ftpPassword)
  46. self.fileItem = None
  47. self.ftpItem = None
  48. self.setupUi()
  49. self.iface = iface
  50. def setupUi(self):
  51. current_directory = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # 获取当前路径
  52. # 构建布局
  53. self.mainLayout = QVBoxLayout()
  54. self.setLayout(self.mainLayout)
  55. self.layoutH = QHBoxLayout() # 按钮布局
  56. self.layoutV = QVBoxLayout() # 树结构的布局
  57. self.mainLayout.addLayout(self.layoutH)
  58. self.mainLayout.addLayout(self.layoutV) # 为窗体添加布局
  59. self.resize(600, 500)
  60. self.setWindowTitle("文件服务器")
  61. self.setWindowIcon(QIcon(os.path.join(current_directory + "\\icon", "logo.png")))
  62. # 功能按钮区域
  63. self.uploadFileButton = QPushButton(self)
  64. self.uploadFileButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件上传.png")))
  65. self.uploadFileButton.setIconSize(QSize(32, 32))
  66. self.uploadFileButton.setFixedSize(36, 36)
  67. self.uploadFileButton.setToolTip("上传文件")
  68. self.uploadFileButton.clicked.connect(self.actionUploadHandler)
  69. self.layoutH.addWidget(self.uploadFileButton)
  70. self.uploadFolderButton = QPushButton(self)
  71. self.uploadFolderButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件夹上传.png")))
  72. self.uploadFolderButton.setIconSize(QSize(32, 32))
  73. self.uploadFolderButton.setFixedSize(36, 36)
  74. self.uploadFolderButton.setToolTip("上传文件夹")
  75. self.uploadFolderButton.clicked.connect(self.actionUploadDirHandler)
  76. self.layoutH.addWidget(self.uploadFolderButton)
  77. self.downloadButton = QPushButton(self)
  78. self.downloadButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件下载.png")))
  79. self.downloadButton.setIconSize(QSize(32, 32))
  80. self.downloadButton.setFixedSize(36, 36)
  81. self.downloadButton.setToolTip("下载")
  82. self.downloadButton.clicked.connect(self.actionDownloadHandler)
  83. self.layoutH.addWidget(self.downloadButton)
  84. self.createDirButton = QPushButton(self)
  85. self.createDirButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "新建文件夹.png")))
  86. self.createDirButton.setIconSize(QSize(32, 32))
  87. self.createDirButton.setFixedSize(36, 36)
  88. self.createDirButton.setToolTip("新建目录")
  89. self.createDirButton.clicked.connect(self.actionCreateHandler)
  90. self.layoutH.addWidget(self.createDirButton)
  91. self.renameButton = QPushButton(self)
  92. self.renameButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件重命名.png")))
  93. self.renameButton.setIconSize(QSize(32, 32))
  94. self.renameButton.setFixedSize(36, 36)
  95. self.renameButton.setToolTip("重命名")
  96. self.renameButton.clicked.connect(self.actionRenameHandler)
  97. self.layoutH.addWidget(self.renameButton)
  98. self.deleteButton = QPushButton(self)
  99. self.deleteButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件删除.png")))
  100. self.deleteButton.setIconSize(QSize(32, 32))
  101. self.deleteButton.setFixedSize(36, 36)
  102. self.deleteButton.setToolTip("删除")
  103. self.deleteButton.clicked.connect(self.actionDeleteHandler)
  104. self.layoutH.addWidget(self.deleteButton)
  105. self.modelViewButton = QPushButton(self)
  106. self.modelViewButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "模型查看.png")))
  107. self.modelViewButton.setIconSize(QSize(32, 32))
  108. self.modelViewButton.setFixedSize(36, 36)
  109. self.modelViewButton.setToolTip("查看模型")
  110. self.modelViewButton.clicked.connect(self.action3DHandler)
  111. self.layoutH.addWidget(self.modelViewButton)
  112. self.openFileButton = QPushButton(self)
  113. self.openFileButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件查看.png")))
  114. self.openFileButton.setIconSize(QSize(32, 32))
  115. self.openFileButton.setFixedSize(36, 36)
  116. self.openFileButton.setToolTip("查看文件")
  117. self.openFileButton.clicked.connect(self.actionFileOpenHandler)
  118. self.layoutH.addWidget(self.openFileButton)
  119. # 目录树结构区
  120. self.treeWidget = QTreeWidget() # QTreeWidget组件定义
  121. self.treeWidget.headerItem().setText(0, "列表") # FTP服务器节点树
  122. self.treeWidget.headerItem().setText(1, "类型") # 节点对应的类型:文件,文件夹
  123. self.treeWidget.headerItem().setText(2, "路径") # 当前节点在FTP服务器中的路径,方便文件操作时获取服务器中路径,为隐藏列
  124. self.treeWidget.setColumnWidth(0, 375) # 给第1列设置列宽
  125. self.rootItem = QTreeWidgetItem()
  126. self.rootItem.setText(0, str(self.ftpOper.ftp.host)) # 给根节点增加文本
  127. self.treeWidget.addTopLevelItem(self.rootItem) # 增加根节点
  128. self.layoutV.addWidget(self.treeWidget) # 将组件添加到布局中
  129. self.treeWidget.setColumnHidden(2, True) # 隐藏路径列
  130. def actionUploadHandler(self):
  131. if self.treeWidget.currentItem():
  132. self.currentItem = self.treeWidget.currentItem()
  133. if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点
  134. self.ftp_path = self.currentItem.parent().text(2)
  135. self.ftpItem = self.currentItem.parent()
  136. elif self.currentItem.text(1) == '文件夹':
  137. self.ftp_path = self.currentItem.text(2)
  138. self.ftpItem = self.currentItem
  139. file_paths, _ = QFileDialog.getOpenFileNames(None, '选择文件', '', 'All Files (*)')
  140. if len(file_paths) > 0:
  141. for file_path in file_paths:
  142. self.file_name = os.path.basename(file_path)
  143. self.ftpOper.uploadfile(file_path, self.ftp_path)
  144. self.fileItem = QTreeWidgetItem()
  145. self.fileItem.setText(0, self.file_name)
  146. self.fileItem.setText(1, '文件')
  147. self.fileItem.setText(2, self.ftp_path + '/' + self.file_name)
  148. # 已有同名节点不再增加此名称的节点
  149. havesame = self.findTreeSameNode(self.ftpItem, self.file_name)
  150. if not havesame:
  151. self.ftpItem.addChild(self.fileItem)
  152. QMessageBox.information(self, "提示信息", "文件上传完成")
  153. else:
  154. QMessageBox.information(self, "提示信息", "请选择树节点")
  155. # 文件上传时查找目录树此节点的子节点中是否已有此名称
  156. def findTreeSameNode(self, thisitem, filename):
  157. for i in range(thisitem.childCount()):
  158. child = thisitem.child(i)
  159. if child.text(0) == filename:
  160. return True
  161. else:
  162. return False
  163. # 文件夹上传操作
  164. def actionUploadDirHandler(self):
  165. if self.treeWidget.currentItem():
  166. self.currentItem = self.treeWidget.currentItem()
  167. if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点
  168. self.ftp_path = self.currentItem.parent().text(2)
  169. self.ftpItem = self.currentItem.parent()
  170. else:
  171. self.ftp_path = self.currentItem.text(2)
  172. self.ftpItem = self.currentItem
  173. dir_path = QFileDialog.getExistingDirectory(None, "选择文件夹", "")
  174. if dir_path:
  175. self.ftpOper.uploaddir(dir_path, self.ftp_path)
  176. dirname = os.path.split(dir_path)[-1] # 文件夹名称
  177. rootchildItem = QTreeWidgetItem()
  178. rootchildItem.setText(0, dirname)
  179. rootchildItem.setText(1, '文件夹')
  180. rootchildItem.setText(2, self.ftp_path + '/' + dirname)
  181. self.ftpItem.addChild(rootchildItem)
  182. self.traverseLocalDirectory(dir_path, rootchildItem, self.ftp_path + '/' + dirname)
  183. QMessageBox.information(self, "提示信息", "文件夹上传完成")
  184. else:
  185. QMessageBox.information(self, "提示信息", "请选择树节点")
  186. # 遍历本地目录,构建可视化树结构
  187. def traverseLocalDirectory(self, dirpath, item, ftppath):
  188. for file in os.listdir(dirpath):
  189. src = os.path.join(dirpath, file)
  190. childItem = QTreeWidgetItem()
  191. childItem.setText(0, file)
  192. if os.path.isfile(src):
  193. childItem.setText(1, '文件')
  194. childItem.setText(2, ftppath + '/' + file)
  195. item.addChild(childItem)
  196. elif os.path.isdir(src):
  197. childItem.setText(1, '文件夹')
  198. childItem.setText(2, ftppath + '/' + file)
  199. item.addChild(childItem)
  200. # 递归调用自身
  201. self.traverseLocalDirectory(src, childItem, ftppath + '/' + file)
  202. # 文件下载操作
  203. def actionDownloadHandler(self):
  204. if self.treeWidget.currentItem():
  205. self.currentItem = self.treeWidget.currentItem()
  206. dir_path = QFileDialog.getExistingDirectory(None, "选择文件夹", "")
  207. if dir_path:
  208. if self.currentItem.text(1) == '文件':
  209. localfilepath = dir_path + '/' + self.currentItem.text(0)
  210. reomtefilepath = self.currentItem.text(2)
  211. self.ftpOper.downloadfile(localfilepath, reomtefilepath)
  212. else:
  213. localdirpath = dir_path + '/' + self.currentItem.text(0)
  214. remotedirpath = self.currentItem.text(2)
  215. self.ftpOper.downloaddir(localdirpath, remotedirpath)
  216. QMessageBox.information(self, "提示信息", "下载完成")
  217. else:
  218. QMessageBox.information(self, "提示信息", "请选择树节点")
  219. # 文件删除操作
  220. def actionDeleteHandler(self):
  221. if self.treeWidget.currentItem():
  222. self.currentItem = self.treeWidget.currentItem()
  223. questiontext = '是否要删除所选中的:【' + self.currentItem.text(0) + '】?'
  224. question = QMessageBox.question(None, '删除确认', questiontext)
  225. if question == QMessageBox.Yes:
  226. reomtefilepath = self.currentItem.text(2)
  227. if self.currentItem.text(1) == '文件':
  228. remotepath, remotefile_name = os.path.split(reomtefilepath)
  229. self.ftpOper.deletfile(remotefile_name, remotepath) # 删除FTP服务端文件
  230. self.currentItem.parent().removeChild(self.currentItem) # 删除目录树对应的节点
  231. else:
  232. self.ftpOper.deletedir(reomtefilepath)
  233. self.currentItem.parent().removeChild(self.currentItem) # 删除目录树对应的节点
  234. else:
  235. QMessageBox.information(self, "提示信息", "请选择树节点")
  236. # 创建文件夹操作
  237. def actionCreateHandler(self):
  238. if self.treeWidget.currentItem():
  239. self.currentItem = self.treeWidget.currentItem()
  240. dirname, ok = QInputDialog.getText(QWidget(), '新建文件夹', '请输入文件夹名称:')
  241. if ok:
  242. if self.currentItem.text(1):
  243. if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点
  244. self.dirpath = self.currentItem.parent().text(2)
  245. self.ftpItem = self.currentItem.parent()
  246. else:
  247. self.dirpath = self.currentItem.text(2)
  248. self.ftpItem = self.currentItem
  249. else:
  250. self.dirpath = ''
  251. self.ftpItem = self.currentItem
  252. self.newdirpath = self.dirpath + '/' + dirname
  253. self.rusulttext = self.ftpOper.makedir(dirname, self.dirpath, self.newdirpath)
  254. if self.rusulttext == '文件夹创建成功':
  255. self.fileItem = QTreeWidgetItem()
  256. self.fileItem.setText(0, dirname)
  257. self.fileItem.setText(1, '文件夹')
  258. self.fileItem.setText(2, self.newdirpath)
  259. self.ftpItem.addChild(self.fileItem)
  260. else:
  261. QMessageBox.information(self, "提示信息", "请选择树节点")
  262. # 重命名操作
  263. def actionRenameHandler(self):
  264. if self.treeWidget.currentItem():
  265. self.currentItem = self.treeWidget.currentItem()
  266. extension = None
  267. oldname = None
  268. oldpath = None
  269. if self.currentItem.text(1) == '文件':
  270. oldname, oldextension = os.path.splitext(self.currentItem.text(0))
  271. elif self.currentItem.text(1) == '文件夹':
  272. oldname = self.currentItem.text(0)
  273. newname, ok = QInputDialog.getText(QWidget(), '重命名', '请输入新名称', QLineEdit.Normal, oldname)
  274. if ok:
  275. if self.currentItem.text(1) == '文件':
  276. oldpath = self.currentItem.text(2)
  277. pathparent, name1 = os.path.split(oldpath)
  278. name2, extension = os.path.splitext(name1)
  279. newpath = pathparent + '/' + newname + extension
  280. else:
  281. oldpath = self.currentItem.text(2)
  282. if not (self.currentItem.parent().text(1)): # 根目录
  283. newpath = '/' + newname
  284. else:
  285. pathparent, name1 = os.path.split(oldpath)
  286. newpath = pathparent + '/' + newname
  287. result = self.ftpOper.rename(oldpath, newpath)
  288. if result:
  289. if extension:
  290. self.currentItem.setText(0, newname + extension)
  291. else:
  292. self.currentItem.setText(0, newname)
  293. self.currentItem.setText(2, newpath)
  294. else:
  295. QMessageBox.information(self, "提示信息", "请选择树节点")
  296. # 查看三维模型操作
  297. def action3DHandler(self):
  298. if self.treeWidget.currentItem():
  299. self.currentItem = self.treeWidget.currentItem()
  300. if (self.currentItem.text(1) == '文件'):
  301. modelUrl = self.modelViewerUri + self.modelBaseUri + self.currentItem.text(2)
  302. self.modelWebView = ModelWebView(weburi=modelUrl, title='数管系统三维模型浏览')
  303. self.modelWebView.show()
  304. else:
  305. QMessageBox.information(self, "提示信息", "请选择文件类型树节点")
  306. else:
  307. QMessageBox.information(self, "提示信息", "请选择树节点")
  308. # 打开文件操作
  309. def actionFileOpenHandler(self):
  310. if self.treeWidget.currentItem():
  311. self.currentItem = self.treeWidget.currentItem()
  312. if (self.currentItem.text(1) == '文件'):
  313. localdir = os.path.abspath(os.curdir)
  314. localtempdir = localdir + '/temp'
  315. if os.path.exists(localtempdir):
  316. if self.currentItem.text(1) == '文件':
  317. localfilepath = localtempdir + '/' + self.currentItem.text(0)
  318. reomtefilepath = self.currentItem.text(2)
  319. if os.path.exists(localfilepath):
  320. try:
  321. os.startfile(localfilepath) # 打开本地文件
  322. except Exception as e:
  323. print(e)
  324. else:
  325. self.ftpOper.downloadfile(localfilepath, reomtefilepath) # 下载到本地临时文件夹
  326. try:
  327. os.startfile(localfilepath) # 打开本地文件
  328. except Exception as e:
  329. print(e)
  330. else:
  331. QMessageBox.information(self, "提示信息", "请选择文件类型树节点")
  332. else:
  333. QMessageBox.information(self, "提示信息", "请选择树节点")
  334. # 创建本地文件临时文件夹
  335. def createTempDir(self):
  336. localdir = os.path.abspath(os.curdir)
  337. localtempdir = localdir + '/temp'
  338. if not (os.path.exists(localtempdir)): # 如不存在,创建本地临时文件夹
  339. os.makedirs(localtempdir)
  340. else: # 如存在,删除后重新创建本地临时文件夹
  341. shutil.rmtree(localtempdir)
  342. os.mkdir(localtempdir)
  343. # 遍历FTP服务端目录,构建可视化树结构
  344. def traverseFTPDirectory(self, ftp, path, item):
  345. ftp.cwd(path)
  346. file_list = ftp.nlst()
  347. for file in file_list:
  348. try:
  349. ftp.cwd(path + '/' + file) # 尝试切换到子目录
  350. # 添加子节点
  351. childItem = QTreeWidgetItem()
  352. childItem.setText(0, file)
  353. childItem.setText(1, '文件夹')
  354. childItem.setText(2, (path + '/' + file).replace('//', '/'))
  355. item.addChild(childItem)
  356. # 递归调用自身
  357. self.traverseFTPDirectory(ftp, (path + '/' + file).replace('//', '/'), childItem)
  358. ftp.cwd('..') # 返回到上一级目录
  359. except ftplib.error_perm:
  360. # 添加子节点
  361. childItem = QTreeWidgetItem()
  362. childItem.setText(0, file)
  363. childItem.setText(1, '文件')
  364. childItem.setText(2, (path + '/' + file).replace('//', '/'))
  365. item.addChild(childItem)