|
| 1 | +import sys |
| 2 | +import os |
| 3 | +import threading |
| 4 | +import socket |
| 5 | +import logging |
| 6 | +from PyQt5 import QtWidgets, QtGui, QtCore |
| 7 | +from tftp.TFTPServer import TftpPacketDAT, TftpPacketERR, TftpServer |
| 8 | +from tftp.TftpClient import TftpClient |
| 9 | +# Logging Configuration |
| 10 | +logger = logging.getLogger('tftp_server') |
| 11 | +logger.setLevel(logging.INFO) |
| 12 | +file_handler = logging.FileHandler('tftp_server_activity.log') |
| 13 | +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') |
| 14 | +file_handler.setFormatter(formatter) |
| 15 | +logger.addHandler(file_handler) |
| 16 | + |
| 17 | +import socket |
| 18 | + |
| 19 | +import psutil |
| 20 | + |
| 21 | +def get_ip_addresses(): |
| 22 | + """Get the list of available IP addresses on the local machine across all interfaces.""" |
| 23 | + ip_addresses = [] |
| 24 | + |
| 25 | + for interface, addresses in psutil.net_if_addrs().items(): |
| 26 | + for address in addresses: |
| 27 | + if address.family == socket.AF_INET: # IPv4 addresses only |
| 28 | + ip_addresses.append(address.address) |
| 29 | + |
| 30 | + # Remove duplicates and return the list of IP addresses |
| 31 | + return list(set(ip_addresses)) |
| 32 | + |
| 33 | +class TFTPClient(QtWidgets.QWidget): |
| 34 | + log_signal = QtCore.pyqtSignal(str) # Signal to update log |
| 35 | + |
| 36 | + def __init__(self): |
| 37 | + super().__init__() |
| 38 | + self.setWindowTitle("TFTP Client") |
| 39 | + self.setGeometry(500, 100, 500, 400) |
| 40 | + |
| 41 | + self.hardcoded_ip = "192.168.0.27" |
| 42 | + |
| 43 | + self.ip_input = QtWidgets.QLineEdit(self) |
| 44 | + self.ip_input.setPlaceholderText("Enter Server IP Address") |
| 45 | + self.ip_input.setText(self.hardcoded_ip) # Set hardcoded IP |
| 46 | + self.ip_input.setReadOnly(True) |
| 47 | + |
| 48 | + # Separate inputs for upload and download |
| 49 | + self.upload_file_input = QtWidgets.QLineEdit(self) |
| 50 | + self.upload_file_input.setPlaceholderText("Select File to Upload") |
| 51 | + |
| 52 | + self.download_file_input = QtWidgets.QLineEdit(self) |
| 53 | + self.download_file_input.setPlaceholderText("Enter File Name to Download") |
| 54 | + |
| 55 | + self.browse_button = QtWidgets.QPushButton("Browse Upload...", self) |
| 56 | + self.browse_button.clicked.connect(self.browse_upload_file) |
| 57 | + |
| 58 | + self.download_button = QtWidgets.QPushButton("Download File", self) |
| 59 | + self.download_button.clicked.connect(self.download_file) |
| 60 | + |
| 61 | + self.upload_button = QtWidgets.QPushButton("Upload File", self) |
| 62 | + self.upload_button.clicked.connect(self.upload_file) |
| 63 | + |
| 64 | + self.use_folder_checkbox = QtWidgets.QCheckBox("Use selected folder for download", self) |
| 65 | + self.use_folder_checkbox.setChecked(False) # Default unchecked |
| 66 | + |
| 67 | + self.default_directory_label = QtWidgets.QLabel(self) |
| 68 | + self.default_directory_label.setText(f"Default Download Directory: {self.get_default_directory()}") |
| 69 | + |
| 70 | + self.status_label = QtWidgets.QLabel("Status: Waiting", self) |
| 71 | + self.status_label.setStyleSheet("font-weight: bold;") |
| 72 | + |
| 73 | + self.log_output = QtWidgets.QTextEdit(self) |
| 74 | + self.log_output.setReadOnly(True) |
| 75 | + |
| 76 | + layout = QtWidgets.QVBoxLayout() |
| 77 | + layout.addWidget(QtWidgets.QLabel("Enter Server IP Address:")) |
| 78 | + layout.addWidget(self.ip_input) |
| 79 | + |
| 80 | + # Upload file section |
| 81 | + layout.addWidget(QtWidgets.QLabel("File to Upload:")) |
| 82 | + layout.addWidget(self.upload_file_input) |
| 83 | + layout.addWidget(self.browse_button) |
| 84 | + |
| 85 | + # Download file section |
| 86 | + layout.addWidget(QtWidgets.QLabel("File to Download:")) |
| 87 | + layout.addWidget(self.download_file_input) |
| 88 | + layout.addWidget(self.use_folder_checkbox) # Add the checkbox here |
| 89 | + |
| 90 | + # Add the default directory label |
| 91 | + layout.addWidget(self.default_directory_label) |
| 92 | + |
| 93 | + button_layout = QtWidgets.QHBoxLayout() |
| 94 | + button_layout.addWidget(self.download_button) |
| 95 | + button_layout.addWidget(self.upload_button) |
| 96 | + button_layout.addWidget(self.status_label) |
| 97 | + layout.addLayout(button_layout) |
| 98 | + |
| 99 | + layout.addWidget(QtWidgets.QLabel("Client Log:")) |
| 100 | + layout.addWidget(self.log_output) |
| 101 | + |
| 102 | + self.setLayout(layout) |
| 103 | + self.setStyleSheet("background-color: #f0f0f0; font-family: Arial;") |
| 104 | + |
| 105 | + self.total_size = 0 |
| 106 | + self.downloaded_size = 0 |
| 107 | + |
| 108 | + # Connect the log signal to the update_log method |
| 109 | + self.log_signal.connect(self.update_log) |
| 110 | + |
| 111 | + def get_default_directory(self): |
| 112 | + """Return the default download directory.""" |
| 113 | + return os.path.expanduser("~") # User's home directory |
| 114 | + |
| 115 | + def browse_upload_file(self): |
| 116 | + options = QtWidgets.QFileDialog.Options() |
| 117 | + file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Select File to Upload", "", "All Files (*);;Text Files (*.txt)", options=options) |
| 118 | + if file_name: |
| 119 | + self.upload_file_input.setText(file_name) # Save the full path for the selected file |
| 120 | + |
| 121 | + def download_file(self): |
| 122 | + ip = self.ip_input.text() # Get the manually entered IP address |
| 123 | + filename = self.download_file_input.text() |
| 124 | + |
| 125 | + if self.use_folder_checkbox.isChecked(): |
| 126 | + # Open a dialog to select the folder for saving the downloaded file |
| 127 | + folder = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Folder to Save Downloaded File") |
| 128 | + |
| 129 | + if folder: # Proceed only if a folder was selected |
| 130 | + self.status_label.setText("Status: Downloading...") |
| 131 | + self.log_output.clear() # Clear previous logs |
| 132 | + |
| 133 | + # Construct the full path for the downloaded file |
| 134 | + full_path = os.path.join(folder, filename) |
| 135 | + |
| 136 | + # Start a new thread for downloading to avoid blocking the UI |
| 137 | + threading.Thread(target=self.perform_download, args=(ip, full_path), daemon=True).start() |
| 138 | + else: |
| 139 | + self.log_signal.emit("Download canceled: No folder selected.") |
| 140 | + else: |
| 141 | + # Default save path (you can modify this to your preferred default directory) |
| 142 | + default_directory = self.get_default_directory() |
| 143 | + full_path = os.path.join(default_directory, filename) |
| 144 | + |
| 145 | + self.status_label.setText("Status: Downloading...") |
| 146 | + self.log_output.clear() # Clear previous logs |
| 147 | + |
| 148 | + # Start a new thread for downloading to avoid blocking the UI |
| 149 | + threading.Thread(target=self.perform_download, args=(ip, full_path), daemon=True).start() |
| 150 | + |
| 151 | + def perform_download(self, ip, full_path): |
| 152 | + try: |
| 153 | + client = TftpClient(ip, 69) |
| 154 | + |
| 155 | + # Get total file size |
| 156 | + self.total_size = client.get_file_size(os.path.basename(full_path)) |
| 157 | + if self.total_size <= 0: |
| 158 | + self.status_label.setText("Error: Invalid file size.") |
| 159 | + self.log_signal.emit("Error: Invalid file size.") |
| 160 | + return |
| 161 | + |
| 162 | + # Function to update progress |
| 163 | + def update_progress(packet): |
| 164 | + if isinstance(packet, TftpPacketERR): |
| 165 | + self.log_signal.emit(f"Error: {packet.errmsg.decode()}") |
| 166 | + return |
| 167 | + |
| 168 | + # Check for DAT packets (data packets) |
| 169 | + if isinstance(packet, TftpPacketDAT): |
| 170 | + self.downloaded_size += len(packet.data) |
| 171 | + self.log_signal.emit(f"Downloaded: {self.downloaded_size} bytes") |
| 172 | + |
| 173 | + # Start downloading with the update_progress callback |
| 174 | + client.download(os.path.basename(full_path), output=full_path, packethook=update_progress) |
| 175 | + |
| 176 | + self.status_label.setText("Status: Download Complete") |
| 177 | + self.log_signal.emit("Download complete.") |
| 178 | + except Exception as e: |
| 179 | + self.status_label.setText(f"Error: {str(e)}") |
| 180 | + self.log_signal.emit(f"Error: {str(e)}") |
| 181 | + |
| 182 | + def upload_file(self): |
| 183 | + ip = self.ip_input.text() # Get the manually entered IP address |
| 184 | + filename = self.upload_file_input.text() |
| 185 | + self.status_label.setText("Status: Uploading...") |
| 186 | + self.log_output.clear() # Clear previous logs |
| 187 | + |
| 188 | + # Start a new thread for uploading to avoid blocking the UI |
| 189 | + threading.Thread(target=self.perform_upload, args=(ip, filename), daemon=True).start() |
| 190 | + |
| 191 | + def perform_upload(self, ip, filename): |
| 192 | + try: |
| 193 | + client = TftpClient(ip, 69) |
| 194 | + |
| 195 | + # Get total file size |
| 196 | + self.total_size = os.path.getsize(filename) |
| 197 | + self.downloaded_size = 0 # Reset downloaded size |
| 198 | + |
| 199 | + # Function to update progress |
| 200 | + def update_progress(packet): |
| 201 | + if isinstance(packet, TftpPacketERR): |
| 202 | + self.log_signal.emit(f"Error: {packet.errmsg.decode()}") |
| 203 | + return |
| 204 | + |
| 205 | + # Check for DAT packets (data packets) |
| 206 | + if isinstance(packet, TftpPacketDAT): |
| 207 | + self.downloaded_size += len(packet.data) |
| 208 | + self.log_signal.emit(f"Uploaded: {self.downloaded_size} bytes") |
| 209 | + |
| 210 | + # Start uploading with the update_progress callback |
| 211 | + with open(filename, 'rb') as f: |
| 212 | + client.upload(os.path.basename(filename), input=f, packethook=update_progress) |
| 213 | + |
| 214 | + self.status_label.setText("Status: Upload Complete") |
| 215 | + self.log_signal.emit("Upload complete.") |
| 216 | + except Exception as e: |
| 217 | + self.status_label.setText(f"Error: {str(e)}") |
| 218 | + self.log_signal.emit(f"Error: {str(e)}") |
| 219 | + |
| 220 | + def update_log(self, message): |
| 221 | + self.log_output.append(message) |
| 222 | + self.log_output.moveCursor(QtGui.QTextCursor.End) # Scroll to the bottom |
| 223 | + |
| 224 | + |
| 225 | +if __name__ == "__main__": |
| 226 | + app = QtWidgets.QApplication(sys.argv) |
| 227 | + client = TFTPClient() |
| 228 | + client.setWindowTitle("TFTP Client by petrunetworking") |
| 229 | + client.setGeometry(100, 100, 500, 400) |
| 230 | + client.setStyleSheet("background-color: #eaeaea; font-family: Arial;") |
| 231 | + client.show() |
| 232 | + sys.exit(app.exec_()) |
0 commit comments