파이썬 실습/GUI,윈도우 FTP(파일전송)프로그램 만들기

파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 5.부가기능-2)폴더생성 및 삭제(리모트)(최종)

파기차차 2022. 12. 23. 21:10
728x90
반응형
SMALL
728x90
반응형

ㅁ 개요

 

O 프로그램 소개

 

 

 - 이번 글은 이전글(2022.12.02 - [파이썬 실습/GUI,윈도우 FTP(파일전송)프로그램 만들기] - 파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 4.기본기능-5)상하위폴더이동(로컬))에 이은 13번째 마지막 글로 FTP서버에 로그인 후 부가 기능인 리모트서버에서 폴더를 생성하고 삭제하는 방법에 대하여 설명합니다.

(본 블로그의 내용은 유튜브 동영상(파이썬 GUI Window FTP(파일전송) 프로그램 만들기-5.부가기능-2)폴더생성&삭제(GUI/Window FTP program by Python-5.CreateFold-2))에서 더욱 자세히 보실 수 있습니다.)

 

 

 

 

**본 프로그램은 아래 깃허브 사이트의 소스를 참고하여 작성하였으며, 세부내용은 아래 사이트를 참고하여 주시기 바랍니다.
[출처] 깃허브, f.tea.p | 작성자 armut
https://github.com/armut/f.tea.p

 

 

 

O 완성된 프로그램 실행 화면

 

 

 - 최종 완성된 프로그램의 결과화면은 아래와 같습니다.

 

1. 먼저 리모트 서버의 파일 이름은 변경하는 기능입니다.

 

처음 서버 로그인 후 아래와 같은 화면이 나타납니다.

 

 

 

아래와 같이 순서대로 (1)파일선택(2)이름변경 버튼을 클릭하면 왼쪽 하단에 라인에디트/푸시버튼이 나타나는데, 여기에 (3)변경하고자 하는 이름을 입력하고 (4)'OK' 버튼을 클릭하면

 

 

 

 

아래 (3)과 같이 이름이 정상적으로 변경된 것을 확인 할 수 있으며, (1)왼쪽 하단 레이블에 서버쪽 결과값을 보여줍니다.

 

 

 

 

 

2. 다음은 리모트 서버의 파일을 삭제하는 기능입니다.

 

아래와 같이 파일이 아닌 폴더를 선태 후 '파일삭제' 버튼 클릭 시 '권한관련 에러 발생' 으로 오류 메시지를 띄워 줍니다.

 

 

 

 다시 정상적으로 파일을 선택 후 '파일 삭제' 버튼 클릭 시

 

 

 

 

 'aa.txt' 파일이 정상적으로 삭제된 것을 확인할 수 있습니다.

 

 

 

 


 

O 시작전 준비 사항

 

 

 - 이번글을 실습하기 위해서는 이전글(2022.11.29 - [분류 전체보기] - 파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기 - 2.사전 준비 작업)에서 설명한 ftp서버를 준비하셔야 합니다.

 


 

ㅁ 세부 내용

 

O 완성된 소스

 

