- 이번 프로그램은 이전글(2022.11.29 - [파이썬 실습/GUI,윈도우 FTP(파일전송)프로그램 만들기] - 파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 3.로그인-5)리스트뷰이벤트처리하기)에 이은 9번째 글로 FTP서버에 로그인 후 기본 기능인 서버에서 파일을 업로드하는 방법에 대하여 설명합니다.
(본 블로그의 내용은 유튜브 동영상(파이썬 GUI Window FTP(파일전송) 프로그램 만들기-4.기본기능-1)업다운로드(GUI/Window FTP program by Python-4.upload&down-1))에서 더욱 자세히 보실 수 있습니다.)
**본 프로그램은 아래 깃허브 사이트의 소스를 참고하여 작성하였으며, 세부내용은 아래 사이트를 참고하여 주시기 바랍니다.
[출처] 깃허브, f.tea.p | 작성자 armut
https://github.com/armut/f.tea.p
1. 로그인 후 로컬PC쪽 파일을 선택 후 '업로드=>' 버튼을 클릭하면 파일이 업로드 됩니다.
O 시작전 준비 사항
- 이번글을 실습하기 위해서는 이전글(2022.11.29 - [분류 전체보기] - 파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 2.사전 준비 작업)에서 설명한 ftp서버를 준비하셔야 합니다.
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic, QtCore, QtGui, QtWidgets
import os
import time
from PyQt5.QtGui import *
import ftplib
from ftplib import FTP
from ftplib import error_perm
form_class = uic.loadUiType("1.FTPClient2_C.ui")[0]
class FTPClass():
__instance = None
@staticmethod
def tea():
if not FTPClass.__instance:
raise Exception("FTPClass를 먼저 인스턴스화 해야 합니다.")
return FTPClass.__instance
@staticmethod
def reset():
FTPClass.__instance = None
def __init__(self, banana):
if FTPClass.__instance:
raise Exception("인스턴스가 이미 존재합니다.")
FTPClass.__instance = banana
class LoginWindow(QtWidgets.QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setFixedSize(820,900)
self.setWindowIcon(QIcon("pagichacha.png"))
self.setupUi(self)
self.lineEdit_5.returnPressed.connect(self.ftpConnect)
self.pushButton.clicked.connect(self.ftpConnect)
def ftpConnect(self):
global result
HOST = self.lineEdit.text()
ID = self.lineEdit_4.text()
PW = self.lineEdit_5.text()
# PORT = int(self.lineEdit_6.text())
FTPClass.reset()
ret = FTPClass(FTP(HOST))
print(ret,"+++++++++++++++++ 1")
FTPClass.tea().encoding = 'utf-8'
result = FTPClass.tea().login(ID, PW)
print(result)
if result and result.split(' ')[0] == '230':
print("로그인 성공!!")
# self.textEdit_2.setText(result+"로그인 성공!!")
self.myWindow = MyWindow()
self.myWindow.show()
self.close()
else:
print("Something went wrong.")
# self.textEdit_2.setText("로그인 실패!!!!, ID/PW 등을 정확히 입력했는지 확인해 보시기 바랍니다.!")
class MyWindow(QtWidgets.QMainWindow, form_class):
def __init__(self):
super().__init__()
self.local_path = None
self.remote_path = None
self.curr_local = None
self.curr_remote = None
self.setFixedSize(820,900)
self.setWindowIcon(QIcon("pagichacha.png"))
self.setupUi(self)
if result and result.split(' ')[0] == '230':
self.textEdit_2.setText(result+"로그인 성공!!")
else:
self.textEdit_2.setText(result+"로그인 실패!!")
self.populate_local()
self.populate_remote()
self.doConnections()
def tidy_up(self):
self.label_8.show()
# print(self.local_path,"++++ 1")
self.pushButton_12.hide()
# print(self.local_path,"++++ 1")
self.lineEdit_3.hide()
# print(self.local_path,"++++ 1")
self.label_8.setText('Ready.')
def doConnections(self):
self.pushButton_3.clicked.connect(self.retr)
self.pushButton_2.clicked.connect(self.stor)
def retr(self):
self.tidy_up()
print(self.curr_remote,"++++ 18")
if self.curr_remote is not None:
try:
r = FTPClass.tea().retrbinary('RETR' + ' ' + self.curr_remote, open(self.curr_remote, 'wb').write)
print(r,"++++ 19")
self.label_8.setText(r + ' ' + self.curr_remote)
except:
self.label_8.setText("폴더가 아닌 파일을 선택해야 합니다.")
try:
os.remove(self.curr_remote)
except:
pass
else:
self.label_8.setText("리모트 사이트의 파일을 선택한 후 '<='을 클릭하셔야 합니다.")
def stor(self):
self.tidy_up()
print(self.curr_local,"++++ 20")
if self.curr_local is not None:
try:
r = FTPClass.tea().storbinary('STOR ' + self.curr_local, open(self.curr_local, 'rb'))
print(r,"++++ 21")
self.label_8.setText(r + ' ' + self.curr_local)
self.populate_remote()
except error_perm:
self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.")
except:
self.label_8.setText("폴더가 아닌 파일을 선택해야 합니다.")
import traceback
traceback.print_exc()
else:
self.label_8.setText("로컬 사이트의 파일을 선택한 후 '=>'을 클릭해야 합니다.")
def populate_local(self):
# self.tidy_up()
self.local_path = os.getcwd() # 현재 디렉토리를 가져옴
print(self.local_path,"++++ 1")
self.fs_model = QtWidgets.QFileSystemModel() #파일시스템 모델의 객체 생성
print(self.fs_model,"++++ 2")
self.fs_model.setRootPath(self.local_path) # 현재 디렉토리를 루트로 설정
print(self.fs_model.setRootPath(self.local_path),"++++ 3")
self.treeView.setModel(self.fs_model) #트리뷰 설정
print(self.treeView.setModel(self.fs_model),"++++ 4")
self.treeView.setRootIndex(self.fs_model.index(self.local_path)) # 트리뷰의 루트를 현재 디렉토리로 지정
print(self.treeView.setRootIndex(self.fs_model.index(self.local_path)),"++++ 5")
print('local_path:', self.local_path)
self.treeView.selectionModel().selectionChanged.connect(self.update_curr_local)
def update_curr_local(self):
# global curr_local
self.tidy_up() # 화면 아래 레이블을 Ready로 바꿈
try:
index = self.treeView.selectedIndexes()[0]
print(index,"++++ 6")
# print(self.tree_l.selectedIndexes()[1],"++++ 6-1")
self.curr_local = index.model().itemData(index).get(0) # 딕셔너리의 첫번째 값(선택한 폴더 또는 파일)
print(index.model().itemData(index),"++++ 7")
print('curr_local: ', self.curr_local)
except:
print("curr_local 변수에 로컬 파일이 선택되지 않았습니다.")
self.curr_local = None
return self.curr_local
def populate_remote(self):
self.remote_path = FTPClass.tea().pwd()
print(self.remote_path,"++++ 8")
remote_listing = FTPClass.tea().nlst()
print(remote_listing,"++++ 9")
self.dir_model = QtGui.QStandardItemModel()
print(self.dir_model,"++++ 10")
for ls in remote_listing:
self.dir_model.appendRow(QtGui.QStandardItem(ls))
# print(self.dir_model.appendRow(QtGui.QStandardItem(ls)),"++++ 11")
# print(type(self.dir_model.appendRow(QtGui.QStandardItem(ls))),"++++ 11")
self.listView.setModel(self.dir_model)
print(self.listView.setModel(self.dir_model),"++++ 12")
print('remote_path: ', self.remote_path)
self.listView.selectionModel().selectionChanged.connect(self.update_curr_remote)
def update_curr_remote(self):
# global curr_remote
self.tidy_up()
try:
index = self.listView.selectedIndexes()[0]
print(index,"++++ 13")
self.curr_remote = index.model().itemData(index).get(0)
print(index.model().itemData(index),"++++ 14")
print('curr_remote: ', self.curr_remote)
except:
print("curr_remote 변수에 리모트 파일이 선택되지 않았습니다.")
self.curr_remote = None
return self.curr_remote
app=QApplication(sys.argv)
# window = MyWindow()
window = LoginWindow()
window.show()
print("Before event loop")
app.exec_()
print("After event loop")
- 아래 소스 분석 내용 중 1~38번 항목은 이전글과 동일하며, 39~41 항목만 새로 추가 되었습니다.
1.form_class = uic.loadUiType("1.FTPClient2_C.ui")[0] <-- qt disigner로 만든 UI를 사용하겠다.
2.class FTPClass(): <-- ftp서버 연결을 위한 클래스 선언
3.@staticmethod <-- 다른 클래스에서 사용 가능하도록 staticmethod 키워드를 사용(이 키워드 아래에 정의된 함수를 다른 클래스에서 사용 가능)
4.
@staticmethod
def tea(): <-- 인스턴스를 리턴
if not FTPClass.__instance:
raise Exception("FTPClass를 먼저 인스턴스화 해야 합니다.")
return FTPClass.__instance
5.
@staticmethod
def reset(): <-- 인스턴스를 'None'로 설정
FTPClass.__instance = None
6.
def __init__(self, banana): <-- 클래스의 인스턴스 초기화
if FTPClass.__instance:
raise Exception("인스턴스가 이미 존재합니다.")
FTPClass.__instance = banana
7.
class LoginWindow(QtWidgets.QMainWindow, form_class):
self.setupUi(self) <-- form_class UI를 사용하겠다
8.self.lineEdit_5.returnPressed.connect(self.ftpConnect) <-- lineEdit_5(패스워드) 입력 후 엔터 시 ftpConnect()함수 호출
9.self.pushButton.clicked.connect(self.ftpConnect) <-- pushButton(연결버튼) 클릭 시 ftpConnect()함수 호출
10. HOST = self.lineEdit.text() <-- lineEdit(호스트)의 값을 읽어서 HOST변수에 저장(아래 해설 동일함)
11.ID = self.lineEdit_4.text()
12.PW = self.lineEdit_5.text()
13.FTPClass.reset() <-- LoginWindow클래스 내에서 다른 클래스(FTPClass)의 함수를 직접 호출(@staticmethod로 선언해줘서 가능), 인스턴스를 'None'로 설정
14.ret = FTPClass(FTP(HOST)) <-- ftp서버에 연결
15.FTPClass.tea().encoding = 'utf-8' # 서버 접속 후 디렉토리 또는 파일명이 한글이면 깨지는 것을 방지
16.result = FTPClass.tea().login(ID, PW) <-- 연결된 ftp서버에 로그인 시도
17.
if result and result.split(' ')[0] == '230': <-- 서버쪽 리턴 값이 '230' 이면 로그인 성공
print("로그인 성공!!")
self.textEdit_2.setText(result+"로그인 성공!!") <-- 화면에 '로그인 성공' 메시지 출력
else:
print("Something went wrong.")
self.textEdit_2.setText("로그인 실패!!!!, ID/PW 등을 정확히 입력했는지 확인해 보시기 바랍니다.!")
18.
def ftpConnect(self):
global result <-- 다른 클래스에서 result변수를 사용하기 위해 global 키워드 사용
19.
if result and result.split(' ')[0] == '230':
print("로그인 성공!!")
# self.textEdit_2.setText(result+"로그인 성공!!")
self.myWindow = MyWindow() <-- MyWindow클래스의 myWindow인스턴스 생성
self.myWindow.show() <-- myWindow인스턴스의 show()함수로 새창을 띄움
self.close() <-- 기존 창은 close
20.
class MyWindow(QtWidgets.QMainWindow, form_class):
if result and result.split(' ')[0] == '230':
self.textEdit_2.setText(result+"로그인 성공!!") <-- 새창에 로그인 메시지를 보여주기 위해 MyWindow클래스에 넣어줌
else:
self.textEdit_2.setText(result+"로그인 실패!!")
21.self.populate_local() <-- 초기화 함수에서 populate_local()함수 호출
22.
def populate_local(self):
self.local_path = os.getcwd() <-- 현재 디렉토리를 가져옴
self.fs_model = QtWidgets.QFileSystemModel() <-- 파일시스템 모델의 객체 생성
self.fs_model.setRootPath(self.local_path) <-- 현재 디렉토리를 루트로 설정
self.treeView.setModel(self.fs_model) <-- 트리뷰 설정
self.treeView.setRootIndex(self.fs_model.index(self.local_path)) <-- 트리뷰의 루트를 현재 디렉토리로 지정
print('local_path:', self.local_path)
23.
def tidy_up(self):
self.label_8.show() <-- label_8을 보여줌
self.pushButton_12.hide() <-- pushButton_12을 숨김
self.lineEdit_3.hide() <-- lineEdit_3 을 숨김
self.label_8.setText('Ready.') <-- label_8의 내용을 'Ready.' 로 설정
24.self.treeView.selectionModel().selectionChanged.connect(self.update_curr_local) <-- 트리뷰의 선택 이벤트 발생시 update_curr_local()함수 호출
25.
def update_curr_local(self):
self.tidy_up() <-- 화면 아래 레이블을 Ready로 바꿈
try:
index = self.treeView.selectedIndexes()[0] <-- 트리뷰 인덱스 설정
self.curr_local = index.model().itemData(index).get(0) <-- 딕셔너리의 첫번째 값(선택한 폴더 또는 파일)을 curr_local변수에 할당
print('curr_local: ', self.curr_local)
except:
print("curr_local 변수에 로컬 파일이 선택되지 않았습니다.")
self.curr_local = None
return self.curr_local
26.self.populate_remote() <-- 초기화 함수에서 리모트 서버의 파일/폴더목록을 보여주기 위해 populate_remote()함수 호출
27.self.remote_path = FTPClass.tea().pwd() <-- 리모트 서버의 현재 경로('/') 획득
28.remote_listing = FTPClass.tea().nlst() <-- 리모트 서버의 파일과 폴더 목록 획득
29.self.dir_model = QtGui.QStandardItemModel() <-- 모델객체 생성
30.
for ls in remote_listing: <-- 파일/폴더 목록을 하나씩 돌면서
self.dir_model.appendRow(QtGui.QStandardItem(ls)) <--모델객체에 append
31.self.listView.setModel(self.dir_model) <-- 모델객체를 리스트뷰에 적용(여기서 실제 리스트뷰에 파일과 폴더 목록이 나타남)
32.self.listView.selectionModel().selectionChanged.connect(self.update_curr_remote) <-- 리스트뷰에서 파일 또는 폴더 선택시 update_curr_remote()함수 호출
33.index = self.listView.selectedIndexes()[0] <-- 리스트뷰 인덱스 설정
34.self.curr_remote = index.model().itemData(index).get(0) <-- 딕셔너리의 첫번째 값(선택한 폴더 또는 파일)을 curr_remote변수에 할당
35.self.doConnections() <-- doConnections()함수를 초기화 함수에 포함
36.
def doConnections(self): <-- doConnections()함수내에 ftp서버의 다양한 기능을 하는 함수를 추가해 줌
self.pushButton_3.clicked.connect(self.retr) <-- pushButton_3(다운로드 버튼) 클릭 시 retr()함수 호출
37.if self.curr_remote is not None: <-- 파일 또는 폴더가 선택되어 있지 않으면
38.r = FTPClass.tea().retrbinary('RETR' + ' ' + self.curr_remote, open(self.curr_remote, 'wb').write) <-- FTPClass클래스의 인스턴스 생성 후 retrbinary()함수를 이용하여 파일다운로드 수행 후 서버쪽 결과값을 r변수에 저장
39.self.pushButton_2.clicked.connect(self.stor) <-- pushButton_2(업로드 버튼) 클릭 시 stor()함수 호출
40.if self.curr_local is not None: <-- 로컬PC의 파일 또는 폴더가 선택되어 있지 않으면
41.r = FTPClass.tea().storbinary('STOR ' + self.curr_local, open(self.curr_local, 'rb'))<-- FTPClass클래스의 인스턴스 생성 후 storbinary()함수를 이용하여 파일업로드 수행 후 서버쪽 결과값을 r변수에 저장
1. 로그인 후 로컬PC쪽 파일을 선택 후 '업로드=>' 버튼을 클릭하면 파일이 업로드 됩니다.
아래의 경우 로컬PC의 '1.testConnect9.py' 파일을 선택 후 업로드 버튼을 클릭하여 파일을 정상적으로 업로드 한 모습을 보여주며, 왼쪽 하단 레이블에 정상적으로 파일을 전송했다는 서버쪽 메시지를 보여주고 있습니다.
2, 만일 파일을 선택하지 않고 '업로드=>' 버튼을 클릭하는 경우 선택한 후 업로드 할 수 있도록 관련 메시지를 왼쪽 하단 레이블에 뿌려줍니다.
3. 또한 파일이 아닌 폴더를 선택하고 '업로드=>' 버튼을 클릭하는 경우에도 폴더가 아닌 파일을 선택해야 한다는 메시지를 뿌려줍니다.
4. doConnections()함수 내에 아래와 같이 코딩해 주면 pushButton_2버튼(업로드버튼) 클릭시 stor()함수가 실행됩니다.
self.pushButton_2.clicked.connect(self.stor)
stor()함수는 로컬PC의 파일(또는 폴더)이 선택되어 있는지 확인(if self.curr_local is not None:) 후 선택되어 있지 않으면 파일을 선택할 수 있도록 안내 메시지를 왼쪽 하단 레이블에 뿌려줍니다.
파일 또는 폴더가 선택되어 있다면, if구문을 실행하고, 폴더를 선택하여 업로드시 퍼미션 관련 에러가 발생하므로 여기서 다시 try~except 구문을 활용하여 except 이하(error_perm)에 안내 메시지를 출력합니다.('파일을 선택해야 합니다.')
만일 정상적으로 파일을 선택한 상태에서 '업로드=>' 버튼을 클릭한 경우 서버쪽 파일 업로드 명령(storbinary('STOR '~~~))을 실행하여 로컬 파일을 서버에 업로드 하고, 서버쪽 실행 결과 값을 r변수로 받아 이를 왼쪽 하단의 레이블에 보여줍니다.
오늘은 여기까지이며, 위의 내용이 유익하셨다면, 좋아요와 구독 부탁드립니다.
감사합니다.
'파이썬 실습 > GUI,윈도우 FTP(파일전송)프로그램 만들기' 카테고리의 다른 글
파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 4.기본기능-4)상위폴더이동(리모트) (0) | 2022.12.06 |
---|---|
파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 4.기본기능-3)하위폴더이동(리모트) (0) | 2022.12.06 |
파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 4.기본기능-1)다운로드 (0) | 2022.12.06 |
파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 3.로그인-5)리스트뷰이벤트처리하기 (0) | 2022.12.03 |
파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 3.로그인-4)원격디렉토리목록보기 (0) | 2022.12.03 |