126 lines
5.1 KiB
Markdown
126 lines
5.1 KiB
Markdown
# 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)
|
||
```
|