Release v2.5.2: fix transfers speed metric overflow, auto-cleanup old binary on update, full Russian localization

This commit is contained in:
RarDog
2026-08-27 13:18:55 +03:00
parent 9f9044646f
commit 35e590c718
6 changed files with 199 additions and 195 deletions
Generated
+1 -1
View File
@@ -6372,7 +6372,7 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zerosend"
version = "2.5.1"
version = "2.5.2"
dependencies = [
"arboard",
"axum",
+1 -1
View File
@@ -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"]
+8
View File
@@ -32,6 +32,14 @@ fn get_default_device_name() -> String {
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 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();
+153 -167
View File
@@ -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 {
+21 -21
View File
@@ -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)),
+15 -5
View File
@@ -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(&current_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);
}
}