# -*- 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.FTPClient3_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, pagichacha):
        if FTPClass.__instance:
            raise Exception("인스턴스가 이미 존재합니다.")
        FTPClass.__instance = pagichacha


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)

        # 리모트 명령
        self.pushButton_4.clicked.connect(self.rnfr_rnto)
        self.pushButton_5.clicked.connect(self.dele)
        self.pushButton_6.clicked.connect(self.rmd)
        self.pushButton_7.clicked.connect(self.mkd)

        self.pushButton_16.clicked.connect(self.remote_enter)
        self.pushButton_15.clicked.connect(self.remote_up)

        # 로컬 명령
        # self.btn_rn.clicked.connect(self.rnfr_rnto)
        # self.btn_dele.clicked.connect(self.dele)
        # # self.btn_dele_2.clicked.connect(self.dele_local)
        # self.btn_rmd.clicked.connect(self.rmd)
        # self.btn_mkd.clicked.connect(self.mkd)
        
        self.pushButton_13.clicked.connect(self.local_enter)
        self.pushButton_14.clicked.connect(self.local_up)


    def local_enter(self):
        self.tidy_up()
        if self.curr_local:
            try:
                os.chdir(os.path.join(self.local_path, self.curr_local))
                print(self.local_path, self.curr_local,"++++ 15")
                self.label_8.setText(self.local_path+"\'"+self.curr_local)
                self.populate_local()
                self.curr_local = None
            except:
                self.label_8.setText("파일이 아닌 폴더를 선택해 주시기 바랍니다.!!")
            
        else:
            self.label_8.setText("로컬 폴더 선택 후 '선택 폴더진입->' 버튼을 클릭해 주시기 바랍니다.!!")


    def local_up(self):
        self.tidy_up()
        os.chdir('..')
        self.populate_local()





    def mkd(self):
        self.tidy_up()
        print(self.remote_path)
        if self.remote_path is not None:
            try: self.pushButton_12.clicked.disconnect()
            except: pass
            self.pushButton_12.clicked.connect(self.apply_mkd)
            self.lineEdit_3.show()
            self.pushButton_12.show()
            self.label_8.hide()

    def apply_mkd(self):
        new_name = self.lineEdit_3.text()
        print(new_name)
        if new_name != '':
            try:
                r = FTPClass.tea().mkd(new_name)
                print(r)
                self.tidy_up()
                self.label_8.setText('새로 생성된 폴더명 : ' + r)
                self.populate_remote()
                if '550' in r: # 퍼미션 에러가 발생하면 아래 익셉션에 rsise 한다.
                    raise Exception(r)
            except Exception as e:
                self.tidy_up()
                self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.: "+ str(e))
                import traceback
                traceback.print_exc()
            except:
                self.tidy_up()
                self.label_8.setText("파일이 아닌 폴더를 선택해야 합니다.")
                import traceback
                traceback.print_exc()
        else:
            self.label_8.setText("폴더명을 입력 후 'OK' 버튼을 클릭하시기 바랍니다.")


    def rmd(self):
        self.tidy_up()
        if self.curr_remote is not None:
            try:
                r = FTPClass.tea().rmd(self.curr_remote)
                self.label_8.setText(r + ' : ' + self.curr_remote)
                self.populate_remote()
                if '550' in r: # 퍼미션 에러가 발생하면 아래 익셉션에 rsise 한다.
                    raise Exception(r)
            except Exception as e:
                self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.(파일선택 X): "+ str(e))
                import traceback
                traceback.print_exc()
            except:
                self.label_8.setText("파일이 아닌 폴더를 선택해야 합니다.")
                import traceback
                traceback.print_exc()
        else:
            self.label_8.setText("리모트 폴더 선택 후 '폴더삭제' 버튼을 클릭해 주시기 바랍니다.!!")



    def dele(self):
        self.tidy_up()
        print(self.curr_remote,"++++ 23")
        curr_remote = self.update_curr_remote() #<------ 여기서  부터 다시 시작
        print(curr_remote,"++++ 24")
        curr_local = self.update_curr_local()
        print(curr_local,"++++ 25")
        if self.curr_remote is not None:
            try:
                r = FTPClass.tea().delete(self.curr_remote)
                print(r,"++++ 22")
                self.label_8.setText(r + ' : ' + self.curr_remote)
                self.populate_remote()
                if '550' in r: # 퍼미션 에러가 발생하면 아래 익셉션에 rsise 한다.
                    raise Exception(r)

            except Exception as e:
                self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.(폴더선택 X): "+ str(e))
            except:
                self.label_8.setText("폴더가 아닌 파일을 선택해야 합니다.")
                import traceback
                traceback.print_exc()
        else:
            self.label_8.setText("리모트 파일 선택 후 '파일삭제' 버튼을 클릭해 주시기 바랍니다.!!")


    def remote_enter(self):
        self.tidy_up()
        if self.curr_remote:
            FTPClass.tea().cwd(os.path.join(self.remote_path, self.curr_remote))
            # print(FTPClass.tea().cwd(os.path.join(self.remote_path, self.curr_remote)))
            self.remote_path2 = FTPClass.tea().pwd()
            self.label_8.setText("현재 위치(리모트): "+self.remote_path2)
            print(self.remote_path, self.curr_remote,"++++ 16")
            self.populate_remote()
            self.curr_remote = None
        else:
            self.label_8.setText("리모트 폴더 선택 후 '폴더진입->' 버튼을 클릭해 주시기 바랍니다.")

    def remote_up(self):
        self.tidy_up()
        # Waffle.tea().cwd((os.sep).join(self.remote_path.split(os.sep)[:-1]))
        FTPClass.tea().cwd('..')
        # print(os.sep, self.remote_path,"++++ 17")
        self.remote_path2 = FTPClass.tea().pwd()
        self.label_8.setText("현재 위치(리모트): "+self.remote_path2)
        self.populate_remote()


    def rnfr_rnto(self):
        if self.curr_remote is not None:
            try: self.pushButton_12.clicked.disconnect()
            except: pass
            self.pushButton_12.clicked.connect(self.apply_rn)
            self.lineEdit_3.show()
            self.pushButton_12.show()
            self.label_8.hide()
        else:
            self.label_8.setText("리모트 사이트의 파일을 선택 하셔야 합니다.")

    def apply_rn(self):
        new_name = self.lineEdit_3.text()
        if new_name != '':
            try:
                r = FTPClass.tea().rename(self.curr_remote, new_name)
                self.tidy_up()
                self.label_8.setText(r + ': ' + new_name)
                self.populate_remote()
            except error_perm:
                self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.")
            except:
                self.label_8.setText("폴더가 아닌 파일을 선택해야 합니다.")
                import traceback
                traceback.print_exc()
        else:
            self.tidy_up()
            self.label_8.setText("리모트 파일의 변경할 이름을 입력해야 합니다.")


    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")

 

