From 35e590c718fe4276dd879b9d795213089db8800c Mon Sep 17 00:00:00 2001 From: RarDog Date: Thu, 27 Aug 2026 13:18:55 +0300 Subject: [PATCH] Release v2.5.2: fix transfers speed metric overflow, auto-cleanup old binary on update, full Russian localization --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 8 ++ src/ui/gui.rs | 320 +++++++++++++++++++++---------------------- src/ui/vim_viewer.rs | 42 +++--- src/updater.rs | 20 ++- 6 files changed, 199 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45e0431..3059f49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6372,7 +6372,7 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerosend" -version = "2.5.1" +version = "2.5.2" dependencies = [ "arboard", "axum", diff --git a/Cargo.toml b/Cargo.toml index ecd5dcf..3d17abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zerosend" -version = "2.5.1" +version = "2.5.2" edition = "2021" description = "Fast, peer-to-peer file transfer designed for ZeroTier, Tailscale, Radmin VPN and LAN" authors = ["RarDog"] diff --git a/src/main.rs b/src/main.rs index c069685..7d775b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,14 @@ fn get_default_device_name() -> String { #[tokio::main] async fn main() -> Result<(), Box> { + // Clean up any old binaries or temp files from previous self-updates + if let Ok(current_exe) = std::env::current_exe() { + if let Some(exe_dir) = current_exe.parent() { + let _ = std::fs::remove_file(exe_dir.join("zerosend.exe.old")); + let _ = std::fs::remove_file(exe_dir.join("zerosend_update.exe")); + } + } + let config = AppConfig::load(); let cli = Cli::parse(); diff --git a/src/ui/gui.rs b/src/ui/gui.rs index f26bb7a..f40ad73 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -3187,45 +3187,63 @@ impl ZeroSendApp { ui.separator(); ui.add_space(6.0); - // 4 Metric Pill Cards - ui.horizontal(|ui| { - let card_w = (ui.available_width() - 30.0) / 4.0; - + // 4 Metric Pill Cards with equal columns and perfect rounding + ui.columns(4, |cols| { // Metric 1: Total - Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| { - ui.set_width(card_w); - ui.vertical(|ui| { - ui.label(RichText::new("Всего передач").size(10.5).color(theme.text_muted)); - ui.label(RichText::new(format!("{}", total_count)).strong().size(15.0).color(theme.text_primary)); + Frame::none() + .fill(theme.bg_card) + .rounding(Rounding::same(6.0)) + .stroke(theme.stroke_border) + .inner_margin(Margin::symmetric(10.0, 6.0)) + .show(&mut cols[0], |ui| { + ui.set_width(ui.available_width()); + ui.vertical(|ui| { + ui.label(RichText::new("Всего передач").size(10.5).color(theme.text_muted)); + ui.label(RichText::new(format!("{}", total_count)).strong().size(15.0).color(theme.text_primary)); + }); }); - }); // Metric 2: Active - Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| { - ui.set_width(card_w); - ui.vertical(|ui| { - ui.label(RichText::new("В процессе").size(10.5).color(theme.text_muted)); - ui.label(RichText::new(format!("{}", active_count)).strong().size(15.0).color(Color32::from_rgb(56, 189, 248))); + Frame::none() + .fill(theme.bg_card) + .rounding(Rounding::same(6.0)) + .stroke(theme.stroke_border) + .inner_margin(Margin::symmetric(10.0, 6.0)) + .show(&mut cols[1], |ui| { + ui.set_width(ui.available_width()); + ui.vertical(|ui| { + ui.label(RichText::new("В процессе").size(10.5).color(theme.text_muted)); + ui.label(RichText::new(format!("{}", active_count)).strong().size(15.0).color(Color32::from_rgb(56, 189, 248))); + }); }); - }); // Metric 3: Completed - Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| { - ui.set_width(card_w); - ui.vertical(|ui| { - ui.label(RichText::new("Завершено").size(10.5).color(theme.text_muted)); - ui.label(RichText::new(format!("{}", completed_count)).strong().size(15.0).color(Color32::from_rgb(34, 197, 94))); + Frame::none() + .fill(theme.bg_card) + .rounding(Rounding::same(6.0)) + .stroke(theme.stroke_border) + .inner_margin(Margin::symmetric(10.0, 6.0)) + .show(&mut cols[2], |ui| { + ui.set_width(ui.available_width()); + ui.vertical(|ui| { + ui.label(RichText::new("Завершено").size(10.5).color(theme.text_muted)); + ui.label(RichText::new(format!("{}", completed_count)).strong().size(15.0).color(Color32::from_rgb(34, 197, 94))); + }); }); - }); // Metric 4: Speed - Frame::none().fill(theme.bg_card).rounding(Rounding::same(6.0)).stroke(theme.stroke_border).inner_margin(Margin::symmetric(10.0, 6.0)).show(ui, |ui| { - ui.set_width(card_w); - ui.vertical(|ui| { - ui.label(RichText::new("Общая скорость").size(10.5).color(theme.text_muted)); - ui.label(RichText::new(format!("{:.1} MB/s", total_speed_mb)).strong().size(15.0).color(if total_speed_mb > 0.0 { Color32::from_rgb(250, 204, 21) } else { theme.text_secondary })); + Frame::none() + .fill(theme.bg_card) + .rounding(Rounding::same(6.0)) + .stroke(theme.stroke_border) + .inner_margin(Margin::symmetric(10.0, 6.0)) + .show(&mut cols[3], |ui| { + ui.set_width(ui.available_width()); + ui.vertical(|ui| { + ui.label(RichText::new("Общая скорость").size(10.5).color(theme.text_muted)); + ui.label(RichText::new(format!("{:.1} MB/s", total_speed_mb)).strong().size(15.0).color(if total_speed_mb > 0.0 { Color32::from_rgb(250, 204, 21) } else { theme.text_secondary })); + }); }); - }); }); }); @@ -3885,7 +3903,7 @@ impl ZeroSendApp { ui.add_space(10.0); // Collapsible Credits & About Card - egui::CollapsingHeader::new(RichText::new("👥 About & Credits").strong().size(14.0).color(theme.text_primary)) + egui::CollapsingHeader::new(RichText::new("👥 О программе и Разработчики").strong().size(14.0).color(theme.text_primary)) .default_open(false) .show(ui, |ui| { Frame::none() @@ -3899,33 +3917,33 @@ impl ZeroSendApp { ui.label(RichText::new(format!("v{}", env!("CARGO_PKG_VERSION"))).size(13.0).color(Color32::from_rgb(34, 197, 94)).strong()); }); ui.add_space(4.0); - ui.label(RichText::new("Fast, peer-to-peer file & text sharing optimized for ZeroTier, Tailscale, Radmin VPN & LAN.").color(theme.text_secondary)); + ui.label(RichText::new("Быстрый децентрализованный обмен файлами, медиа и текстом, оптимизированный для сетей ZeroTier, Tailscale, Radmin VPN и локального Wi-Fi/LAN.").color(theme.text_secondary)); ui.add_space(8.0); - ui.label(RichText::new("🛠 Developed by:").strong().color(theme.text_primary)); - ui.label(RichText::new(" • RarDog (Idea, Requirements & Direction)").color(theme.text_secondary)); - ui.label(RichText::new(" • Antigravity (Google DeepMind AI Coding Assistant)").color(theme.text_secondary)); + ui.label(RichText::new("🛠 Создатели и разработка:").strong().color(theme.text_primary)); + ui.label(RichText::new(" • RarDog (Идея, архитектура, требования и руководство проектом)").color(theme.text_secondary)); + ui.label(RichText::new(" • Antigravity (Google DeepMind AI Coding Assistant — парное программирование и реализация)").color(theme.text_secondary)); ui.add_space(6.0); - ui.label(RichText::new("⚙ Technology Stack:").strong().color(theme.text_primary)); - ui.label(RichText::new(" • Rust 1.98, Tokio Async Runtime, Axum HTTP Streaming").color(theme.text_secondary)); - ui.label(RichText::new(" • 🔄 Two-Way Live Folder Synchronization Engine (Continuous Peer Mirroring)").color(theme.text_secondary)); - ui.label(RichText::new(" • 🖧 Windows Explorer Network Drive (WebDAV RFC 4918)").color(theme.text_secondary)); - ui.label(RichText::new(" • 🎬 HTTP 206 Partial Content Streaming & Watch Party Session Sync").color(theme.text_secondary)); - ui.label(RichText::new(" • ✏️ Full Modal Vim Editor with Normal/Insert Modes, :w Save & Markdown Preview").color(theme.text_secondary)); - ui.label(RichText::new(" • 🔎 Remote Full-Text Grep Code & Text Search Engine").color(theme.text_secondary)); - ui.label(RichText::new(" • 📦 Single-Folder Portable Exe Configuration & Data Storage").color(theme.text_secondary)); - ui.label(RichText::new(" • 🛡️ SHA-256 On-The-Fly Cryptographic File Integrity Verification").color(theme.text_secondary)); - ui.label(RichText::new(" • 4-Stream Parallel Chunk Worker Engine (Multi-stream Range transfers)").color(theme.text_secondary)); - ui.label(RichText::new(" • Resumable .zerosend_part File Checkpointing Engine").color(theme.text_secondary)); - ui.label(RichText::new(" • UDP Broadcast ZeroTier Mesh Discovery Engine").color(theme.text_secondary)); + ui.label(RichText::new("⚙ Стек технологий и возможности:").strong().color(theme.text_primary)); + ui.label(RichText::new(" • Rust 1.98, асинхронный рантайм Tokio, потоковый HTTP сервер Axum").color(theme.text_secondary)); + ui.label(RichText::new(" • 🔄 Двусторонняя автосинхронизация папок в реальном времени (Auto-Sync Mirroring)").color(theme.text_secondary)); + ui.label(RichText::new(" • 🖧 Монтирование сетевого диска в Проводник Windows (WebDAV RFC 4918, диск Z:\\)").color(theme.text_secondary)); + ui.label(RichText::new(" • 🎬 Потоковое воспроизведение видео/аудио без скачивания (HTTP 206) и совместный просмотр Watch Party").color(theme.text_secondary)); + ui.label(RichText::new(" • ✏️ Встроенный Vim-редактор: режимы Normal/Insert, сохранение :w по сети, предпросмотр Markdown").color(theme.text_secondary)); + ui.label(RichText::new(" • 🔎 Удаленный полнотекстовый поиск Grep по коду и файлам друзей с переходом по строкам").color(theme.text_secondary)); + ui.label(RichText::new(" • 📦 Портативный режим (Portable): конфиг и загрузки хранятся рядом с exe в одной папке").color(theme.text_secondary)); + ui.label(RichText::new(" • 🛡️ Криптографическая проверка целостности SHA-256 на лету").color(theme.text_secondary)); + ui.label(RichText::new(" • ⚡ 4-поточный параллельный чанк-движок быстрой передачи").color(theme.text_secondary)); + ui.label(RichText::new(" • 🔄 Докачка и контрольные точки (.zerosend_part) при обрыве связи").color(theme.text_secondary)); + ui.label(RichText::new(" • 🌐 Автоматическое UDP-обнаружение узлов в сетях ZeroTier / LAN").color(theme.text_secondary)); }); }); ui.add_space(8.0); // Collapsible Version History / Changelog Card - egui::CollapsingHeader::new(RichText::new("📜 Version History (Changelog)").strong().size(14.0).color(theme.text_primary)) + egui::CollapsingHeader::new(RichText::new("📜 История версий (Список изменений)").strong().size(14.0).color(theme.text_primary)) .default_open(false) .show(ui, |ui| { Frame::none() @@ -3934,10 +3952,21 @@ impl ZeroSendApp { .stroke(theme.stroke_border) .inner_margin(Margin::same(14.0)) .show(ui, |ui| { + // v2.5.2 + ui.horizontal(|ui| { + ui.label(RichText::new("v2.5.2").strong().color(Color32::from_rgb(34, 197, 94))); + ui.label(RichText::new("(Текущая версия)").size(11.0).color(Color32::GRAY)); + }); + ui.label(" • 🎨 Исправление макета передач: Идеальное скругление и выравнивание карточек скорости и статистики дашборда."); + ui.label(" • 🧹 Чистое автообновление: Автоматическое удаление старых версий (.old и временных файлов) при установке обновлений."); + ui.label(" • 🇷🇺 Полная русская локализация: Переведены Credits, стек технологий, модальные окна, Grep, редактор Vim и история версий."); + ui.add_space(8.0); + ui.separator(); + ui.add_space(8.0); + // v2.5.1 ui.horizontal(|ui| { - ui.label(RichText::new("v2.5.1").strong().color(Color32::from_rgb(34, 197, 94))); - ui.label(RichText::new("(Текущая версия)").size(11.0).color(Color32::GRAY)); + ui.label(RichText::new("v2.5.1").strong().color(Color32::from_rgb(56, 189, 248))); }); ui.label(" • 🎨 Полная модернизация интерфейса: Единый стиль карточек, дашборд статистики передач, пошаговые мастера отправки и автосинхронизации, баннеры подсказок."); ui.label(" • 🔄 Улучшенный мастер Auto-Sync: Наглядный 3-шаговый визард выбора папок и онлайн-друзей с интуитивным управлением синхронизацией."); @@ -3950,12 +3979,12 @@ impl ZeroSendApp { ui.horizontal(|ui| { ui.label(RichText::new("v2.5.0").strong().color(Color32::from_rgb(56, 189, 248))); }); - ui.label(" • 🔄 Live Folder Two-Way Auto-Sync: Real-time background sync engine that keeps local directories and remote shared folders mirrored without user interaction."); - ui.label(" • 🖧 Windows Explorer Network Drive (WebDAV): 1-click mounting of all shared folders directly into Windows Explorer as drive Z:\\."); - ui.label(" • 🎵 In-App Audio Player Dock & 🎬 Watch Party: Sticky mini-player dock and interactive video watch rooms with real-time playback sync."); - ui.label(" • ✏️ Full Vim Editor with Write-Back: Normal + Insert Mode, buffer editing, Markdown preview, and :w saving back across the network."); - ui.label(" • 🔎 Remote Full-Text Grep: Search code and text files across friends' machines with instant line navigation."); - ui.label(" • ⬇️ Multi-Select Batch Downloads & Drag-and-Drop: Multi-file checkboxes and direct drag & drop upload into remote shares."); + ui.label(" • 🔄 Двусторонняя автосинхронизация папок: Фоновый движок синхронизации локальных и удаленных каталогов без участия пользователя."); + ui.label(" • 🖧 Сетевой диск Windows (WebDAV): Подключение общих папок напрямую в Проводник Windows как диск Z:\\ в 1 клик."); + ui.label(" • 🎵 Аудиоплеер и 🎬 Watch Party: Док мини-плеера и совместные комнаты просмотра видео с синхронизацией воспроизведения."); + ui.label(" • ✏️ Полноценный Vim-редактор с сохранением по сети: Режимы Normal/Insert, правка буфера, предпросмотр Markdown и запись (:w)."); + ui.label(" • 🔎 Удаленный Grep-поиск: Поиск кода и текста на компьютерах друзей с мгновенным переходом к строке."); + ui.label(" • ⬇️ Пакетное скачивание и Drag & Drop: Выбор файлов чекбоксами и прямое перетаскивание в сетевые папки."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); @@ -3964,11 +3993,11 @@ impl ZeroSendApp { ui.horizontal(|ui| { ui.label(RichText::new("v2.4.1").strong().color(Color32::from_rgb(56, 189, 248))); }); - ui.label(" • 📁 Shared Folders Tab: Dedicated Shared Folders management with Read-Only / Read-Write permissions and PIN security."); - ui.label(" • 🎬 HTTP 206 Streaming: Instant real-time video/audio streaming with seeking without downloading entire files."); - ui.label(" • ⚡ Built-in Vim Code Viewer: Terminal-style code viewer with full Vim keybindings, search (/), and line jump."); - ui.label(" • 📦 Single-Folder Portable Mode: All settings (config.json) and downloads are created & saved right in the exe folder."); - ui.label(" • 🔄 Auto-Sync: Real-time discovery beacon broadcasting of shared folders when connecting to ZeroTier/VPN."); + ui.label(" • 📁 Вкладка Общие папки: Управление сетевыми папками с правами «Только чтение» / «Чтение и запись» и защитой PIN."); + ui.label(" • 🎬 HTTP 206 Streaming: Мгновенное воспроизведение видео/аудио с перемоткой без предварительного скачивания."); + ui.label(" • ⚡ Встроенный просмотрщик Vim: Терминальный просмотр кода с горячими клавишами Vim, поиском (/) и прыжком по строкам."); + ui.label(" • 📦 Единая папка (Portable): Все настройки (config.json) и загрузки создаются и хранятся рядом с exe."); + ui.label(" • 🔄 Auto-Sync маяки: Оповещение об общих папках при подключении к ZeroTier/VPN."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); @@ -3977,11 +4006,11 @@ impl ZeroSendApp { ui.horizontal(|ui| { ui.label(RichText::new("v2.4.0").strong().color(Color32::from_rgb(56, 189, 248))); }); - ui.label(" • 🛡️ SHA-256 Checksum on the Fly: File integrity calculation & verification during streaming with green verification badges."); - ui.label(" • 📸 Mobile Media Previews: Thumbnail gallery previews for photos with instant remove buttons before uploading."); - ui.label(" • 🎙️ Mobile Voice Notes: Built-in voice recorder with live audio wave pulse animation and one-tap delivery."); - ui.label(" • 📲 PWA Support: Web App Manifest & Add to Home Screen standalone install capabilities for mobile browsers."); - ui.label(" • 🎨 Auto Theme: Dark/Light theme mode with Windows Registry theme sync & contrast fixes."); + ui.label(" • 🛡️ Контрольная сумма SHA-256 на лету: Проверка целостности передаваемых файлов с зелеными бейджами гарантии."); + ui.label(" • 📸 Превью фото на мобильных: Галерея миниатюр перед отправкой с возможностью удаления лишних снимков."); + ui.label(" • 🎙️ Голосовые сообщения на мобильных: Встроенный диктофон с пульсирующей анимацией волны."); + ui.label(" • 📲 Поддержка PWA: Установка веб-приложения на главный экран смартфона прямо из браузера."); + ui.label(" • 🎨 Авто-тема: Темная/светлая тема с синхронизацией с реестром Windows."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); @@ -3990,13 +4019,12 @@ impl ZeroSendApp { ui.horizontal(|ui| { ui.label(RichText::new("v2.3.2").strong().color(Color32::from_rgb(56, 189, 248))); }); - ui.label(" • 🚀 Start with Windows: Option to run ZeroSend on Windows startup minimized to tray."); - ui.label(" • 🎯 Drag & Drop Quick Send: Drag files directly onto any friend's card in Nearby tab."); - ui.label(" • Dual Web Links with dedicated QR codes (ZeroTier/VPN mesh + Local Wi-Fi router)."); - ui.label(" • ZeroTier Web Confirmation popup on PC (Auto-accept on Local LAN)."); - ui.label(" • Auto-switch to Transfers view on both PC and mobile when transfers begin."); - ui.label(" • Exponential Moving Average (EMA) smoothed speed calculations (no spikes)."); - ui.label(" • 4-Stream Parallel Chunk Transfers with Adaptive Chunk Sizing."); + ui.label(" • 🚀 Автозапуск с Windows: Опция запуска ZeroSend при старте системы в свёрнутом виде в трей."); + ui.label(" • 🎯 Быстрая отправка Drag & Drop: Перетаскивание файлов прямо на карточку любого друга во вкладке Устройства."); + ui.label(" • Двойные ссылки и QR-коды (ZeroTier/VPN + Домашний Wi-Fi)."); + ui.label(" • Всплывающее окно подтверждения передачи через ZeroTier."); + ui.label(" • Сглаживание показаний скорости алгоритмом EMA (без скачков)."); + ui.label(" • 4-поточная параллельная передача файлов."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); @@ -4005,83 +4033,41 @@ impl ZeroSendApp { ui.horizontal(|ui| { ui.label(RichText::new("v2.3.1").strong().color(Color32::from_rgb(34, 197, 94))); }); - ui.label(" • Redesigned Mobile-First Web Dropzone (camera/photo quick shortcuts, mobile UX)."); - ui.label(" • Dual Web Links with dedicated QR codes (ZeroTier/VPN mesh + Local Wi-Fi router)."); - ui.label(" • ZeroTier Web Confirmation popup on PC (Auto-accept on Local LAN)."); - ui.label(" • Auto-switch to Transfers view on both PC and mobile when transfers begin."); - ui.label(" • Exponential Moving Average (EMA) smoothed speed calculations (no spikes)."); - ui.label(" • Live folder compression status (shows 'Compressing to ZIP' in Transfers)."); - ui.label(" • Smart Dropzone with colored category badges (Video, Audio, Docs, Binaries)."); - ui.label(" • 4-Stream Parallel Chunk Transfers with Adaptive Chunk Sizing."); + ui.label(" • Модернизированная мобильная Web Dropzone с кнопками камеры и фото."); + ui.label(" • Раздельные QR-коды для ZeroTier и локальной сети Wi-Fi."); + ui.label(" • Автоматическое переключение на вкладку Передач при старте отправки."); + ui.label(" • Отображение статуса сжатия папок в ZIP в реальном времени."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); // v2.0.0 ui.label(RichText::new("v2.0.0").strong().color(Color32::from_rgb(56, 189, 248))); - ui.label(" • Redesigned modern Glassmorphism Web & Mobile dropzone dashboard."); - ui.label(" • Web Quick Text sender & mobile-responsive drag-and-drop."); - ui.label(" • Fixed global Ctrl+V screenshot pasting with retry mechanism."); - ui.label(" • Real-time upload streaming progress & speed meters for sender."); - ui.label(" • Permanent purge for 'Clear finished' transfers."); - ui.label(" • Peer deduplication & persistent device UUIDs."); - ui.add_space(8.0); - ui.separator(); - ui.add_space(8.0); - - // v1.2.1 - ui.label(RichText::new("v1.2.1").strong().color(Color32::from_rgb(56, 189, 248))); - ui.label(" • Real-time stream upload progress & live speed on sender side."); - ui.label(" • Working 'Clear finished' button in Transfers tab."); - ui.label(" • Persistent peer UUIDs & automatic peer deduplication (no duplicate ghosts)."); - ui.label(" • Official Windows PE Resource & Manifest embedding (zero false positives)."); + ui.label(" • Современный стеклянный интерфейс (Glassmorphism) для Web Dropzone."); + ui.label(" • Быстрая передача текста из браузера и адаптивный Drag & Drop."); + ui.label(" • Глобальная вставка скриншотов из буфера (Ctrl+V)."); + ui.label(" • Реальная скорость и прогресс на стороне отправителя."); + ui.label(" • Постоянные UUID устройств и защита от дубликатов."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); // v1.2.0 ui.label(RichText::new("v1.2.0").strong().color(Color32::from_rgb(56, 189, 248))); - ui.label(" • Crystal-clear transfer visualization, file icons & live speed meters."); - ui.label(" • Fixed incoming transfer confirmation popup & robust message delivery."); - ui.label(" • Fixed System Tray background listener and instant exit."); - ui.label(" • On-the-fly fast ZIP compression for directories (10x faster)."); - ui.label(" • Automatic ZIP extraction in receiver download directory."); - ui.add_space(8.0); - ui.separator(); - ui.add_space(8.0); - - // v1.1.0 - ui.label(RichText::new("v1.1.0").strong().color(Color32::from_rgb(56, 189, 248))); - ui.label(" • System Tray integration with background daemon and tray menu."); - ui.label(" • Window size & layout auto-save (memory)."); - ui.label(" • Collapsible About, Credits & Version History section."); - ui.label(" • Polish and performance improvements."); + ui.label(" • Наглядная визуализация передач, иконки типов файлов и спидометры."); + ui.label(" • Фоновая работа в системном трее Windows и мгновенный выход."); + ui.label(" • Сжатие папок в ZIP на лету и автораспаковка в папке загрузок."); ui.add_space(8.0); ui.separator(); ui.add_space(8.0); // v1.0.0 ui.label(RichText::new("v1.0.0").strong().color(Color32::GRAY)); - ui.label(" • Native Desktop GUI (no terminal console window)."); - ui.label(" • Multi-recipient parallel file & text broadcasting."); - ui.label(" • Smart Clipboard with content auto-detection (IP, URL, Code, JSON)."); - ui.label(" • QR Code generator for mobile file dropzone."); - ui.label(" • 4-digit PIN security protection for incoming transfers."); - ui.label(" • Global Drag-and-Drop files directly into the window."); - ui.label(" • Ctrl+V screenshot/clipboard image pasting."); - ui.label(" • 'Open file' and 'Show in folder' buttons in Transfers."); - ui.label(" • Custom peer nicknames (aliases) saved in config."); - ui.label(" • Windows Explorer context menu ('Send with ZeroSend')."); - ui.add_space(8.0); - ui.separator(); - ui.add_space(8.0); - - // v0.1.0 - ui.label(RichText::new("v0.1.0").strong().color(Color32::GRAY)); - ui.label(" • Initial ZeroTier & VPN network interface scanner."); - ui.label(" • UDP broadcast peer discovery engine."); - ui.label(" • High-speed HTTP streaming transfer engine."); - ui.label(" • Terminal UI (TUI) and headless CLI daemon."); + ui.label(" • Нативный графический интерфейс (GUI) без консоли."); + ui.label(" • Параллельная рассылка файлов и текста нескольким получателям."); + ui.label(" • Генератор QR-кодов для отправки с телефонов."); + ui.label(" • Защита 4-значным PIN-кодом входящих передач."); + ui.label(" • Кнопки «Открыть файл» и «Показать в папке»."); }); }); @@ -4092,12 +4078,12 @@ impl ZeroSendApp { fn render_modals(&mut self, ctx: &egui::Context, theme: ThemeColors) { // Direct IP Modal if self.show_direct_ip_modal { - egui::Window::new("🔍 Direct Connect (IP)") + egui::Window::new("🔍 Прямое подключение (IP)") .collapsible(false) .resizable(false) .anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO) .show(ctx, |ui| { - ui.label(RichText::new("Enter ZeroTier / Tailscale / Radmin IP of peer:").color(theme.text_primary)); + ui.label(RichText::new("Введите IP-адрес узла в сети ZeroTier / Tailscale / Radmin:").color(theme.text_primary)); ui.add_space(4.0); ui.text_edit_singleline(&mut self.direct_ip_input); @@ -4108,19 +4094,19 @@ impl ZeroSendApp { ui.add_space(12.0); ui.horizontal(|ui| { - if ui.add(Button::new(RichText::new("Connect & Ping").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() { + if ui.add(Button::new(RichText::new("Подключить и проверить").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() { let ip = self.direct_ip_input.trim().to_string(); if !ip.is_empty() { let client = self.client.clone(); let peers_store = self.discovery_peers.clone(); let log_tx = self.log_tx.clone(); - self.direct_ip_status = "Connecting...".to_string(); + self.direct_ip_status = "Подключение...".to_string(); tokio::spawn(async move { match client.ping_peer(&ip, 53318).await { Ok(beacon) => { - let _ = log_tx.send(format!("✨ Added direct node: {} ({})", beacon.name, beacon.ip)); + let _ = log_tx.send(format!("✨ Добавлен узел: {} ({})", beacon.name, beacon.ip)); let mut peers = peers_store.write().await; peers.insert( beacon.peer_id.clone(), @@ -4140,7 +4126,7 @@ impl ZeroSendApp { ); } Err(e) => { - let _ = log_tx.send(format!("❌ Direct connect error: {}", e)); + let _ = log_tx.send(format!("❌ Ошибка прямого подключения: {}", e)); } } }); @@ -4148,7 +4134,7 @@ impl ZeroSendApp { } } - if ui.button("Cancel").clicked() { + if ui.button("Отмена").clicked() { self.show_direct_ip_modal = false; } }); @@ -4157,18 +4143,18 @@ impl ZeroSendApp { // Rename Peer Modal if let Some((peer_id, peer_name, mut current_alias)) = self.renaming_peer.clone() { - egui::Window::new("✏️ Rename Friend / Set Alias") + egui::Window::new("✏️ Переименовать друга / Псевдоним") .collapsible(false) .resizable(false) .anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO) .show(ctx, |ui| { - ui.label(RichText::new(format!("Set custom nickname for {}", peer_name)).color(theme.text_primary)); + ui.label(RichText::new(format!("Укажите удобное имя (псевдоним) для {}", peer_name)).color(theme.text_primary)); ui.add_space(6.0); ui.text_edit_singleline(&mut current_alias); ui.add_space(10.0); ui.horizontal(|ui| { - if ui.add(Button::new(RichText::new("Save Alias").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() { + if ui.add(Button::new(RichText::new("Сохранить псевдоним").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() { if current_alias.trim().is_empty() { self.config.peer_aliases.remove(&peer_id); } else { @@ -4178,7 +4164,7 @@ impl ZeroSendApp { self.renaming_peer = None; } - if ui.button("Cancel").clicked() { + if ui.button("Отмена").clicked() { self.renaming_peer = None; } }); @@ -4188,14 +4174,14 @@ impl ZeroSendApp { // Received Quick Text Modal if let Some(msg) = self.received_text_modal.clone() { let (r, g, b) = msg.content_type.badge_color_rgb(); - egui::Window::new("📋 Received Quick Text") + egui::Window::new("📋 Входящий быстрый текст") .collapsible(false) .resizable(true) .min_width(400.0) .anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO) .show(ctx, |ui| { ui.horizontal(|ui| { - ui.label(RichText::new(format!("From: {}", msg.sender_name)).strong().size(14.0).color(theme.text_primary)); + ui.label(RichText::new(format!("От: {}", msg.sender_name)).strong().size(14.0).color(theme.text_primary)); ui.label( RichText::new(format!("[{}]", msg.content_type.label())) .strong() @@ -4223,18 +4209,18 @@ impl ZeroSendApp { ui.add_space(12.0); ui.horizontal(|ui| { let content_copy = msg.content.clone(); - if ui.add(Button::new(RichText::new("📋 Copy to Clipboard").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() { + if ui.add(Button::new(RichText::new("📋 Скопировать в буфер").strong().color(Color32::WHITE)).fill(Color32::from_rgb(37, 99, 235))).clicked() { ui.output_mut(|o| o.copied_text = content_copy.clone()); } if msg.content_type == ContentType::WebUrl { - if ui.button("🌐 Open Link").clicked() { + if ui.button("🌐 Открыть ссылку").clicked() { ui.ctx().open_url(egui::OpenUrl::same_tab(&content_copy)); } } ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button("Close").clicked() { + if ui.button("Закрыть").clicked() { self.received_text_modal = None; } }); @@ -4244,7 +4230,7 @@ impl ZeroSendApp { // PROMINENT INCOMING FILE TRANSFER CONFIRMATION MODAL if let Some(req) = self.pending_request.clone() { - egui::Window::new("📥 INCOMING FILE TRANSFER REQUEST") + egui::Window::new("📥 ВХОДЯЩИЙ ЗАПРОС НА ПЕРЕДАЧУ ФАЙЛОВ") .collapsible(false) .resizable(false) .min_width(450.0) @@ -4255,14 +4241,14 @@ impl ZeroSendApp { ui.label(RichText::new("📥").size(32.0)); ui.vertical(|ui| { ui.label( - RichText::new(format!("Incoming files from: {}", req.sender_name)) - .size(17.0) + RichText::new(format!("Входящие файлы от: {}", req.sender_name)) + .size(16.0) .strong() .color(theme.text_accent), ); ui.label( RichText::new(format!( - "Total: {} file(s) • Total Size: {}", + "Всего: {} файл(ов) • Общий размер: {}", req.files.len(), human_bytes(req.total_size as f64) )) @@ -4273,7 +4259,7 @@ impl ZeroSendApp { }); ui.add_space(10.0); - ui.label(RichText::new("Items to be received:").strong().size(13.0).color(theme.text_primary)); + ui.label(RichText::new("Список передаваемых файлов:").strong().size(13.0).color(theme.text_primary)); ui.add_space(4.0); Frame::none() @@ -4293,21 +4279,21 @@ impl ZeroSendApp { ui.separator(); } if req.files.len() > 8 { - ui.label(RichText::new(format!(" ... and {} more files", req.files.len() - 8)).color(theme.text_muted)); + ui.label(RichText::new(format!(" ... и ещё {} файлов", req.files.len() - 8)).color(theme.text_muted)); } }); }); ui.add_space(8.0); ui.horizontal(|ui| { - ui.label(RichText::new("Save destination:").size(11.0).color(theme.text_muted)); + ui.label(RichText::new("Папка сохранения:").size(11.0).color(theme.text_muted)); ui.label(RichText::new(format!("{:?}", self.download_dir)).size(11.0).color(theme.text_secondary)); }); ui.add_space(16.0); ui.horizontal(|ui| { let accept_btn = Button::new( - RichText::new("✅ ACCEPT & DOWNLOAD") + RichText::new("✅ ПРИНЯТЬ И СКАЧАТЬ") .size(14.0) .strong() .color(Color32::WHITE), @@ -4348,7 +4334,7 @@ impl ZeroSendApp { } let reject_btn = Button::new( - RichText::new("❌ DECLINE") + RichText::new("❌ ОТКЛОНИТЬ") .size(14.0) .strong() .color(Color32::WHITE), @@ -4375,7 +4361,7 @@ impl ZeroSendApp { let mut is_open = self.show_grep_modal; let mut trigger_open_file = None; - egui::Window::new("🔎 Search Text / Code in Remote Share") + egui::Window::new("🔎 Поиск текста и кода в общей папке (Grep)") .open(&mut is_open) .collapsible(false) .resizable(true) @@ -4383,10 +4369,10 @@ impl ZeroSendApp { .anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO) .show(ctx, |ui| { ui.horizontal(|ui| { - ui.label(RichText::new("Query:").strong().color(theme.text_primary)); + ui.label(RichText::new("Запрос:").strong().color(theme.text_primary)); let edit = ui.add( egui::TextEdit::singleline(&mut self.grep_query) - .hint_text("e.g. fn main, TODO, password...") + .hint_text("например: fn main, TODO, пароль...") .desired_width(260.0), ); @@ -4396,7 +4382,7 @@ impl ZeroSendApp { } let search_btn = Button::new( - RichText::new(if self.is_grepping { "⏳ Searching..." } else { "🔍 Search" }) + RichText::new(if self.is_grepping { "⏳ Поиск..." } else { "🔍 Искать" }) .strong() .color(Color32::WHITE), ) @@ -4411,7 +4397,7 @@ impl ZeroSendApp { if let (Some(peer_id), Some(folder_id)) = (&self.selected_peer_id_for_share, &self.selected_folder_id_for_share) { if let Some(peer) = self.peers.iter().find(|p| &p.peer_id == peer_id).cloned() { self.is_grepping = true; - self.grep_status_msg = Some("Searching across remote files...".to_string()); + self.grep_status_msg = Some("Поиск по удаленным файлам...".to_string()); let client = self.client.clone(); let peer_url = peer.transfer_url(); let f_id = folder_id.clone(); @@ -4423,10 +4409,10 @@ impl ZeroSendApp { tokio::spawn(async move { match client.grep_shared_folder(&peer_url, &f_id, &q, pin.as_deref(), Some(&sub), false).await { Ok(resp) => { - let _ = log_tx.send(format!("🔎 Grep found {} match(es) in {} searched files", resp.matches.len(), resp.total_files_searched)); + let _ = log_tx.send(format!("🔎 Grep: найдено {} совпадений в {} проверенных файлах", resp.matches.len(), resp.total_files_searched)); } Err(e) => { - let _ = log_tx.send(format!("❌ Grep failed: {}", e)); + let _ = log_tx.send(format!("❌ Ошибка Grep: {}", e)); } } }); @@ -4443,12 +4429,12 @@ impl ZeroSendApp { if self.is_grepping { ui.vertical_centered(|ui| { ui.spinner(); - ui.label("Searching remote code & text files..."); + ui.label("Поиск по удаленным файлам кода и текста..."); }); } else if self.grep_results.is_empty() { - ui.label(RichText::new("Enter a search term above and press Search to find text or code lines.").size(12.0).color(theme.text_muted)); + ui.label(RichText::new("Введите поисковый запрос выше и нажмите «Искать» для поиска строк в коде.").size(12.0).color(theme.text_muted)); } else { - ui.label(RichText::new(format!("Found {} matches:", self.grep_results.len())).strong().color(theme.text_accent)); + ui.label(RichText::new(format!("Найдено совпадений: {}", self.grep_results.len())).strong().color(theme.text_accent)); ui.add_space(4.0); ScrollArea::vertical().max_height(280.0).show(ui, |ui| { @@ -4467,7 +4453,7 @@ impl ZeroSendApp { ui.label(RichText::new(&m.line_content).monospace().color(theme.text_primary)); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button("Open in Vim").clicked() { + if ui.button("Открыть в Vim").clicked() { trigger_open_file = Some((m.file_path.clone(), m.line_number)); } }); @@ -4509,7 +4495,7 @@ impl ZeroSendApp { pub fn trigger_update_check(&mut self) { self.is_checking_update = true; - self.update_check_status_msg = Some("Connecting to Gitea and checking for updates...".to_string()); + self.update_check_status_msg = Some("Подключение к Gitea и проверка обновлений...".to_string()); let tx = self.update_tx.clone(); let api_url = self.config.gitea_api_url.clone(); tokio::spawn(async move { diff --git a/src/ui/vim_viewer.rs b/src/ui/vim_viewer.rs index 62639f2..1c9a3f2 100644 --- a/src/ui/vim_viewer.rs +++ b/src/ui/vim_viewer.rs @@ -100,9 +100,9 @@ impl VimViewerModal { self.is_command_mode = false; self.is_dirty = false; self.status_msg = Some(format!( - "Loaded {} lines • {}", + "Загружено {} строк • {}", self.lines.len(), - if self.is_writable { "Writable [i: edit, :w: save]" } else { "Read-Only" } + if self.is_writable { "Доступна запись [i: правка, :w: сохранить]" } else { "Только чтение" } )); self.last_g_press = None; } @@ -117,7 +117,7 @@ impl VimViewerModal { pub fn trigger_save(&mut self) { if !self.is_writable { - self.status_msg = Some("❌ Cannot save: folder is Read-Only".to_string()); + self.status_msg = Some("❌ Невозможно сохранить: папка только для чтения".to_string()); return; } @@ -129,10 +129,10 @@ impl VimViewerModal { self.buffer_content.clone(), self.pin.clone(), )); - self.status_msg = Some("⏳ Saving changes to remote peer...".to_string()); + self.status_msg = Some("⏳ Сохранение изменений на удаленном узле...".to_string()); self.is_dirty = false; } else { - self.status_msg = Some("❌ No remote endpoint associated with this file".to_string()); + self.status_msg = Some("❌ Нет удаленного подключения для этого файла".to_string()); } } @@ -361,14 +361,14 @@ impl VimViewerModal { } if self.is_writable { - ui.label(RichText::new("• [Writable]").color(Color32::from_rgb(34, 197, 94)).size(11.0)); + ui.label(RichText::new("• [Доступна запись]").color(Color32::from_rgb(34, 197, 94)).size(11.0)); } else { - ui.label(RichText::new("• [Read-Only]").color(Color32::from_rgb(148, 163, 184)).size(11.0)); + ui.label(RichText::new("• [Только чтение]").color(Color32::from_rgb(148, 163, 184)).size(11.0)); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui - .button(RichText::new("✕ Close (:q)").color(Color32::from_rgb(203, 213, 225))) + .button(RichText::new("✕ Закрыть (:q)").color(Color32::from_rgb(203, 213, 225))) .clicked() { self.close(); @@ -376,7 +376,7 @@ impl VimViewerModal { if self.is_writable { let save_btn = egui::Button::new( - RichText::new("💾 Save (:w)") + RichText::new("💾 Сохранить (:w)") .strong() .color(Color32::WHITE), ) @@ -387,7 +387,7 @@ impl VimViewerModal { self.trigger_save(); } - let edit_label = if self.is_insert_mode { "👁 View Mode (Esc)" } else { "✏️ Edit (i)" }; + let edit_label = if self.is_insert_mode { "👁 Просмотр (Esc)" } else { "✏️ Редактировать (i)" }; if ui.button(RichText::new(edit_label).color(Color32::from_rgb(226, 232, 240))).clicked() { self.is_insert_mode = !self.is_insert_mode; if !self.is_insert_mode { @@ -398,7 +398,7 @@ impl VimViewerModal { if self.file_name.to_lowercase().ends_with(".md") { if ui - .selectable_label(self.is_markdown_view, "👁 Markdown Preview") + .selectable_label(self.is_markdown_view, "👁 Предпросмотр Markdown") .clicked() { self.is_markdown_view = !self.is_markdown_view; @@ -406,7 +406,7 @@ impl VimViewerModal { } ui.label( - RichText::new(format!("{} lines", self.lines.len())) + RichText::new(format!("{} строк", self.lines.len())) .size(11.0) .color(Color32::from_rgb(148, 163, 184)), ); @@ -539,13 +539,13 @@ impl VimViewerModal { ui.horizontal(|ui| { // Mode Pill let (mode_text, mode_bg, mode_fg) = if self.is_insert_mode { - ("INSERT", Color32::from_rgb(34, 197, 94), Color32::BLACK) + ("ВСТАВКА", Color32::from_rgb(34, 197, 94), Color32::BLACK) } else if self.is_searching { - ("SEARCH", Color32::from_rgb(217, 119, 6), Color32::WHITE) + ("ПОИСК", Color32::from_rgb(217, 119, 6), Color32::WHITE) } else if self.is_command_mode { - ("COMMAND", Color32::from_rgb(147, 51, 234), Color32::WHITE) + ("КОМАНДА", Color32::from_rgb(147, 51, 234), Color32::WHITE) } else { - ("NORMAL", Color32::from_rgb(37, 99, 235), Color32::WHITE) + ("ОБЫЧНЫЙ", Color32::from_rgb(37, 99, 235), Color32::WHITE) }; Frame::none() @@ -559,16 +559,16 @@ impl VimViewerModal { if self.is_searching { ui.label(RichText::new("/").monospace().color(Color32::from_rgb(245, 158, 11))); let edit = TextEdit::singleline(&mut self.search_query) - .hint_text("pattern (press Enter)...") + .hint_text("поиск (нажмите Enter)...") .font(egui::TextStyle::Monospace) .desired_width(200.0); ui.add(edit).request_focus(); } else if self.is_command_mode { ui.label(RichText::new(":").monospace().color(Color32::from_rgb(168, 85, 247))); let edit = TextEdit::singleline(&mut self.command_input) - .hint_text("w (save) / q (quit) / line...") + .hint_text("w (сохранить) / q (выход) / номер...") .font(egui::TextStyle::Monospace) - .desired_width(200.0); + .desired_width(220.0); ui.add(edit).request_focus(); } else { if let Some(msg) = &self.status_msg { @@ -578,7 +578,7 @@ impl VimViewerModal { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.label( - RichText::new("i: edit • Esc: normal • :w: save • :q: exit") + RichText::new("i: правка • Esc: обычный • :w: сохранить • :q: выход") .size(10.0) .color(Color32::from_rgb(100, 116, 139)), ); @@ -586,7 +586,7 @@ impl VimViewerModal { ui.separator(); ui.label( - RichText::new(format!("Ln {}, Col 1", self.cursor_line)) + RichText::new(format!("Стр {}, Кол 1", self.cursor_line)) .size(11.0) .monospace() .color(Color32::from_rgb(148, 163, 184)), diff --git a/src/updater.rs b/src/updater.rs index 740b855..bdf3524 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -335,17 +335,27 @@ pub async fn perform_update_download( info!("Update successfully installed to {:?}", current_exe); let _ = progress_tx.send(UpdateProgress::Completed); + // Spawn background PowerShell cleanup script to delete old binary once this process terminates + let cleanup_script = format!( + "Start-Sleep -Seconds 1; Remove-Item -Force -ErrorAction SilentlyContinue '{}'", + old_backup_path.to_string_lossy() + ); + let _ = std::process::Command::new("powershell") + .args(["-NoProfile", "-WindowStyle", "Hidden", "-Command", &cleanup_script]) + .spawn(); + // Spawn updated application and exit let _ = std::process::Command::new(¤t_exe).spawn(); - std::thread::sleep(Duration::from_millis(500)); + std::thread::sleep(Duration::from_millis(400)); std::process::exit(0); } else { - // Fallback: Use PowerShell deferred replacement + // Fallback: Use PowerShell deferred replacement and cleanup info!("Direct rename locked; triggering background PowerShell replacement..."); let script = format!( - "Start-Sleep -Milliseconds 600; Move-Item -Force '{temp}' '{target}'; Start-Process '{target}'", + "Start-Sleep -Milliseconds 600; Move-Item -Force '{temp}' '{target}'; Start-Sleep -Seconds 1; Remove-Item -Force -ErrorAction SilentlyContinue '{old}'; Start-Process '{target}'", temp = temp_download_path.to_string_lossy(), - target = current_exe.to_string_lossy() + target = current_exe.to_string_lossy(), + old = old_backup_path.to_string_lossy() ); let _ = std::process::Command::new("powershell") @@ -353,7 +363,7 @@ pub async fn perform_update_download( .spawn(); let _ = progress_tx.send(UpdateProgress::Completed); - std::thread::sleep(Duration::from_millis(500)); + std::thread::sleep(Duration::from_millis(400)); std::process::exit(0); } }