# ZTorrent — TODO & Контекст улучшений > Цель: довести ztorrent до уровня qBittorrent по функциональности и скорости. > Ведётся автоматически — отражает актуальное состояние реализации. --- ## Архитектура проекта ``` internal/ torrent/ engine.go — основной движок: загрузка торрента, воркеры, координация peerwire.go — BitTorrent wire protocol: handshake, download piece, messages piecescheduler.go — выбор кусков (rarest-first), endgame mode debug.go — debugf helper tracker/ tracker.go — HTTP + UDP announce torrentfile/ torrentfile.go — парсинг .torrent (bencode) app/ controller.go — тонкая обёртка над Engine для UI ui/ ui.go — Bubble Tea TUI (welcome, loading, dashboard) styles.go — цветовая палитра ``` --- ## P0 — Критичные ✅ ГОТОВО - [x] **Resume download** — `createOrOpenPartFile()` без O_TRUNC + SHA-1 верификация - [x] **Keep-alive** — `keepaliveLoop()` горутина, 90с, `msgCancel=8` - [x] **Endgame mode** — `pieceDone` broadcast + `SendCancel()` при дублировании ## P1 — Производительность ✅ ГОТОВО - [x] **Adaptive pipeline** — depth 4–64, пересчёт каждые 4 блока по RTT+BW - [x] **IPv6 compact peers** — `parsePeers6()` в tracker.go - [x] **Rate limiting** — `rateLimitedConn`, `SetDownloadLimit`/`SetUploadLimit` - [x] **Bitmap resume data** — `.part.bitmap` JSON, `asyncSaveBitmap()` в heartbeat ## Баги ✅ ПОФИКШЕНО - [x] **`announce` пустой** — BEP 12 fallback: берём первый URL из `announce-list` > `torrentfile.go` → `primaryAnnounce` fallback в `Open()` - [x] **`peerBackoff` O(n)** → O(1) через `peerIdx map[string]int` > Добавлен `Engine.peerIdx`, `peerByKey()` helper > Все peer-методы: `setPeerState`, `setPeerError`, `peerBackoff`, `recordPieceComplete` - [x] **Скорость без EWMA** → сглаживание α=0.3 в `updateSpeedLocked()` > `downloadSpeed = 0.3*sample + 0.7*prev` - [x] **Reannounce блокирует heartbeat** → запускается в отдельной горутине > `heartbeatTicker.C` → `go func() { refreshPeersFromTrackers() }()` > Таймаут уменьшен с 20с до 15с ## Фича ✅ РЕАЛИЗОВАНО - [x] **Incoming connections** — TCP listener на :6881 > `listenIncoming()` + `handleIncoming()` в engine.go > Handshake верификация (info_hash), регистрация в `peerIdx` > Входящие пиры подхватываются `startWorkers` на следующем heartbeat тике --- ## P2 — Долгосрочные (не начато) - [x] **DHT (BEP 5)** — Kademlia DHT для поиска пиров без трекера > Новый пакет `internal/dht/` — реализовано! - [x] **Магнет-ссылки (BEP 9)** — `magnet:?xt=...`, ut_metadata extension > Новый пакет `internal/magnet/` — средняя сложность - [x] **Seeding / upload (tit-for-tat)** — раздача после завершения > `peerwire.go` → обработка `msgRequest`, отправка `msgPiece` - [x] **PEX (BEP 11)** — Peer Exchange между пирами > Extension handshake + `ut_pex` message --- ## Технические решения ### peerIdx — O(1) peer lookup ``` Engine.peers []PeerStatus // слайс со всеми пирами Engine.peerIdx map[string]int // "host:port" → индекс в слайсе buildPeerIdx(peers) // строится при LoadTorrent и reannounce peerByKey(key) *PeerStatus // O(1) под e.mu ``` ### EWMA сглаживание скорости ``` updateSpeedLocked(): sample = delta_bytes / elapsed_sec speed = 0.3*sample + 0.7*prev_speed // α=0.3 ``` ### Incoming connections flow ``` runDownload() └── go listenIncoming(ctx, tf) └── net.Listen(":6881") └── for { conn = Accept() → go handleIncoming(conn) } ├── ReadFull → verify wireProtocol + infoHash ├── Write reply handshake └── register peer in peerIdx → подхватывается startWorkers на следующем heartbeat ``` ### Resume flow ``` createOrOpenPartFile(): ├── если .part существует + .bitmap → verifyPieces() (быстро) ├── если .part существует, нет bitmap → verifyAllPieces() (SHA-1 scan) └── если .part нет → создаём новый, Truncate(size) heartbeat: asyncSaveBitmap() → .part.bitmap завершение: os.Remove(.part) + os.Remove(.part.bitmap) ```