O 소스 다운로드 및 실행

 
 - 소스파일 다운로드 후 cmd 또는 파워쉘 등에서 아래와 같이 실행하시기 바랍니다.
( '1.FTPClient3_C.ui' 파일은 실행 파일(1.testConnect17.py)과 동일한 경로에 위치 시켜야 합니다.)
 > python 1.testConnect17.py

 

 


 
 

O 주요 내용

 

1. 먼저 리모트 서버의 폴더를 생성하는 기능입니다.

 

로그인 후 아래와 같이 (1)폴더생성 버튼 클릭, (2)왼쪽 하단 레이블에 변경할 폴더 이름 입력 후(3) 'OK'버튼을 클릭 합니다.

 

아래의 경우 이미 존재하는 폴더와 동일한 이름으로 폴더를 생성하려고 하고 있습니다.

 

 

 

 

그러면 아래 왼쪽 레이블에 서버쪽 오류 메시지를 보여줍니다.(550 File exists)

 

 

 

다시 폴더생성 버튼을 클릭 후 존재하지 않는 폴더명을 입렵하고, 'OK' 버튼을 누르면

 

 

 

 

 

새로운 폴더('cc')가 생성된 것을 볼 수 있습니다.

 

 

 

 

 

 

pushButton_7(폴더생성) 버튼 클릭시 mkd()함수를 호출합니다.

remote_path('/')변수에 값이 있으면('if self.remote_path is not None:') if구문 실행하고, pushButton_12('OK') 버튼 클릭시 apply_mkd()함수를 호출합니다.

 

apply_mkd()함수는 실제 리모트서버에서 폴더를 생성하는 기능을 하는데, 'lineEdit_3.text()'으로 입력된 폴더명을 가져와서 실제 서버쪽 폴더생성 명령을 실행 후 r 변수에 서버의 결과값을 저장합니다.('r = FTPClass.tea().mkd(new_name)')

만약 에러('550')가 발생할 경우 에러를 확인하기 위하여 try~raise구문을 이용하여 서버쪽 에러를 Exception() 시켜 줍니다.

(아래 빨간색 부분을 유심히 봐주시기 바랍니다.)

 

        try:
            r = FTPClass.tea().mkd(new_name) <-- 실제 서버쪽 폴더생성 명령 실행 
            if '550' in r: <-- 서버쪽 결과값에 퍼미션 에러('550')가 발생하면 아래 익셉션을 rsise 한다.
                raise Exception(r)
        except Exception as e: <-- 익셉션을 e로 설정하고
            self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.: "+ str(e)) <-- e를 스트링으로 변환하여 label_8에 보여준다

 

 

 

 

 

 

 

 

2. 다음은 리모트 서버의 폴더를 삭제하는 기능입니다.

 

폴더삭제 시 아래와 같이 파일을 선택 후 폴더삭제 버튼을 클릭하면 왼쪽 하단 레이블에 '폴더가 아니다' 라는 서버쪽 메시지를 뿌려줍니다.

 

 

 

 

 

만일 빈 폴더가 아닌 폴더를 선택 후 폴더삭제 버튼 클릭 시 아래와 같이 '빈 폴더(디렉토리)가 아니다'라는 메시지를 볼 수 있습니다.(폴더 삭제 안됨)

 

 

 

 

 

이제 정상적으로 폴더(kkkk)를 선택 후 '폴더삭제' 버튼을 클릭하면

 

 

 

 

