Это история моей первой программы, которую не стыдно показать людям. Задача была бытовая: чтобы фото и видео между компьютерами семьи ходили сами — без облаков, аккаунтов и флешек. Получилась «Общая папка» и робот Папыч, который доставляет файлы. Расскажу, как это устроено внутри: прямые соединения через iroh, свой протокол поверх QUIC, правила синхронизации, обновления без сервера, анимированный персонаж на SVG и путь в Microsoft Store.

Задача

Брату нужно скинуть фото с дачи — пару гигабайт. Мессенджер сжимает и режет по размеру, облако забито или просит подписку, флешку везти лень. При этом оба компьютера включены и подключены к интернету. Хотелось простого: папка, в которую кладёшь файл, — а у родственника появляется «Получить / Отклонить», и файл идёт напрямую.

Требования, которые я себе поставил:

  • никакого своего сервера: его нужно содержать, и он видит файлы;

  • работает между разными городами и провайдерами, за NAT;

  • любой размер и докачка после обрыва;

  • ничего не удаляется у других без спроса;

  • понятно человеку, который не знает слов «синхронизация» и «P2P».

Как это выглядит

Обычная папка «Общая» в Проводнике. Положил файл — через три секунды тишины программа считает его готовым и предлагает семье. У получателя уведомление «Получить / Отклонить» (или автоприём, например, только фото). Файл идёт напрямую с компьютера на компьютер.

📎

Связь: iroh вместо своего сервера

Сердце программы — библиотека iroh от команды n0. Устройство адресуется не IP-адресом, а своим публичным ключом (Ed25519). iroh сам пробует пробить NAT и установить прямое QUIC-соединение; если не выходит, данные идут через публичные ретрансляторы n0 — зашифрованными (TLS 1.3 внутри QUIC), ретранслятор видит только ключи и IP-адреса.

Для передачи файлов в iroh есть модуль iroh-blobs, но на момент разработки он был помечен как не готовый для продакшена, поэтому протокол свой, поверх потоков QUIC:

  • управляющий поток с короткими сообщениями: приветствие (имя, версия, состав семьи), предложение файла, ответ, текстовые сообщения и подтверждения доставки;

  • для каждого файла — отдельный поток с данными, докачка с места обрыва и проверка целостности по BLAKE3.

Грабли совместимости: старая версия программы не знает новых видов сообщений и при неизвестном варианте рвёт связь. Поэтому новые сообщения отправляются только тем устройствам, чья версия из приветствия их поддерживает, а остальные получат их после обновления.

Семья и коды приглашения

Семья — это сколько угодно устройств, каждое знает ключи остальных. Новое подключается одноразовым кодом приглашения, который действует сутки: в нём ключ пригласившего и секрет. После знакомства новое устройство получает всё, что уже лежит в общей папке.

Изменения, версии и конфликты

Самое коварное оказалось не в передаче, а в правилах:

  • новый файл считается готовым после 3 секунд тишины, изменённый — после 20, иначе семья получит недописанный документ;

  • изменённый файл — это новая версия; если старое предложение ещё не принято, оно просто заменяется новым;

  • старая версия у получателя уходит в Корзину, а не исчезает;

  • если двое правили одновременно — сохраняются обе: «имя (версия от Брат).ext»;

  • удаление не передаётся совсем. Удалил у себя — у остальных файл остаётся. Для семейных фото это важнее, чем зеркальная синхронность.

Облако — только запасной путь

Если компьютеры не бывают включены одновременно, можно подключить своё облако: Яндекс Диск, любой WebDAV или папку OneDrive / Google Диска. Файлы шифруются (XChaCha20) ещё до загрузки, идут кусками по 32 МБ и удаляются из облака после получения. Облако необязательно и содержимого не видит.

Обновления без сервера

Раз сервера нет, раздавать обновления тоже неоткуда. Решение: установщик подписывается ключом разработчика (minisign), и устройства передают его друг другу так же, как файлы. Получатель проверяет подпись встроенным открытым ключом и ставит обновление тихо — когда ничего не передаётся и окно закрыто. Поставил новую версию на один компьютер — через пару минут она у всей семьи.

Окно: Tauri 2 и обычный HTML

«Двигатель» написан на Rust, интерфейс — Tauri 2 (системный WebView2) и обычные HTML, CSS и JavaScript без сборщика. Установщик весит около 7 МБ. Девять языков, строки общие для окна и «двигателя».

Шторка сверху экрана

Идея подсмотрена у телефонов: тащишь файл к верхнему краю экрана — выезжает чёрная шторка. Бросил на Папыча — получат все, на мини-робота родственника — только он.

📎

Технически это отдельное прозрачное окно поверх всех: прозрачные области пропускают клики к окнам под ним, видимая часть — нет. Положение курсора программа опрашивает раз в 33 мс и решает, пора ли шторке выглянуть. Чтобы отличить перетаскивание файла от перетаскивания окна, учитывается, зажата ли кнопка мыши, где её нажали и двигается ли окно под курсором.

Грабли: такое окно нельзя показывать и прятать напрямую через WinAPI (ShowWindow, SetWindowPos). Оконная библиотека Tauri пересчитывает стили из своих флагов и прячет окно, которое считает скрытым, — только через её собственные show и hide.

Папыч

Робот-папка нарисован кодом: SVG собирается в JavaScript, а каждая подвижная часть — глаза, веки, руки, «приседание», наклон — висит на пружине с жёсткостью и затуханием. Один общий цикл requestAnimationFrame двигает все пружины, а сценки («проглотить файл», «чихнуть», «уснуть в колпаке») — это async-функции, которые меняют цели пружин и ждут. Глаза следят за курсором по всему экрану: Rust передаёт в окно глобальную позицию курсора. Когда окно скрыто, анимация останавливается, чтобы не тратить процессор.

📎

После установки Папыч выпрыгивает из окна и с указкой проводит экскурсию — для этого поверх экрана на время открывается ещё одно прозрачное окно.

📎

Microsoft Store

Неподписанный установщик пугает людей предупреждением SmartScreen, а сертификат для подписи стоит денег. Выход — Microsoft Store: пакет MSIX Microsoft подписывает сам, а регистрация для частных разработчиков теперь бесплатная. Программа при этом одна и та же: она узнаёт, что запущена из пакета (Package.Current), и тогда не ставит обновления от семьи — их приносит Store, — включает автозапуск через StartupTask вместо реестра и показывает уведомления от имени пакета. Проверка в Store заняла меньше суток.

Итог

Буду рад вопросам и критике в комментариях — особенно про протокол и правила синхронизации. И идеям, чему ещё научить Папыча.