29 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
4a67e70a92 Обновить README.md 2025-02-16 20:52:02 +00: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
a140b7d8a0 Clean up .gitignore file
- Remove specific config and test files from tracking
- Add Configs/ directory to ignore list
- Remove unnecessary icon and test script entries
2025-02-16 05:36:53 +03:00
2c9edcd859 Remove outdated ComConfigCopy executable binary 2025-02-16 05:35:35 +03:00
5a00efd175 Update executable binary after feature removal 2025-02-16 05:08:44 +03:00
2f4b2985cd Remove documentation and CLI mode features
- Remove "Documentation" menu option
- Delete unused CLI mode code
- Clean up commented-out argument parsing function
2025-02-16 05:00:43 +03:00
d1a870fed7 Add update checking and documentation features
- Implement UpdateChecker for version comparison and update notifications
- Add menu options for documentation and update checking
- Enhance AboutWindow with dynamic version display
- Update requirements.txt with new dependencies
- Create infrastructure for opening local documentation
- Improve application menu with additional help options
2025-02-16 04:50:33 +03:00
2e2dd9e705 Add custom text and entry widgets with enhanced copy/paste functionality
- Implement CustomText and CustomEntry classes with advanced text interaction features
- Add context menu for text widgets with cut, copy, paste, and select all options
- Support multiple keyboard shortcuts for text manipulation
- Replace standard Tkinter Text and Entry widgets with custom implementations
- Remove global text/entry widget bindings in favor of class-specific methods
2025-02-16 03:57:48 +03:00
d937042ea2 Update application title to reflect project name
- Change window title from "Serial Device Manager" to "ComConfigCopy"
2025-02-16 03:53:21 +03:00
136c7877d3 Refactor TFTP file transfer with improved reliability and error handling
- Implement more robust file transfer mechanism with configurable retry and timeout settings
- Add detailed logging for transfer progress and error scenarios
- Enhance block transfer logic with better error recovery
- Simplify transfer socket management and cleanup process
- Improve overall transfer reliability and error tracking
2025-02-16 03:50:27 +03:00
467d582095 Improve file transfer progress tracking and display
- Add dynamic transfer speed calculation
- Compute and display estimated remaining transfer time
- Enhance remaining bytes display with more informative status
- Update transfers table with more detailed transfer progress information
2025-02-16 03:43:34 +03:00
9 changed files with 1874 additions and 385 deletions

5
.gitignore vendored
View File

@@ -1,9 +1,6 @@
app.log
Settings/settings.json
Configs/Eltex MES2424 AC - Сеть FTTB 2G, доп.txt
Configs/конфиг доп 3750-52 с айпи 172.17.141.133 .txt
DALL·E 2024-12-29 01.01.02 - Square vector logo_ A clean and minimalistic app icon for serial port management software. The design prominently features a simplified rectangular CO.ico
test.py
Configs/
__pycache__/
Firmware/1.jpg
Firmware/2

File diff suppressed because it is too large Load Diff

BIN
Icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

44
README.md Normal file
View File

