Skip to content

Commit

Permalink
Merge pull request #1 from Schrodinger71/dev
Browse files Browse the repository at this point in the history
Базовый клиент на GUI
  • Loading branch information
themanyfaceddemon authored Jul 27, 2024
2 parents ff037e2 + 73213be commit f282e17
Show file tree
Hide file tree
Showing 68 changed files with 1,054 additions and 1,212 deletions.
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# VS and VSC
.vs/*
.vscode/*

# Data
[Dd]ata/*
Content/*

# Texture
Sprites/

# PyQT6
*_ui.py
10 changes: 10 additions & 0 deletions Code/app_client_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys

from gui import LoginWindow
from PyQt6.QtWidgets import QApplication

if __name__ == "__main__":
app = QApplication(sys.argv)
window: LoginWindow = LoginWindow.get_instance()
window.show()
sys.exit(app.exec())
1 change: 1 addition & 0 deletions Code/gui/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from gui.login_window import LoginWindow
217 changes: 217 additions & 0 deletions Code/gui/login_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import os

from gui.main_window import MainApplicationWindow
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import (QDialog, QLabel, QLineEdit, QMainWindow,
QMessageBox, QProgressBar, QPushButton)
from PyQt6.uic import loadUi
from root_path import ROOT_PATH
from systems.decorators import global_class
from systems.network import ClientUnit
from PyQt6.QtCore import QThread, pyqtSignal

@global_class
class LoginWindow(QMainWindow):
def __init__(self):
super().__init__()
loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'LoginWindow.ui'), self)

# Привязка виджетов
self.ip_line = self.findChild(QLineEdit, 'IPLine')
self.port_line = self.findChild(QLineEdit, 'PortLine')
self.server_check_button = self.findChild(QPushButton, 'ServerCheckButton')
self.server_check_label = self.findChild(QLabel, 'ServerCheckLabel')

self.login_button = self.findChild(QPushButton, 'LoginButton')
self.register_button = self.findChild(QPushButton, 'RegisterButton')

self.set_server_check_label("failure")

# Привязка функций к кнопкам
self.server_check_button.clicked.connect(self.check_server)
self.login_button.clicked.connect(self.open_auth_window)
self.register_button.clicked.connect(self.open_registration_window)

# Начальное состояние кнопок
self.login_button.setEnabled(False)
self.register_button.setEnabled(False)

def set_server_check_label(self, state: str):
self.server_check_label.setPixmap(QPixmap(os.path.join(ROOT_PATH, 'GUI', 'images', 'status', f'{state}_64_64.png')))

def check_server(self):
ip = self.ip_line.text()
port = self.port_line.text()

if not ip or not port:
QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!")
self.set_server_check_label("failure")
self.set_buttons(False)
return

if not self.is_valid_ip(ip):
QMessageBox.warning(self, "Ошибка", "IP неверно указан. Такого IP не может существовать.")
self.set_server_check_label("failure")
self.set_buttons(False)
return

if not self.is_valid_port(port):
QMessageBox.warning(self, "Ошибка", "Порт неверно указан. Такого порта не может существовать.")
self.set_server_check_label("failure")
self.set_buttons(False)
return

cl_unit: ClientUnit = ClientUnit.get_instance()
try:
cl_unit.check_server(ip, int(port))
self.set_server_check_label("success")
self.set_buttons(True)

except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Невозможно подключиться к серверу: {str(e)}")
self.set_server_check_label("failure")
self.set_buttons(False)
return

def open_auth_window(self):
auth_window = AuthDialog(self)
auth_window.exec()

def open_registration_window(self):
registration_window = RegistrationDialog(self)
registration_window.exec()

def set_buttons(self, value: bool):
self.login_button.setEnabled(value)
self.register_button.setEnabled(value)

def is_valid_ip(self, ip: str) -> bool:
parts = ip.split('.')
if len(parts) != 4:
return False

for part in parts:
if not part.isdigit():
return False

if not 0 <= int(part) <= 255:
return False

return True

def is_valid_port(self, port: str) -> bool:
if not port.isdigit():
return False

port_num = int(port)
return 1 <= port_num <= 65535

def run_download_server_content(self) -> None:
download_dialog = ServerContentDownloadDialog(self)
download_dialog.exec()

def run_main_app(self) -> None:
self.close()
self.main_window: MainApplicationWindow = MainApplicationWindow.get_instance()
self.main_window.show()

class AuthDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'AuthDialog.ui'), self)

self.username_line = self.findChild(QLineEdit, 'UsernameLine')
self.password_line = self.findChild(QLineEdit, 'PasswordLine')
self.login_button = self.findChild(QPushButton, 'LoginButton')

self.login_button.clicked.connect(self.authenticate)

def authenticate(self):
username = self.username_line.text()
password = self.password_line.text()

if not username or not password:
QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!")
return

cl_unit: ClientUnit = ClientUnit.get_instance()
try:
cl_unit.login(username, password)
self.close_current_and_open_main_window()

except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Невозможно выполнить аутентификацию: {str(e)}")

def close_current_and_open_main_window(self):
self.close()
self.parent().run_download_server_content()

class RegistrationDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'RegistrationDialog.ui'), self)

self.username_line = self.findChild(QLineEdit, 'UsernameLine')
self.password_line = self.findChild(QLineEdit, 'PasswordLine')
self.password_line_2 = self.findChild(QLineEdit, 'PasswordLine_2')
self.register_button = self.findChild(QPushButton, 'RegisterButton')

self.register_button.clicked.connect(self.register)

def register(self):
username = self.username_line.text()
password = self.password_line.text()
password_2 = self.password_line_2.text()

if not username or not password or not password_2:
QMessageBox.warning(self, "Ошибка", "Все поля должны быть заполнены!")
return

if password != password_2:
QMessageBox.warning(self, "Ошибка", "Пароли должны совпадать!")
return

cl_unit: ClientUnit = ClientUnit.get_instance()
try:
cl_unit.register(username, password)
self.close_current_and_open_main_window()
except Exception as err:
QMessageBox.warning(self, "Ошибка", f"Логин который вы указали уже занят. Подробная ошибка: {err}")

def close_current_and_open_main_window(self):
self.close()
self.parent().run_download_server_content()

class DownloadThread(QThread):
progress_updated = pyqtSignal(int, int)

def __init__(self):
super().__init__()

def run(self):
cl_unit: ClientUnit = ClientUnit.get_instance()
cl_unit.download_server_content(self.report_progress)

def report_progress(self, downloaded_size, total_size):
self.progress_updated.emit(downloaded_size, total_size)

class ServerContentDownloadDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'ServerContentDownloadDialog.ui'), self)

self.progress_bar = self.findChild(QProgressBar, 'progressBar')

self.download_thread = DownloadThread()
self.download_thread.progress_updated.connect(self.update_progress)
self.download_thread.start()

def update_progress(self, downloaded_size, total_size):
self.progress_bar.setMaximum(total_size)
self.progress_bar.setValue(downloaded_size)

if downloaded_size >= total_size:
self.download_thread.quit()
self.download_thread.wait()
self.close()
self.parent().run_main_app()
16 changes: 16 additions & 0 deletions Code/gui/main_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os

from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import (QDialog, QLabel, QLineEdit, QMainWindow,
QMessageBox, QPushButton)
from PyQt6.uic import loadUi
from root_path import ROOT_PATH
from systems.decorators import global_class
from systems.network import ClientUnit


@global_class
class MainApplicationWindow(QMainWindow):
def __init__(self):
super().__init__()
loadUi(os.path.join(ROOT_PATH, 'GUI', 'windows', 'MainWindow.ui'), self)
3 changes: 3 additions & 0 deletions Code/root_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import os

ROOT_PATH = os.path.abspath(os.path.join(__file__, os.pardir, os.pardir))
File renamed without changes.
1 change: 1 addition & 0 deletions Code/systems/bin_system/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from systems.bin_system.bin_system import BinaryFileSystem
76 changes: 76 additions & 0 deletions Code/systems/bin_system/bin_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import atexit
import os
import pickle
from typing import Any, Dict, Optional

from root_path import ROOT_PATH


class BinaryFileSystem:
__slots__ = ['_data', '_file_path']

def __init__(self, file_path: Optional[str], file_name: str) -> None:
"""Инициализирует объект BinaryFileSystem.
На текущий момент данный класс используется как сохранение настроек
Args:
file_path (Optional[str]): Путь до файла с данными
file_name (str): Имя файла
"""
if not file_path:
file_path = ""

full_file_path = os.path.join(ROOT_PATH, "data", full_file_path)

if not os.path.exists(full_file_path):
os.makedirs(full_file_path)

self._file_path = os.path.join(full_file_path, f"{file_name}.bin")

self._data = {}
try:
self._read_file()

except Exception:
self._write_file()

atexit.register(self._write_file)

def _write_file(self) -> None:
"""Записывает данные в файл.
"""
with open(self._file_path, 'wb') as file:
pickle.dump(self._data, file)

def _read_file(self) -> Dict[str, Any]:
"""Читает данные из файла
Returns:
Dict[str, Any]: Словарь данных
"""
with open(self._file_path, 'rb') as file:
self._data = pickle.load(file)

def __getitem__(self, key) -> Any:
"""Возвращает значение по ключу.
Args:
key (str): Ключ для доступа к значению.
Returns:
Any: Значение, соответствующее ключу.
"""
return self._data.get(key, None)

def __setitem__(self, key: str, value: Any) -> None:
"""Устанавливает значение по ключу и сохраняет изменения в файл, если значение отличается от текущего.
Args:
key (str): Ключ для установки значения.
value (Any): Устанавливаемое значение.
"""
if key in self._data and self._data.get(key, None) == value:
return

self._data[key] = value
self._write_file()
28 changes: 28 additions & 0 deletions Code/systems/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
def global_class(cls):
"""Декоратор для обеспечения того, что класс следует паттерну Singleton, то есть создается и используется только один экземпляр класса во всем приложении.
Args:
cls (type): Класс, к которому применяется декоратор.
Returns:
type: Класс с добавленным методом для получения единственного экземпляра.
"""
instances = {}

def get_instance(*args, **kwargs):
"""Возвращает единственный экземпляр класса, создавая его, если он еще не существует.
Args:
*args: Позиционные аргументы для передачи в конструктор класса.
**kwargs: Именованные аргументы для передачи в конструктор класса.
Returns:
object: Единственный экземпляр класса.
"""
if cls not in instances:
instances[cls] = cls(*args, **kwargs)

return instances[cls]

cls.get_instance = get_instance
return cls
4 changes: 4 additions & 0 deletions Code/systems/events_system/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from systems.events_system.event_manager import EventManager
from systems.events_system.register import register_ev

__all__ = ['EventManager', 'register_ev']
Loading

0 comments on commit f282e17

Please sign in to comment.