폴더 'kkkk'가 삭제된 것을 확인할 수 있습니다.

 

 

 

 

 

 

 

pushButton_6(폴더삭제) 버튼 클릭시 rmd()함수를 호출합니다.

폴더(또는 파일)를 선택했으면 if문을 실행합니다.(if self.curr_remote is not None: )

 

그리고 나서 실제 서버쪽 폴더 삭제 명령을 수행 후 r 변수에 서버쪽 결과값을 저장합니다.

r = FTPClass.tea().rmd(self.curr_remote)

 

만약 에러('550')가 발생할 경우 에러를 확인하기 위하여 try~raise구문을 이용하여 서버쪽 에러를 Exception() 시켜 줍니다.

(아래 빨간색 부분을 유심히 봐주시기 바랍니다.)

 

        try:
            r = FTPClass.tea().mkd(new_name) <-- 실제 서버쪽 폴더생성 명령 실행 
            if '550' in r: <-- 서버쪽 결과값에 퍼미션 에러('550')가 발생하면 아래 익셉션을 rsise 한다.
                raise Exception(r)
        except Exception as e: <-- 익셉션을 e로 설정하고
            self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.(파일선택 X): "+ str(e)) <-- e를 스트링으로 변환하여 label_8에 보여준다

 

 

 

 

 

 

 

 

 

 

 


 

 

O 소스 분석

(내용이 많으므로 필요한 부분만 보시면 좋습니다.)

 

- 아래 소스 분석 내용 중 1~57번 항목은 이전글과 동일하며, 58~60 항목만 새로 추가 되었습니다.

 

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변수에 저장

42.self.pushButton_16.clicked.connect(self.remote_enter) <-- pushButton_16 버튼('선택 폴더진입->') 클릭시 remote_enter()함수 호출

43.if self.curr_remote: <-- 폴더(또는 파일) 선택 시 if문 실행
44.FTPClass.tea().cwd(os.path.join(self.remote_path, self.curr_remote)) <-- 서버쪽 cwd()함수로 경로 변경(해당 폴더로 이동)
45.self.remote_path2 = FTPClass.tea().pwd() <-- 왼쪽 하단 레이블에 이동한 경로를 표시해 주기 위해 현재 위치(pwd())를 가져옴
46.self.label_8.setText("현재 위치(리모트): "+self.remote_path2) <-- 위에서 가져온 현재 위치를 레이블에 보여줌47.self.pushButton_13.clicked.connect(self.local_enter) <-- pushButton_13('선택 폴더진입->' 버튼) 버튼 클릭 시 local_enter()함수 호출
48.if self.curr_local: <-- 폴더(또는 파일)이 선택되었으면 if문 실행, 그렇지 않으면 else문 실행
49.os.chdir(os.path.join(self.local_path, self.curr_local)) <-- 로컬의 디렉토리를 변경(부모디렉토리경로+현재 선택한 폴더를 join으로 합쳐서 그 경로로 이동)
50.self.pushButton_14.clicked.connect(self.local_up) <-- pushButton_14('^' 버튼) 버튼 클릭 시 local_up()함수 호출
os.chdir('..') <-- 로컬의 상위 디렉토리로 이동51.self.pushButton_5.clicked.connect(self.dele) <-- pushButton_5('파일삭제') 버튼 클릭시 dele()함수 호출
52.if self.curr_remote is not None: <-- 파일(또는 폴더)을 선택했으면 if문 실행
53.r = FTPClass.tea().delete(self.curr_remote) <-- 실제 ftp서버의 delete()명령 실행하여 파일 삭제 수행

54.

if '550' in r: <-- 서버쪽 명령 실행시 퍼미션 에러가 발생하면 아래 익셉션에 rsise 한다.
   raise Exception(r)
except Exception as e: <-- 위에 에러가 발생하면 그 에러 메시지를 보여주기위해서 이렇게 표현한 것임
   self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.(폴더선택 X): "+ str(e)) <-- 문자열함수인 str()로 e를 감싸 줘야 함
55.self.pushButton_4.clicked.connect(self.rnfr_rnto) <-- pushButton_4('이름변경') 버튼 클릭시 rnfr_rnto()함수 호출

56.

def rnfr_rnto(self):
    if self.curr_remote is not None: <-- 파일(또는 폴더)을 선택했으면 if문 실행
        self.pushButton_12.clicked.connect(self.apply_rn) <-- 왼쪽 하단의 'OK'버튼 클릭시 apply_rn()함수 실행
