19 Commits

Author SHA1 Message Date
11253286f8 Improved configuration file management with advanced features
- Added send2trash library for safer file deletion
- Implemented folder and file moving functionality in ConfigSelectorWindow
- Created FolderSelectorDialog for moving files to a folder
- Improved file and folder renaming with improved error handling
- Added emoji icons for better visual representation of actions
- Updated requirements.txt to include send2trash library
- Removed drag-and-drop support. Postponed for future
2025-02-19 23:06:15 +03:00
ea432d2893 Add configuration file selection and management window
- Implement ConfigSelectorWindow for advanced configuration file selection
- Add drag-and-drop support for configuration files
- Create file management features: add, edit, rename, and delete configs
- Enhance file selection process with a tree-view interface
- Improve configuration file handling with user-friendly interactions
2025-02-19 22:25:28 +03:00
d3f832cdbb Add connection state verification in command execution
- Check serial connection status before sending commands
- Prevent command execution on closed or inactive connections
- Add informative log message when connection is lost
- Enhance robustness of command sending mechanism
2025-02-19 21:51:55 +03:00
7d92682482 Enhance terminal widgets with read-only mode and clear functionality
- Add read-only mode to TerminalWidget to prevent manual text editing
- Disable text paste and key input in terminal widgets
- Modify context menu to remove cut and paste options
- Add clear buttons for interactive, file execution, and TFTP log terminals
- Implement temporary state changes for text appending and clearing
2025-02-19 21:49:49 +03:00
0ebcf9cb6a Enhance command execution with robust error handling and connection monitoring
- Add consecutive error tracking and connection state checks
- Implement automatic pause and user notification for persistent connection issues
- Improve block and line mode execution with enhanced error detection
- Modify logging and status reporting for better visibility of execution problems
- Add connection verification before and during command execution
2025-02-19 21:39:58 +03:00
99e9163760 Add advanced port monitoring and execution progress tracking
- Implement real-time COM port state monitoring with automatic reconnection
- Add connection indicator with dynamic status updates
- Create progress bar and timer for file command execution
- Enhance execution control with detailed progress tracking
- Implement thread-safe port monitoring and execution mechanisms
- Improve error handling and user feedback during command execution
2025-02-19 21:23:47 +03:00
b573de9166 Add advanced file execution control with pause and stop functionality
- Implement multi-stage command execution from file
- Add start, pause, and stop buttons for file command execution
- Create thread-safe execution control with pause and stop mechanisms
- Enhance error handling and user feedback during file command execution
- Improve command execution flow with step-by-step processing
2025-02-19 21:04:38 +03:00
f45f1bf556 Improve command logging and terminal display formatting
- Simplify command logging messages with concise `[CMD]` prefix
- Remove redundant command attempt counter from log messages
- Modify TerminalWidget to handle `[CMD]` tagged messages
- Enhance command display with consistent formatting and separators
2025-02-19 20:56:25 +03:00
e6766660c6 Add Enter key support for command input
- Bind Enter key to send command in interactive tab
2025-02-18 20:10:51 +03:00
e0705ad6b5 Add advanced TerminalWidget with enhanced text display and formatting
- Implement TerminalWidget class with color-coded message types
- Add support for error, warning, info, and command message styles
- Include automatic scrollbar and text formatting
- Replace CustomText with TerminalWidget in interactive tabs
- Enhance text appending methods with type-specific display
- Improve terminal readability with command separators
2025-02-18 00:35:14 +03:00
c0d8fd8d89 Improve interactive tab buttons with Unicode icons
- Add Unicode icons to the connect, disconnect and send buttons in the interactive tab
2025-02-18 00:11:42 +03:00
ce81100150 Enhance status bar with TFTP server indicator
- Add TFTP server status indicator to the status bar
- Implement tooltip for TFTP server status
- Update status bar update method to reflect TFTP server state
- Improve status bar layout with separate frames for indicators
2025-02-17 19:39:29 +03:00
48c9bd2d40 Bump version to 1.0.2
- Update application version number in ComConfigCopy.py
- Minor version increment to reflect recent changes
2025-02-17 18:35:58 +03:00
e0f64060f5 Add status bar with connection and settings information
- Implement create_status_bar method to add a new status bar
- Add connection indicator with color-coded status
- Create tooltip for connection status details
- Update status bar with current port, baudrate, and copy mode settings
- Integrate status bar updates in connection and settings change events
2025-02-17 18:31:58 +03:00
f5935d6b8f Merge pull request 'v1.0.1' (#4) from v1.0.1 into main
Reviewed-on: #4
2025-02-16 22:02:20 +00:00
1a511ff54f Enhance update checking
- Refactor UpdateChecker
- Add support for parsing release details with improved formatting
- Implement more robust version comparison and release type handling
- Add logging for update checking process
- Improve error handling and release information extraction
- Update update checking logic to handle stable and pre-release versions
2025-02-17 00:53:24 +03:00
f84e20631b Update contact email in README.md
- Change contact email from LowaWorkMail@gmail.com to SPRF555@gmail.com
2025-02-17 00:11:10 +03:00
12562e615f Bump version to 1.0.1
- Update application version number in ComConfigCopy.py
- Minor version increment to reflect recent changes
2025-02-16 23:48:59 +03:00
7ebeb52808 Refactor and optimize code structure with base widget and utility functions
- Create CustomWidgetBase for shared context menu and shortcut functionality
- Add utility functions for common tasks like text appending and file selection
- Simplify command processing with a generic send_command_and_process_response function
- Remove redundant comments and unused imports
- Improve code organization and readability
- Enhance modularity of custom widgets and utility methods
2025-02-16 23:45:19 +03:00
4 changed files with 1579 additions and 370 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -69,7 +69,7 @@ class AboutWindow(tk.Toplevel):
ttk.Label( ttk.Label(
contact_frame, contact_frame,
text="Email: LowaWorkMail@gmail.com" text="Email: SPRF555@gmail.com"
).pack(anchor="w") ).pack(anchor="w")
telegram_link = ttk.Label( telegram_link = ttk.Label(

View File

@@ -1,4 +1,5 @@
tftpy>=0.8.0 tftpy>=0.8.0
pyserial>=3.5 pyserial>=3.5
requests>=2.31.0 requests>=2.31.0
packaging>=23.2 packaging>=23.2
send2trash>=1.8.0

View File

@@ -1,175 +1,165 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json
import logging import logging
import requests import requests
import threading import threading
import re
from packaging import version from packaging import version
import xml.etree.ElementTree as ET
import html
class UpdateCheckError(Exception): class UpdateCheckError(Exception):
"""Исключение для ошибок проверки обновлений""" """Исключение для ошибок проверки обновлений"""
pass pass
class ReleaseType:
"""Типы релизов"""
STABLE = "stable"
PRERELEASE = "prerelease"
class UpdateChecker: class UpdateChecker:
"""Класс для проверки обновлений программы""" """Класс для проверки обновлений программы"""
def __init__(self, current_version, repo_url): def __init__(self, current_version, repo_url, include_prereleases=False):
self.current_version = current_version self.current_version = current_version
self.repo_url = repo_url self.repo_url = repo_url
# Формируем базовый URL API self.include_prereleases = include_prereleases
self.api_url = repo_url.replace("gitea.filow.ru", "gitea.filow.ru/api/v1/repos/LowaSC/ComConfigCopy") self.rss_url = f"{repo_url}/releases.rss"
self._update_available = False self.release_info = None
self._latest_version = None
self._latest_release = None def _clean_html(self, html_text):
self._error = None """Очищает HTML-разметку и форматирует текст"""
self._changelog = None if not html_text:
return ""
def get_changelog(self, callback=None): text = re.sub(r'<[^>]+>', '', html_text)
""" text = html.unescape(text)
Получение changelog из репозитория. text = re.sub(r'\n\s*\n', '\n\n', text)
:param callback: Функция обратного вызова, которая будет вызвана после получения changelog return '\n'.join(line.strip() for line in text.splitlines()).strip()
"""
def fetch(): def _parse_release_info(self, item):
try: """Извлекает информацию о релизе из RSS item"""
# Пытаемся получить CHANGELOG.md из репозитория title = item.find('title').text if item.find('title') is not None else ''
response = requests.get(f"{self.api_url}/contents/CHANGELOG.md", timeout=10) link = item.find('link').text if item.find('link') is not None else ''
response.raise_for_status() description = item.find('description').text if item.find('description') is not None else ''
content = item.find('{http://purl.org/rss/1.0/modules/content/}encoded')
content = response.json() content_text = content.text if content is not None else ''
if "content" in content:
import base64
changelog_content = base64.b64decode(content["content"]).decode("utf-8")
self._changelog = changelog_content
self._error = None
else:
raise UpdateCheckError("Не удалось получить содержимое CHANGELOG.md")
except requests.RequestException as e:
error_msg = f"Ошибка получения changelog: {e}"
logging.error(error_msg, exc_info=True)
self._error = error_msg
self._changelog = None
except Exception as e:
error_msg = f"Неизвестная ошибка при получении changelog: {e}"
logging.error(error_msg, exc_info=True)
self._error = error_msg
self._changelog = None
finally:
if callback:
callback(self._changelog, self._error)
# Запускаем получение в отдельном потоке # Извлекаем версию и проверяем тип релиза из тега
threading.Thread(target=fetch, daemon=True).start() version_match = re.search(r'/releases/tag/(?:pre-)?v?(\d+\.\d+(?:\.\d+)?)', link)
if not version_match:
return None
version_str = version_match.group(1)
# Проверяем наличие префикса pre- в теге
is_prerelease = 'pre-' in link.lower()
# Форматируем название релиза
formatted_title = title
if title == version_str or not title.strip():
# Если заголовок пустой или совпадает с версией, создаем стандартное название
release_type = "Пре-релиз" if is_prerelease else "Версия"
formatted_title = f"{release_type} {version_str}"
elif not re.search(version_str, title):
# Если версия не указана в заголовке, добавляем её
formatted_title = f"{title} ({version_str})"
# Форматируем описание
formatted_description = self._clean_html(content_text or description)
if not formatted_description.strip():
formatted_description = "Нет описания"
# Добавляем метку типа релиза в начало описания
release_type_label = "[Пре-релиз] " if is_prerelease else ""
formatted_description = f"{release_type_label}{formatted_description}"
return {
'title': formatted_title,
'link': link,
'description': formatted_description,
'version': version_str,
'type': ReleaseType.PRERELEASE if is_prerelease else ReleaseType.STABLE
}
def check_updates(self, callback=None): def check_updates(self, callback=None):
""" """Проверяет наличие обновлений в асинхронном режиме"""
Проверка наличия обновлений. def check_worker():
:param callback: Функция обратного вызова, которая будет вызвана после проверки
"""
def check():
try: try:
response = requests.get(f"{self.api_url}/releases", timeout=10) logging.info(f"Текущая версия программы: {self.current_version}")
logging.info(f"Проверка пре-релизов: {self.include_prereleases}")
logging.info(f"Запрос RSS ленты: {self.rss_url}")
response = requests.get(self.rss_url, timeout=10)
response.raise_for_status() response.raise_for_status()
releases = response.json() root = ET.fromstring(response.content)
if not releases: items = root.findall('.//item')
raise UpdateCheckError("Не найдено релизов в репозитории") if not items:
raise UpdateCheckError("Релизы не найдены")
latest_release = releases[0] logging.info(f"Найдено {len(items)} релизов")
latest_version = latest_release.get("tag_name", "").lstrip("v")
if not latest_version: latest_version = None
raise UpdateCheckError("Не удалось определить версию последнего релиза") latest_info = None
try: for item in items:
if version.parse(latest_version) > version.parse(self.current_version): release_info = self._parse_release_info(item)
self._update_available = True if not release_info:
self._latest_version = latest_version continue
self._latest_release = latest_release
logging.info(f"Доступно обновление: {latest_version}") is_prerelease = release_info['type'] == ReleaseType.PRERELEASE
else: logging.info(
logging.info("Обновления не требуются") f"Проверка релиза: {release_info['title']}, "
except version.InvalidVersion as e: f"версия: {release_info['version']}, "
raise UpdateCheckError(f"Некорректный формат версии: {e}") f"тип: {'пре-релиз' if is_prerelease else 'стабильная'}"
)
self._error = None # Пропускаем пре-релизы если они не включены
if is_prerelease and not self.include_prereleases:
logging.info(f"Пропуск пре-релиза: {release_info['version']}")
continue
except requests.RequestException as e: # Сравниваем версии
error_msg = f"Ошибка сетевого подключения: {e}" try:
logging.error(error_msg, exc_info=True) current_ver = version.parse(latest_version or "0.0.0")
self._error = error_msg new_ver = version.parse(release_info['version'].split('-')[0]) # Убираем суффикс для сравнения
except UpdateCheckError as e:
logging.error(str(e), exc_info=True) if new_ver > current_ver:
self._error = str(e) latest_version = release_info['version']
except Exception as e: latest_info = release_info
error_msg = f"Неизвестная ошибка при проверке обновлений: {e}" logging.info(f"Новая версия: {latest_version}")
logging.error(error_msg, exc_info=True)
self._error = error_msg except version.InvalidVersion as e:
finally: logging.warning(f"Некорректный формат версии {release_info['version']}: {e}")
continue
if not latest_info:
raise UpdateCheckError("Не найдены подходящие версии")
self.release_info = latest_info
# Сравниваем с текущей версией
current_ver = version.parse(self.current_version)
latest_ver = version.parse(latest_version.split('-')[0])
update_available = latest_ver > current_ver
logging.info(f"Сравнение версий: текущая {current_ver} <-> последняя {latest_ver}")
logging.info(f"Доступно обновление: {update_available}")
if callback: if callback:
callback(self._update_available, self._error) callback(update_available, None)
@property except UpdateCheckError as e:
def update_available(self): logging.error(str(e))
"""Доступно ли обновление""" if callback:
return self._update_available callback(False, str(e))
except Exception as e:
@property logging.error(f"Ошибка при проверке обновлений: {e}", exc_info=True)
def latest_version(self): if callback:
"""Последняя доступная версия""" callback(False, str(e))
return self._latest_version
threading.Thread(target=check_worker, daemon=True).start()
@property
def error(self):
"""Последняя ошибка при проверке обновлений"""
return self._error
@property
def changelog(self):
"""Текущий changelog"""
return self._changelog
def get_release_notes(self): def get_release_notes(self):
"""Получение информации о последнем релизе""" """Возвращает информацию о последнем релизе"""
if self._latest_release: return self.release_info
return {
"version": self._latest_version,
"description": self._latest_release.get("body", ""),
"download_url": self._latest_release.get("assets", [{}])[0].get("browser_download_url", "")
}
return None
def get_releases(self, callback=None):
"""
Получение списка релизов из репозитория.
:param callback: Функция обратного вызова, которая будет вызвана после получения списка релизов
"""
def fetch():
try:
response = requests.get(f"{self.api_url}/releases", timeout=10)
response.raise_for_status()
releases = response.json()
if not releases:
raise UpdateCheckError("Не найдено релизов в репозитории")
self._error = None
if callback:
callback(releases, None)
except requests.RequestException as e:
error_msg = f"Ошибка сетевого подключения: {e}"
logging.error(error_msg, exc_info=True)
self._error = error_msg
if callback:
callback(None, error_msg)
except Exception as e:
error_msg = f"Ошибка при получении списка релизов: {e}"
logging.error(error_msg, exc_info=True)
self._error = error_msg
if callback:
callback(None, error_msg)
# Запускаем получение в отдельном потоке
threading.Thread(target=fetch, daemon=True).start()