initial commit

This commit is contained in:
ghostersk
2024-10-05 05:58:50 +01:00
committed by GitHub
parent 22fd30f113
commit 917538e583
4 changed files with 337 additions and 0 deletions

BIN
ftp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

292
ftp_gui.py Normal file
View File

@@ -0,0 +1,292 @@
import sys
import os
import socket
import configparser
import logging
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QPushButton,
QVBoxLayout, QHBoxLayout, QLineEdit, QTextEdit,
QFormLayout, QSpinBox, QFileDialog, QSystemTrayIcon, QMenu, QAction, QCheckBox)
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QIcon
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
# requirements:
# python.exe -m pip install pyftpdlib PyQt5
# compiling and dependancies:
# python.exe -m pip install pyinstaller Pillow
# pyinstaller --onefile --windowed --add-data "ftp.png;." --icon=ftp.png ftp_gui.py
# this file will be created where the code is run from or in folder where .exe is
# it gives options to save settings
CONFIG_FILE = "ftp-config.cfg"
class FTPServerThread(QThread):
log_signal = pyqtSignal(str)
def __init__(self, port, username, password, ftp_directory, log_file, parent=None):
super(FTPServerThread, self).__init__(parent)
self.port = port
self.username = username
self.password = password
self.ftp_directory = ftp_directory
self.log_file = log_file
self.server = None
self.running = False
self.setup_logger()
def setup_logger(self):
"""Setup logger to log both to file and emit to GUI."""
self.logger = logging.getLogger("FTPServer")
self.logger.setLevel(logging.INFO)
# Create file handler
file_handler = logging.FileHandler(self.log_file)
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
self.logger.addHandler(file_handler)
def run(self):
if not os.path.exists(self.ftp_directory):
os.makedirs(self.ftp_directory)
# Set up authorizer with user authentication
authorizer = DummyAuthorizer()
authorizer.add_user(self.username, self.password, self.ftp_directory, perm="elradfmw")
# Set up FTP handler
handler = FTPHandler
handler.authorizer = authorizer
# Override handler log method to connect to the log signal and file logger
handler.log = self.log
# Create FTP server
self.server = FTPServer(("0.0.0.0", self.port), handler)
self.running = True
self.server.serve_forever()
def log(self, message, logfun=None):
"""Custom log method to emit logs to the GUI and log to the file."""
self.log_signal.emit(message)
self.logger.info(message)
def stop(self):
if self.server:
self.server.close_all()
self.running = False
class FTPGuiApp(QWidget):
def __init__(self, config):
super().__init__()
self.setWindowTitle("Simple FTP Server")
self.server_thread = None
self.ftp_directory = config['FTP']['ftp_directory']
self.run_as_daemon = config.getboolean('FTP', 'run_as_daemon')
self.log_file = config['FTP'].get('log_file', 'ftp_log.txt')
self.username = config['FTP']['username']
self.password = config['FTP']['password']
self.port = config.getint('FTP', 'port')
self.tray_icon = None
self.init_ui()
# Start in daemon mode if configured
if self.run_as_daemon:
self.start_server()
self.hide_to_tray()
def init_ui(self):
layout = QVBoxLayout()
# Form for user, password, port, and directory
form_layout = QFormLayout()
self.username_input = QLineEdit()
self.username_input.setText(self.username)
self.password_input = QLineEdit()
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setText(self.password)
self.port_input = QSpinBox()
self.port_input.setRange(1, 65535)
self.port_input.setValue(self.port)
form_layout.addRow("Username:", self.username_input)
form_layout.addRow("Password:", self.password_input)
form_layout.addRow("Port:", self.port_input)
# FTP directory picker
self.directory_input = QLineEdit()
self.directory_input.setText(self.ftp_directory)
self.directory_button = QPushButton("Browse FTP Folder")
self.directory_button.clicked.connect(self.select_directory)
directory_layout = QHBoxLayout()
directory_layout.addWidget(self.directory_input)
directory_layout.addWidget(self.directory_button)
layout.addLayout(form_layout)
layout.addLayout(directory_layout)
# Log file path picker
self.log_file_input = QLineEdit()
self.log_file_input.setText(self.log_file)
self.log_file_button = QPushButton("Browse Log File")
self.log_file_button.clicked.connect(self.select_log_file)
log_layout = QHBoxLayout()
log_layout.addWidget(self.log_file_input)
log_layout.addWidget(self.log_file_button)
layout.addLayout(log_layout)
# Daemon mode checkbox
self.daemon_checkbox = QCheckBox("Run as Daemon (Minimize to Tray)")
self.daemon_checkbox.setChecked(self.run_as_daemon)
layout.addWidget(self.daemon_checkbox)
# Button to start and stop the server
self.start_button = QPushButton("Start FTP Server")
self.start_button.clicked.connect(self.start_server)
self.stop_button = QPushButton("Stop FTP Server")
self.stop_button.clicked.connect(self.stop_server)
self.stop_button.setEnabled(False)
# Layout for buttons
button_layout = QHBoxLayout()
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.stop_button)
layout.addLayout(button_layout)
# Log view to show server activity
self.log_view = QTextEdit()
self.log_view.setReadOnly(True)
layout.addWidget(self.log_view)
# Label to show listening IP addresses
self.ip_label = QLabel("Listening on:")
layout.addWidget(self.ip_label)
self.setLayout(layout)
# Set up system tray for daemon mode
self.setup_tray_icon()
def setup_tray_icon(self):
icon_path = os.path.join(os.path.dirname(__file__), 'ftp.png')
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(QIcon(icon_path))
tray_menu = QMenu()
restore_action = QAction("Restore", self)
restore_action.triggered.connect(self.show)
tray_menu.addAction(restore_action)
quit_action = QAction("Quit", self)
quit_action.triggered.connect(self.close)
tray_menu.addAction(quit_action)
self.tray_icon.setContextMenu(tray_menu)
self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.tray_icon.show()
def on_tray_icon_activated(self, reason):
if reason == QSystemTrayIcon.Trigger:
self.show()
def hide_to_tray(self):
self.hide()
self.tray_icon.showMessage("FTP Server", "Running in background", QSystemTrayIcon.Information, 3000)
def select_directory(self):
folder = QFileDialog.getExistingDirectory(self, "Select FTP Folder", self.ftp_directory)
if folder:
self.ftp_directory = folder
self.directory_input.setText(folder)
def select_log_file(self):
log_file, _ = QFileDialog.getSaveFileName(self, "Select Log File", self.log_file, "Log Files (*.txt)")
if log_file:
self.log_file = log_file
self.log_file_input.setText(log_file)
def start_server(self):
username = self.username_input.text()
password = self.password_input.text()
port = self.port_input.value()
ftp_directory = self.directory_input.text()
log_file = self.log_file_input.text()
self.server_thread = FTPServerThread(port, username, password, ftp_directory, log_file)
self.server_thread.log_signal.connect(self.log_message)
self.server_thread.start()
self.update_ip_addresses()
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
if self.daemon_checkbox.isChecked():
self.hide_to_tray()
def stop_server(self):
if self.server_thread:
self.server_thread.stop()
self.server_thread.wait()
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
def log_message(self, message):
self.log_view.append(message)
def update_ip_addresses(self):
ip_addresses = self.get_ip_addresses()
self.ip_label.setText(f"Listening on: {', '.join(ip_addresses)}")
def get_ip_addresses(self):
ip_list = []
for ip in socket.gethostbyname_ex(socket.gethostname())[2]:
if not ip.startswith("127."):
ip_list.append(ip)
return ip_list
def closeEvent(self, event):
if self.daemon_checkbox.isChecked():
event.ignore()
self.hide_to_tray()
else:
event.accept()
def load_config():
config = configparser.ConfigParser()
if not os.path.exists(CONFIG_FILE):
# Default settings for first run
config['FTP'] = {
'username': 'user',
'password': '123',
'port': '21',
'ftp_directory': r'C:\Temp\FTP',
'run_as_daemon': '0',
'log_file': 'ftp_log.txt'
}
with open(CONFIG_FILE, 'w') as configfile:
config.write(configfile)
else:
config.read(CONFIG_FILE)
return config
if __name__ == '__main__':
config = load_config()
app = QApplication(sys.argv)
gui = FTPGuiApp(config)
# Show the window if not in daemon mode
if not config.getboolean('FTP', 'run_as_daemon'):
gui.show()
sys.exit(app.exec_())

39
ftp_gui.spec Normal file
View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['ftp_gui.py'],
pathex=[],
binaries=[],
datas=[('ftp.png', '.')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='ftp_gui',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['ftp.png'],
)

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
PyQt5
pyftpdlib
# for windows compiling:
pyinstaller
Pillow