๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ๐ŸŒฎ ๐Ÿ’ฌ
๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป/python

[python] PySide6๋กœ ์˜ฌ๋ฆฐ gui๋กœ ์›๊ฒฉ ์„œ๋ฒ„์˜ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œํ•˜๊ธฐ

by ๋ฐ”์ฟ„๋ฆฌ 2024. 12. 31.

๊ฐœ์š”

์ด๋ฒˆ์— ์ง„ํ–‰ํ•œ ํ”„๋กœ์ ํŠธํ•ด์„œ 2๊ฐ€์ง€๋ฅผ ๊ฐœ๋ฐœํ•ด์•ผํ–ˆ๋‹ค.

 

1. ์›๊ฒฉ ์„œ๋ฒ„์— ์žˆ๋Š” ํŒŒ์ผ์„ ๋กœ์ปฌ์— ๋‹ค์šด๋กœ๋“œํ•ด์•ผํ•œ๋‹ค. (ํŒŒ์ผ์ด ์ปค์„œ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋‹ˆ, progress bar๋กœ ์ง„ํ–‰ ์‚ฌํ•ญ์„ ๋ณด์—ฌ์ฃผ์ž)

2. PySide6๋กœ gui๋ฅผ ์˜ฌ๋ฆฐ๋‹ค.

 

1๏ธโƒฃ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ

import paramiko
import time

def download_file(hostname, port, username, password, remote_file_path, local_file_path, progress_callback):
    try:
        start = time.perf_counter()
        
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname, port, username, password)

        sftp = ssh.open_sftp()
        
        remote_file_size = sftp.stat(remote_file_path).st_size
        
        with sftp.open(remote_file_path, "rb") as remote_file, open(local_file_path, "wb") as local_file:
            downloaded_size = 0
            chunk_size = 8192
            
            while True:
                data = remote_file.read(chunk_size)
                if not data:
                    break
                local_file.write(data)
                downloaded_size += len(data)
                
                # ์ง„ํ–‰๋ฅ  ๊ณ„์‚ฐ ๋ฐ ์ฝœ๋ฐฑ ํ˜ธ์ถœ
                progress = int((downloaded_size / remote_file_size) * 100)
                progress_callback(progress)

        print(f"File downloaded successfully from {remote_file_path} to {local_file_path}")

        sftp.close()
        ssh.close()
        
        end = time.perf_counter()
        print(f"model ๋‹ค์šด๋กœ๋“œ ์‹œ๊ฐ„: {end - start:.6f} ์ดˆ")

    except Exception as e:
        print(f"An error occurred: {e}")

 

- paramiko ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ssh ์ ‘์† ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด ์›๊ฒฉ ์„œ๋ฒ„์— ์žˆ๋Š” ํŒŒ์ผ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค.

- ๋‹ค์šด๋กœ๋“œ ํ•ด์•ผํ•˜๋Š” ํŒŒ์ผ์˜ ํฌ๊ธฐ์™€ ๋กœ์ปฌ์— ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์ง€๋Š” ํŒŒ์ผ์˜ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•ด์„œ ์ง„ํ–‰๋ฅ ์„ ๊ณ„์‚ฐํ–ˆ๋‹ค.

 

2๏ธโƒฃ PySide6์œผ๋กœ GUI ์˜ฌ๋ฆฌ๊ธฐ

from PySide6.QtWidgets import (
    QApplication, QVBoxLayout, QWidget, QProgressBar, QPushButton, QLabel, QDialog, QLineEdit, QFormLayout, QMessageBox
)
from PySide6.QtCore import QThread, Signal
from download_file_pyside6 import download_file


class DownloadThread(QThread):
    progress = Signal(int)
    completed = Signal()

    def __init__(self, hostname, port, username, password, remote_file_path, local_file_path):
        super().__init__()
        self.hostname = hostname
        self.port = port
        self.username = username
        self.password = password
        self.remote_file_path = remote_file_path
        self.local_file_path = local_file_path

    def run(self):
        download_file(
            hostname=self.hostname,
            port=self.port,
            username=self.username,
            password=self.password,
            remote_file_path=self.remote_file_path,
            local_file_path=self.local_file_path,
            progress_callback=self.progress.emit
        )
        self.completed.emit()


class DownloadDialog(QDialog):
    def __init__(self, parent, hostname, port, username, password, remote_file_path, local_file_path):
        super().__init__(parent)
        self.setWindowTitle("Downloading File")
        self.resize(400, 150)
        
        self.layout = QVBoxLayout(self)

        # ์ƒํƒœ ๋ ˆ์ด๋ธ”
        self.status_label = QLabel("Preparing to download...", self)
        self.layout.addWidget(self.status_label)

        # ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ”
        self.progress_bar = QProgressBar(self)
        self.progress_bar.setValue(0)
        self.progress_bar.setMaximum(100)
        self.layout.addWidget(self.progress_bar)

        # ๋‹ซ๊ธฐ ๋ฒ„ํŠผ
        self.close_button = QPushButton("Close", self)
        self.close_button.setEnabled(False)
        self.close_button.clicked.connect(self.close)
        self.layout.addWidget(self.close_button)

        # ๋‹ค์šด๋กœ๋“œ ์Šค๋ ˆ๋“œ ์„ค์ •
        self.download_thread = DownloadThread(
            hostname, port, username, password, remote_file_path, local_file_path
        )
        self.download_thread.progress.connect(self.update_progress)
        self.download_thread.completed.connect(self.download_completed)

        # ๋‹ค์šด๋กœ๋“œ ์‹œ์ž‘
        self.download_thread.start()

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.status_label.setText(f"Downloading... {value}%")

    def download_completed(self):
        self.status_label.setText("Download complete!")
        self.close_button.setEnabled(True)
        
    def closeEvent(self, event):
        if self.download_completed:
            event.accept()
        else:
            confirm = QMessageBox.question(
                self,
                "Confirm Close",
                "Are you sure you want to stop downloading the model?",
                QMessageBox.Yes | QMessageBox.No,
            )
            if confirm == QMessageBox.Yes:
                event.accept()
                os.remove(local_file_path)
            else:
                event.ignore()

class InputDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Enter Download Details")
        self.resize(400, 300)
        
        self.layout = QFormLayout(self)

        # ์ž…๋ ฅ ํ•„๋“œ ์ƒ์„ฑ
        self.hostname_input = QLineEdit(self)
        self.port_input = QLineEdit(self)
        self.port_input.setText("5022")  # ๊ธฐ๋ณธ๊ฐ’
        self.username_input = QLineEdit(self)
        self.password_input = QLineEdit(self)
        self.password_input.setEchoMode(QLineEdit.Password)
        self.remote_file_input = QLineEdit(self)
        self.local_file_input = QLineEdit(self)
        
        # ํ•„๋“œ ๋ ˆ์ด์•„์›ƒ ์ถ”๊ฐ€
        self.layout.addRow("Hostname:", self.hostname_input)
        self.layout.addRow("Port:", self.port_input)
        self.layout.addRow("Username:", self.username_input)
        self.layout.addRow("Password:", self.password_input)
        self.layout.addRow("Remote File Path:", self.remote_file_input)
        self.layout.addRow("Local File Path:", self.local_file_input)

        # ํ™•์ธ ๋ฐ ์ทจ์†Œ ๋ฒ„ํŠผ
        self.ok_button = QPushButton("OK", self)
        self.ok_button.clicked.connect(self.accept)
        self.layout.addRow(self.ok_button)

        self.setLayout(self.layout)

    def get_inputs(self):
        return {
            "hostname": self.hostname_input.text(),
            "port": int(self.port_input.text()),
            "username": self.username_input.text(),
            "password": self.password_input.text(),
            "remote_file_path": self.remote_file_input.text(),
            "local_file_path": self.local_file_input.text(),
        }


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Main Window")
        self.resize(400, 200)
        
        self.layout = QVBoxLayout(self)

        # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ
        self.download_button = QPushButton("Download File", self)
        self.download_button.clicked.connect(self.start_download_dialog)
        self.layout.addWidget(self.download_button)

    def start_download_dialog(self):
        # ์ž…๋ ฅ ๋Œ€ํ™” ์ƒ์ž ์—ด๊ธฐ
        input_dialog = InputDialog(self)
        if input_dialog.exec():  # ์‚ฌ์šฉ์ž๊ฐ€ ํ™•์ธ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋Š”์ง€ ํ™•์ธ
            inputs = input_dialog.get_inputs()
            dialog = DownloadDialog(
                self,
                hostname=inputs["hostname"],
                port=inputs["port"],
                username=inputs["username"],
                password=inputs["password"],
                remote_file_path=inputs["remote_file_path"],
                local_file_path=inputs["local_file_path"],
            )
            dialog.exec()


if __name__ == "__main__":
    app = QApplication([])
    window = MainWindow()
    window.show()
    app.exec()

 

1. MainWindow๋ฅผ ์ฒซ ํŽ˜์ด์ง€๋กœ showํ•œ๋‹ค.

2. Download File ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด InputDialog ๊ฐ€ ์‹คํ–‰๋˜๋ฉฐ ssh ์ ‘์† ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” Dialog๊ฐ€ ๋„์–ด์ง„๋‹ค.

 

default ๊ฐ’ (placeholder)๋กœ ๊ฐ’์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๊ณ , ์ž…๋ ฅํ•˜๋Š” ๊ฐ’์„ password๋กœ ์•”ํ˜ธํ™”ํ•ด์„œ ๋ณด์—ฌ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.

 

3. ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  OK๋ฅผ ํด๋ฆญํ•˜๋ฉด DownloadDialog๊ฐ€ ์‹คํ–‰๋œ๋‹ค

 

๋‹ค์šด๋กœ๋“œ ์ค‘์—๋Š” Close ๋ฒ„ํŠผ์ด ๋น„ํ™œ์„ฑํ™”๋˜๊ณ , ๋‹ค์šด๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด Close ๋ฒ„ํŠผ์ด ํ™œ์„ฑํ™”๋œ๋‹ค.

๋‹ค์šด๋กœ๋“œ ์ง„ํ–‰ ์ค‘์— Downloading File dialog๋ฅผ ๋‹ซ์œผ๋ ค๊ณ  ํ•œ๋‹ค๋ฉด, "Are you sure you want to stop downloading the model?" ์„ ๋ฌป๋Š”๋‹ค. Yes → ๋‹ค์šด๋กœ๋“œ๋Š” ์ค‘์ง€ํ•˜๊ณ , ๋‹ค์šด๋กœ๋“œ ์ค‘์ด์—ˆ๋˜ ํŒŒ์ผ์„ ์‚ญ์ œํ•œ๋‹ค. No → ๋‹ค์šด๋กœ๋“œ๋Š” ๊ณ„์† ์ด์–ด๊ฐ„๋‹ค.