Skip to content

Commit 3b31db6

Browse files
authored
Merge pull request #500 from Hyperloop-UPV/electron/add-python-tftp-app
Electron/Add BLCU
2 parents b9c5877 + 172a43d commit 3b31db6

19 files changed

Lines changed: 3142 additions & 1 deletion

blcu-programming/.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

blcu-programming/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/.venv
2+
/.vscode
3+
*.pyc
4+
*__pycache__/
5+
*.pyo
6+
*.pyd
7+
.idea/
8+
containers/*/.venv
9+
.env
10+
*.log

blcu-programming/LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

blcu-programming/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# TFTP Server and Client
2+
3+
**TFTP Server and Client** is a comprehensive Python-based application designed to facilitate Trivial File Transfer Protocol (TFTP) operations through a user-friendly graphical interface built with PyQt5. This tool allows users to efficiently manage file transfers to and from TFTP servers, making it an essential resource for network administrators.
4+
5+
## Key Features
6+
7+
### TFTP Server
8+
- **Start/Stop Server:** Easily control the TFTP server with start and stop functionalities.
9+
- **Select IP Address:** Choose the specific IP address for the server to listen on.
10+
- **Change Working Directory:** Modify the current working directory for file transfers with a straightforward interface.
11+
- **View Directory Contents:** Quickly access and view files in the current working directory.
12+
- **Logging:** Track server activity with real-time logging displayed in the application and stored in a log file.
13+
[tftp_server](../TFTP_GUI_Server_Client/screenshot/tftp_server.png)
14+
### TFTP Client
15+
- **Upload Files:** Seamlessly upload files to a TFTP server using a simple interface.
16+
- **Download Files:** Effortlessly download files from a TFTP server with progress monitoring.
17+
- **File Browsing:** Use an integrated file dialog for easy file selection.
18+
- **Status Updates:** Receive real-time status updates during upload and download processes.
19+
[tftp_client](../TFTP_GUI_Server_Client/screenshot/tftp_client.png)
20+
### Monitoring and Alerts
21+
- Monitor TFTP server activities and transfer statuses with immediate feedback on successes or failures.
22+
- Real-time updates ensure that users can track the progress of their operations.
23+
24+
### Error Handling and Prompt Detection
25+
- Implement robust error handling specific to TFTP operations to ensure smooth functionality.
26+
- Dynamic prompt detection allows the application to handle varying device responses effectively.
27+
28+
## Example Configurations
29+
30+
### Setting Up the TFTP Server
31+
1. Launch the application and navigate to the TFTP Server tab.
32+
2. Select an available IP address from the dropdown menu.
33+
3. Specify the desired port number (default is 69).
34+
4. Click **Start Server** to begin listening for incoming TFTP requests.
35+
36+
### Uploading Files
37+
1. Navigate to the TFTP Client tab.
38+
2. Enter the TFTP server IP address.
39+
3. Use the **Browse Upload...** button to select the file you wish to upload.
40+
4. Click **Upload File** and monitor the status for updates.
41+
42+
### Downloading Files
43+
1. Enter the TFTP server IP address and specify the file name for download.
44+
2. Click **Download File** to initiate the download process.
45+
3. Progress and status will be displayed in real time.
46+
47+
## System Requirements
48+
49+
To run the TFTP Server and Client application, the following system requirements must be met:
50+
51+
- **Python 3.x**
52+
- **PyQt5**
53+
- **TFTP Library**
54+
- **OS:** Windows 10 or Ubuntu >=20.04 (recommended) or any modern Linux distribution
55+
56+
## Technologies Used
57+
58+
- **Python:** The core language utilized for backend logic.
59+
- **PyQt5:** The framework used for developing the graphical user interface.
60+
- **TFTP Library:** Employed for implementing TFTP protocol functionalities.
61+
62+
## License
63+
64+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
65+
66+
## Contributing
67+
68+
Contributions are welcome! Please fork the repository and create a pull request with your enhancements. Alternatively, you can open an issue to suggest improvements or report bugs.
69+
70+
## Author
71+
72+
**petrunetworking** (Network Engineer)
73+
74+
## Acknowledgements
75+
76+
Developed using Python and inspired by the need for efficient file transfer solutions, this application aims to simplify TFTP operations for network professionals, enabling them to manage their file transfers effectively and reliably.
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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

Comments
 (0)