57.
def apply_rn(self):
    new_name = self.lineEdit_3.text() <-- lineEdit_3의 값(사용자가 입력한 변경할 파일이름)을 읽어서
    if new_name != '': <-- lineEdit_3의 값이 비어있지 않으면 if문 실행
        try:
            r = FTPClass.tea().rename(self.curr_remote, new_name) <-- 실제 서버쪽 이름변경 명령인 rename()를 실행하여 이름을 변경 후 r변수에 실행결과 값을 저장
        except error_perm: <-- 혹시라도 서버쪽 권한관련 에러가 발생하면
            self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.") <-- 에러메시지 출력

 

58.

def mkd(self):
    if self.remote_path is not None: <-- remote_path(/)변수에 값이 있으면 if구문 실행
        self.pushButton_12.clicked.connect(self.apply_mkd) <-- pushButton_12('OK') 버튼 클릭시 apply_mkd()함수 실행
59.
def apply_mkd(self):
    new_name = self.lineEdit_3.text() <-- 생성할 폴더명을 가져와서
    if new_name != '': <-- 생성할 폴더명을 입력했으면 if문 실행
        try:
            r = FTPClass.tea().mkd(new_name) <-- 실제 서버쪽 폴더생성 명령 실행 
            if '550' in r: <-- 퍼미션 에러가 발생하면 아래 익셉션에 rsise 한다.
                raise Exception(r)
        except Exception as e:
            self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.: "+ str(e))
60.
def rmd(self):
    if self.curr_remote is not None: <-- 폴더(또는 파일)를 선택했으면 if문 실행
        try:
            r = FTPClass.tea().rmd(self.curr_remote) <-- 실제 서버쪽 폴더 삭제 명령 실행
            if '550' in r: # 퍼미션 에러가 발생하면 아래 익셉션에 rsise 한다.
                raise Exception(r)
        except Exception as e:
            self.label_8.setText("서버쪽 권한 관련 에러가 발생하였습니다. 권한 부분을 확인해 주시기 바랍니다.(파일선택 X): "+ str(e))

 

 


 

ㅁ 정리

 

O 우리가 배운 내용

 
 - 오늘은 FTP서버에 로그인 후 리모트 ftp서버에서 파일이름을 변경하고, 파일을 삭제하는 부가기능에 대하여 알아봤습니다.
 
 
 
 
 
 - 오늘 우리가 배운 내용을 두줄로 정리해 보면 아래와 같습니다.
 
 
 > 1.서버에서 파일이름을 변경하는 명령인 rename()함수를 이용하여 파일이름을 변경r = FTPClass.tea().rename(self.curr_remote, new_name)
 
 > 2.서버에서 파일을 삭제하는 명령인 delete()함수를 이용하여 파일을 삭제
r = FTPClass.tea().delete(self.curr_remote)

 

 

 

 

 

여기까지해서 파이썬 GUI/윈도우 FTP(파일전송) 프로그램 만들기를 모두 마칩니다. 여기까지 따라오신 분들이라면 아마도 무엇이라도 만들 수 있는 자신감과 그러한 능력이 생겼을 거라고 생각합니다.

 

소개편에서 말씀드린 바와 같이 우리 프로그램은 몇가지 추가 개선이 필요합니다. 아래 추가 개선 사항을 직접 스스로 해결해 보신다면 엄청난 실력향상을 느끼실 수 있을 것입니다.

 

1) ftp 포트 지정

>현재 우리 프로그램은 기본 포트인 21번만 사용이 가능합니다. 따라서 사용자가 포트를 변경하여 포트를 사용할 수 있도록 개선이 필요합니다.

 

2) 폴더(디렉토리) 삭제 시 빈 폴더만 삭제 가능

>현재 우리 프로그램은 폴더 삭제시 빈 폴더만 삭제 가능합니다. 따라서 폴더내 다른 폴더나 파일이 존재하더라도 삭제가 가능하도록 개선이 필요합니다.


3) 로컬 기능 추가 : 이름변경, 파일삭제, 폴더삭제, 폴더생성

>현재 우리 프로그램에서 부가기능(이름변경, 파일삭제, 폴더삭제, 폴더생성)은 리모트 FTP서버에서만 기능 구현이 되어 있습니다. 해당 기능을 로컬PC에서도 사용할 수 있도록 추가 개발이 필요합니다.

 

 

오늘은 여기까지 입니다. 위의 내용이 유익하셨다면, 좋아요와 구독 부탁드립니다.

 

 

감사합니다.

728x90
반응형
LIST