FtpClient.py 22 KB

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