@@ -0,0 +1,44 @@
# ComConfigCopy
Программа для копирования конфигураций на коммутаторы.
## Описание
ComConfigCopy - это утилита, разработанная для автоматизации процесса копирования конфигураций на сетевые коммутаторы. Программа предоставляет удобный графический интерфейс для управления процессом копирования и настройки параметров подключения.
## Основные возможности
- Копирование конфигураций на коммутаторы через COM-порт
- Поддержка различных скоростей подключения
- Автоматическое определение доступных COM-портов
- Возможность сохранения и загрузки настроек
- Автоматическое обновление через GitHub
## Системные требования
- Windows 7/8/10/11
- Python 3.8 или выше
- Доступ к COM-портам
## Установка
1. Скачайте последнюю версию программы из [репозитория](https://gitea.filow.ru/LowaSC/ComConfigCopy/releases)
2. Распакуйте архив в удобное место
3. Запустите файл `ComConfigCopy.exe`
## Использование
1. Выберите COM-порт из списка доступных
2. Настройте параметры подключения (скорость, биты данных и т.д.)
3. Выберите файл конфигурации для отправки
4. Нажмите кнопку "Отправить" для начала процесса копирования
## Контакты
- Email: SPRF555@gmail.com
- Telegram: [@LowaSC](https://t.me/LowaSC)
- Репозиторий: [ComConfigCopy](https://gitea.filow.ru/LowaSC/ComConfigCopy)
## Лицензия
Этот проект распространяется под лицензией MIT. Подробности смотрите в файле [LICENSE](LICENSE).

View File

@@ -224,6 +224,8 @@ class TFTPServer:
Передача файла клиенту по протоколу TFTP.
"""
BLOCK_SIZE = 512
MAX_RETRIES = 5
TIMEOUT = 2.0
transfer_socket = None
try:
if not os.path.exists(file_path):
@@ -249,124 +251,99 @@ class TFTPServer:
'start_time': start_time
}
self.log(f"[INFO] Начало передачи файла '{file_basename}' клиенту {client_addr}. Размер файла: {filesize} байт.")
# Создаем отдельный сокет для передачи файла
# Создаем новый сокет для передачи данных
transfer_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
transfer_socket.settimeout(5.0) # Таймаут ожидания ACK
transfer_socket.settimeout(TIMEOUT)
# Добавляем сокет в множество активных сокетов
with self.lock:
self.transfer_sockets.add(transfer_socket)
block_num = 1
bytes_sent = 0
last_progress_time = time.time()
self.log(f"[INFO] Начало передачи файла '{file_basename}' клиенту {client_addr}. Размер файла: {filesize} байт.")
try:
with open(file_path, "rb") as f:
while self.running: # Проверяем флаг running
with open(file_path, 'rb') as file:
block_number = 1
last_successful_block = 0
while True:
# Читаем блок данных
data = file.read(BLOCK_SIZE)
# Формируем и отправляем пакет данных
packet = struct.pack('!HH', 3, block_number) + data
retries = 0
while retries < MAX_RETRIES:
try:
data_block = f.read(BLOCK_SIZE)
if not data_block: # Достигнут конец файла
break
# Проверяем флаг running перед отправкой блока
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
# Формируем TFTP пакет данных
packet = struct.pack("!HH", 3, block_num) + data_block
attempts = 0
ack_received = False
# Попытка отправки текущего блока (до 3 повторных попыток)
while attempts < 3 and not ack_received and self.running:
if transfer_socket is None:
raise Exception("Сокет передачи закрыт")
transfer_socket.sendto(packet, client_addr)
# Ожидаем подтверждение
while True:
try:
transfer_socket.sendto(packet, client_addr)
# Логируем прогресс каждую секунду
current_time = time.time()
if current_time - last_progress_time >= 1.0:
elapsed_time = current_time - start_time
remaining = filesize - bytes_sent
self.log(f"[STATUS] Клиент: {client_addr} | Файл: {file_basename} | "
f"Отправлено: {bytes_sent}/{filesize} байт | "
f"Осталось: {remaining} байт | "
f"Время: {elapsed_time:.2f} сек.")
last_progress_time = current_time
# Ожидаем подтверждение
ack_data, addr = transfer_socket.recvfrom(4)
if addr == client_addr:
ack_opcode, ack_block = struct.unpack("!HH", ack_data)
if ack_opcode == 4 and ack_block == block_num:
ack_received = True
bytes_sent += len(data_block)
with self.lock:
if client_addr in self.active_transfers:
self.active_transfers[client_addr]['bytes_sent'] = bytes_sent
else:
self.log(f"[WARN] Неверный ACK от {client_addr}. "
f"Ожидался блок {block_num}, получен {ack_block}.")
ack_data, ack_addr = transfer_socket.recvfrom(4)
if ack_addr == client_addr and len(ack_data) >= 4:
opcode, ack_block = struct.unpack('!HH', ack_data)
if opcode == 4: # ACK
if ack_block == block_number:
# Успешное подтверждение
last_successful_block = block_number
bytes_sent = min((block_number * BLOCK_SIZE), filesize)
# Обновляем информацию о прогрессе
with self.lock:
if client_addr in self.active_transfers:
self.active_transfers[client_addr]['bytes_sent'] = bytes_sent
# Логируем статус каждую секунду
current_time = time.time()
if current_time - start_time >= 1.0:
bytes_remaining = filesize - bytes_sent
elapsed_time = current_time - start_time
self.log(f"[STATUS] Клиент: {client_addr} | Файл: {file_basename} | "
f"Отправлено: {bytes_sent}/{filesize} байт | "
f"Осталось: {bytes_remaining} байт | "
f"Время: {elapsed_time:.2f} сек.")
break
elif ack_block < block_number:
# Получен старый ACK, игнорируем
continue
except socket.timeout:
attempts += 1
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_num} "
f"от {client_addr}. Попытка {attempts+1}.")
except socket.error as e:
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
self.log(f"[ERROR] Ошибка сокета при отправке блока {block_num}: {str(e)}")
attempts += 1
except Exception as e:
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
self.log(f"[ERROR] Ошибка при отправке блока {block_num}: {str(e)}")
attempts += 1
if not ack_received:
raise Exception(f"Не удалось получить подтверждение для блока {block_num}")
block_num = (block_num + 1) % 65536
break
if last_successful_block == block_number:
break
else:
retries += 1
self.log(f"[WARN] Таймаут ожидания ACK для блока {block_number} от {client_addr}. "
f"Попытка {retries + 1}.")
except Exception as e:
if not self.running:
raise Exception("Передача прервана: сервер остановлен")
raise
retries += 1
self.log(f"[ERROR] Ошибка при передаче блока {block_number}: {str(e)}")
if retries >= MAX_RETRIES:
self.log(f"[ERROR] Превышено максимальное количество попыток для блока {block_number}")
return
block_number += 1
# Если отправили меньше BLOCK_SIZE байт, это был последний блок
if len(data) < BLOCK_SIZE:
break
if bytes_sent == filesize:
elapsed_time = time.time() - start_time
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} "
f"завершена за {elapsed_time:.2f} сек. Всего отправлено {bytes_sent} байт.")
except Exception as e:
if not self.running:
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} прервана: сервер остановлен")
raise
self.log(f"[INFO] Передача файла '{file_basename}' клиенту {client_addr} завершена успешно")
except Exception as e:
if not self.running:
return # Не логируем повторно о прерывании передачи
self.log(f"[ERROR] Ошибка при передаче файла '{os.path.basename(file_path)}' "
f"клиенту {client_addr}: {str(e)}")
try:
self.send_error(client_addr, 0, str(e))
except:
pass
self.log(f"[ERROR] Ошибка при передаче файла: {str(e)}")
finally:
# Закрываем сокет передачи
if transfer_socket:
try:
with self.lock:
self.transfer_sockets.discard(transfer_socket)
transfer_socket.close()
transfer_socket = None
except:
pass
# Удаляем информацию о передаче
# Очищаем информацию о передаче
with self.lock:
if client_addr in self.active_transfers:
del self.active_transfers[client_addr]
del self.active_transfers[client_addr]
if transfer_socket in self.transfer_sockets:
self.transfer_sockets.remove(transfer_socket)
if transfer_socket:
try:
transfer_socket.close()
except:
pass

View File

@@ -2,19 +2,22 @@
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk, BOTH, X, BOTTOM
from tkinter import ttk, BOTH, X, BOTTOM, END
import webbrowser
class AboutWindow(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("О программе")
self.geometry("400x300")
self.geometry("600x500")
self.resizable(False, False)
# Создаем фрейм
# Сохраняем ссылку на родительское окно
self.parent = parent
# Создаем фрейм для содержимого
about_frame = ttk.Frame(self, padding="20")
about_frame.pack(fill=BOTH, expand=True)
about_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)
# Заголовок
ttk.Label(
@@ -33,7 +36,7 @@ class AboutWindow(tk.Toplevel):
# Версия
ttk.Label(
about_frame,
text="Версия 1.0",
text=f"Версия {getattr(parent, 'VERSION', '1.0.0')}",
font=("Segoe UI", 10)
).pack(pady=(0, 20))
@@ -66,7 +69,7 @@ class AboutWindow(tk.Toplevel):
ttk.Label(
contact_frame,
text="Email: LowaWorkMail@gmail.com"
text="Email: SPRF555@gmail.com"
).pack(anchor="w")
telegram_link = ttk.Label(
@@ -80,14 +83,14 @@ class AboutWindow(tk.Toplevel):
# Кнопка закрытия
ttk.Button(
about_frame,
self,
text="Закрыть",
command=self.destroy
).pack(side=BOTTOM, pady=(20, 0))
).pack(side=BOTTOM, pady=10)
# Центрируем окно
self.center_window()
def center_window(self):
self.update_idletasks()
width = self.winfo_width()
@@ -95,6 +98,6 @@ class AboutWindow(tk.Toplevel):
x = (self.winfo_screenwidth() // 2) - (width // 2)
y = (self.winfo_screenheight() // 2) - (height // 2)
self.geometry(f"{width}x{height}+{x}+{y}")
def open_url(self, url):
webbrowser.open(url)

Binary file not shown.

View File

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

165
update_checker.py Normal file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logging
import requests
import threading
import re
from packaging import version
import xml.etree.ElementTree as ET
import html
class UpdateCheckError(Exception):
"""Исключение для ошибок проверки обновлений"""
pass
class ReleaseType:
"""Типы релизов"""
STABLE = "stable"
PRERELEASE = "prerelease"
class UpdateChecker:
"""Класс для проверки обновлений программы"""
def __init__(self, current_version, repo_url, include_prereleases=False):
self.current_version = current_version
self.repo_url = repo_url
self.include_prereleases = include_prereleases
self.rss_url = f"{repo_url}/releases.rss"
self.release_info = None
def _clean_html(self, html_text):
"""Очищает HTML-разметку и форматирует текст"""
if not html_text:
return ""
text = re.sub(r'<[^>]+>', '', html_text)
text = html.unescape(text)
text = re.sub(r'\n\s*\n', '\n\n', text)
return '\n'.join(line.strip() for line in text.splitlines()).strip()
def _parse_release_info(self, item):
"""Извлекает информацию о релизе из RSS item"""
title = item.find('title').text if item.find('title') is not None else ''
link = item.find('link').text if item.find('link') is not None else ''
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_text = content.text if content is not None else ''
# Извлекаем версию и проверяем тип релиза из тега
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_worker():
try:
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()
root = ET.fromstring(response.content)
items = root.findall('.//item')
if not items:
raise UpdateCheckError("Релизы не найдены")
logging.info(f"Найдено {len(items)} релизов")
latest_version = None
latest_info = None
for item in items:
release_info = self._parse_release_info(item)
if not release_info:
continue
is_prerelease = release_info['type'] == ReleaseType.PRERELEASE
logging.info(
f"Проверка релиза: {release_info['title']}, "
f"версия: {release_info['version']}, "
f"тип: {'пре-релиз' if is_prerelease else 'стабильная'}"
)
# Пропускаем пре-релизы если они не включены
if is_prerelease and not self.include_prereleases:
logging.info(f"Пропуск пре-релиза: {release_info['version']}")
continue
# Сравниваем версии
try:
current_ver = version.parse(latest_version or "0.0.0")
new_ver = version.parse(release_info['version'].split('-')[0]) # Убираем суффикс для сравнения
if new_ver > current_ver:
latest_version = release_info['version']
latest_info = release_info
logging.info(f"Новая версия: {latest_version}")
except version.InvalidVersion as e:
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:
callback(update_available, None)
except UpdateCheckError as e:
logging.error(str(e))
if callback:
callback(False, str(e))
except Exception as e:
logging.error(f"Ошибка при проверке обновлений: {e}", exc_info=True)
if callback:
callback(False, str(e))
threading.Thread(target=check_worker, daemon=True).start()
def get_release_notes(self):
"""Возвращает информацию о последнем релизе"""
return self.release_info