feat: implémentation complete v0.1.0 — daemon de sync clipboard bidirectionnel
CI / Lint (push) Failing after 14m46s
CI / Test (push) Failing after 4m23s
CI / Build (darwin/amd64) (push) Skipped
CI / Build (linux/amd64) (push) Skipped
CI / Build (windows/amd64) (push) Skipped
CI / Build (darwin/arm64) (push) Skipped
CI / Build (linux/arm64) (push) Skipped
CI / Build (windows/arm64) (push) Skipped
CI / Release (push) Skipped
CI / Lint (push) Failing after 14m46s
CI / Test (push) Failing after 4m23s
CI / Build (darwin/amd64) (push) Skipped
CI / Build (linux/amd64) (push) Skipped
CI / Build (windows/amd64) (push) Skipped
CI / Build (darwin/arm64) (push) Skipped
CI / Build (linux/arm64) (push) Skipped
CI / Build (windows/arm64) (push) Skipped
CI / Release (push) Skipped
This commit is contained in:
+109
-51
@@ -1,72 +1,130 @@
|
||||
# clip-sync — Architecture
|
||||
# ARCHITECTURE.md — clip-sync
|
||||
|
||||
## Principe
|
||||
## Vue d'ensemble
|
||||
|
||||
Un daemon Go (~150 lignes, zéro dépendance hors stdlib) qui synchronise le presse-papier entre machines sur un LAN. Chaque machine tourne le même binaire, écoute sur `:9137`, et pousse les nouveaux clips vers toutes les autres.
|
||||
clip-sync est un daemon de synchronisation bidirectionnelle de presse-papiers
|
||||
en mesh pair-à-pair sur réseau local. Chaque nœud est à la fois client et serveur.
|
||||
|
||||
## Flux de données
|
||||
|
||||
```
|
||||
┌──────────────┐ POST /clip ┌──────────────┐
|
||||
│ deepin │◄──────────────►│ vivobook │
|
||||
│ :9137 │ {text,ts} │ :9137 │
|
||||
│ │ │ │
|
||||
│ poll 500ms │ │ poll 500ms │
|
||||
│ xclip -o │ │ Get-Clip │
|
||||
└──────────────┘ └──────────────┘
|
||||
┌────────────────────────────────────┐
|
||||
│ clip-sync (nœud) │
|
||||
│ │
|
||||
Presse-papiers ──►│ Poller (500ms) │
|
||||
(Ctrl+C) │ │ lecture (OS clipboard) │
|
||||
│ ▼ │
|
||||
│ Comparaison (vs dernier texte) │
|
||||
│ │ si nouveau texte │
|
||||
│ ▼ │
|
||||
│ Filtre anti-boucle │
|
||||
│ │ MarkWritten(hash, ts) │
|
||||
│ ▼ │
|
||||
│ Broadcaster │
|
||||
│ │ POST /clip → chaque pair │
|
||||
│ │ goroutine par pair │
|
||||
└────┼───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ Pair distant :9137 │
|
||||
│ POST /clip {json} │
|
||||
│ ↓ │
|
||||
│ Filtre anti-boucle │
|
||||
│ ↓ (si accepté) │
|
||||
│ Write clipboard │
|
||||
│ ↓ (Ctrl+V possible)│
|
||||
└───────────────────────┘
|
||||
```
|
||||
|
||||
## Boucles (tout dans `main.go`)
|
||||
|
||||
### Boucle 1 — Watch local
|
||||
Toutes les 500ms, lit le clipboard local (`xclip -o` / `powershell Get-Clipboard`), le hashe (SHA-256). Si le hash diffère du dernier connu → broadcast HTTP POST à tous les pairs.
|
||||
|
||||
### Boucle 2 — Receive
|
||||
Serveur HTTP `POST /clip`. Reçoit `{text, ts, host}`. Si le host est soi-même ou si le timestamp est ≤ au dernier reçu de ce host → ignore. Sinon, écrit dans le clipboard local ET met à jour `lastHash` pour que la boucle 1 ne rebroadcast pas.
|
||||
|
||||
### Anti-boucle d'écho
|
||||
Après un `clipboard.Write()` déclenché par un clip reçu, `lastHash` est mis à jour avec le hash du texte reçu. Le poller local le voit comme "déjà connu" → pas de rebroadcast.
|
||||
|
||||
## Composants
|
||||
|
||||
| Fichier | Rôle | Lignes |
|
||||
|---------|------|--------|
|
||||
| `main.go` | Serveur HTTP + watch loop + dispatch | ~120 |
|
||||
| `peers.toml` | Configuration des pairs | ~10 |
|
||||
| `install.sh` | Installation one-liner cross-OS | ~40 |
|
||||
### 1. Clipboard (`internal/clipboard/`)
|
||||
|
||||
## Plateformes
|
||||
Interface unifiée `Read() / Write()` avec implémentations par plateforme :
|
||||
|
||||
| OS | Clipboard read | Clipboard write |
|
||||
|----|---------------|-----------------|
|
||||
| Linux (X11) | `xclip -o -selection clipboard` | `xclip -selection clipboard` |
|
||||
| Linux (Wayland) | `wl-paste` | `wl-copy` |
|
||||
| macOS | `pbpaste` | `pbcopy` |
|
||||
| Windows | `powershell -Command Get-Clipboard` | `powershell -Command Set-Clipboard -Value ...` |
|
||||
- **Linux** : auto-détection X11/Wayland via `$XDG_SESSION_TYPE`
|
||||
- X11 → `xclip -selection clipboard -o` / `xclip -selection clipboard`
|
||||
- Wayland → `wl-paste` / `wl-copy`
|
||||
- **Windows** : Win32 API via `syscall` (user32.dll, kernel32.dll), zéro CGo
|
||||
- **macOS** : `pbpaste` / `pbcopy`
|
||||
|
||||
Détection automatique au démarrage via `runtime.GOOS` + test de présence des binaires.
|
||||
### 2. Configuration (`internal/config/`)
|
||||
|
||||
## Ce qui est volontairement omis
|
||||
- Fichier TOML : `~/.config/clip-sync/peers.toml`
|
||||
- Parsing : `github.com/BurntSushi/toml` (seule dépendance externe — TOML absent de stdlib)
|
||||
- Override par variables d'environnement : `CLIP_SYNC_PORT`, `CLIP_SYNC_POLL_MS`
|
||||
|
||||
- **Chiffrement** — LAN de confiance. Si besoin → TLS entre pairs (v2).
|
||||
- **Découverte automatique (mDNS)** — config statique = 5 lignes de TOML. Discovery = 200 lignes. v2.
|
||||
- **Historique / ring buffer** — ce n'est pas un gestionnaire de presse-papier, c'est du live sync.
|
||||
- **UI / tray icon** — daemon headless, `systemd --user` ou `nssm` pour Windows.
|
||||
- **Auth token** — HTTP en clair sur LAN. Port 9137 non exposé à l'extérieur.
|
||||
### 3. Anti-boucle (`internal/dedup/`)
|
||||
|
||||
Deux garde-fous empêchent les boucles de synchronisation :
|
||||
|
||||
| Garde-fou | Mécanisme | Scénario bloqué |
|
||||
|-----------|-----------|-----------------|
|
||||
| **Hash** | Cache LRU (20 derniers) des SHA-256 locaux | Notre texte nous revient via un pair |
|
||||
| **Origine** | Comparaison `origin == hostname` | Rebond via 3e pair |
|
||||
| **Timestamp** | `ts <= lastWriteTs` | Contournement multi-pair |
|
||||
|
||||
Payload JSON :
|
||||
```json
|
||||
{
|
||||
"text": "contenu du presse-papiers",
|
||||
"ts": 1690000000000000000,
|
||||
"origin": "nom-de-machine"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Communication pair-à-pair (`internal/peer/`, `internal/server/`)
|
||||
|
||||
- Protocole : HTTP POST `/clip` (JSON), réponse `204 No Content`
|
||||
- Timeout connexion : 3s (dial), 5s (request)
|
||||
- Chaque broadcast aux pairs est lancé dans sa propre goroutine (non-bloquant)
|
||||
- Échec d'un pair = log + continue (pas de blocage des autres)
|
||||
|
||||
### 5. Daemon (`internal/daemon/`)
|
||||
|
||||
Boucle principale :
|
||||
1. Démarre le serveur HTTP dans une goroutine
|
||||
2. Ticker 500ms : lit le presse-papiers, compare, broadcast si changé
|
||||
3. Signal SIGINT/SIGTERM → arrêt gracieux
|
||||
|
||||
## Dépendances
|
||||
|
||||
Zéro. `net/http`, `crypto/sha256`, `encoding/json`, `os/exec`, `runtime`. Stdlib Go pure.
|
||||
| Dépendance | Version | Justification |
|
||||
|-----------|---------|---------------|
|
||||
| `github.com/BurntSushi/toml` | v1.3.2 | Parsing TOML (stdlib Go n'inclut pas TOML) |
|
||||
|
||||
## Build
|
||||
**Total : 1 dépendance externe.** Tout le reste est stdlib Go.
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
GOOS=linux GOARCH=amd64 go build -o clip-sync .
|
||||
## Décisions architecturales
|
||||
|
||||
# Windows
|
||||
GOOS=windows GOARCH=amd64 go build -o clip-sync.exe .
|
||||
### Pourquoi HTTP et pas TCP raw / gRPC ?
|
||||
|
||||
# macOS
|
||||
GOOS=darwin GOARCH=amd64 go build -o clip-sync .
|
||||
```
|
||||
- Débogable avec `curl`
|
||||
- `net/http` est dans la stdlib, zéro dépendance
|
||||
- Le surcoût HTTP est négligeable pour du texte de presse-papiers
|
||||
- Facile à étendre (TLS, auth headers) dans les versions futures
|
||||
|
||||
Un binaire statique par OS. Pas de Docker, pas de runtime.
|
||||
### Pourquoi TOML et pas YAML/JSON ?
|
||||
|
||||
- TOML est le format standard de configuration en Go
|
||||
- Plus lisible que JSON pour des humains
|
||||
- Pas de problème de whitespace-significant comme YAML
|
||||
|
||||
### Pourquoi pas mDNS en v1.0 ?
|
||||
|
||||
- Philosophie du projet : configuration explicite, zéro magie
|
||||
- mDNS ajoute une complexité significative (multicast, découverte, timeouts)
|
||||
- Prévu pour la v1.0 dans la roadmap
|
||||
|
||||
### Pourquoi du polling (500ms) et pas des events OS ?
|
||||
|
||||
- Les APIs de notification de presse-papiers sont non-portables (Win32 `AddClipboardFormatListener`, X11 `XFixes`, etc.)
|
||||
- Le polling 500ms est un compromis acceptable entre réactivité et CPU
|
||||
- Un seul `Read()` par tick, coût négligeable
|
||||
|
||||
### Pourquoi un cache LRU de 20 hash et pas plus ?
|
||||
|
||||
- 20 entrées couvrent ~10 secondes de copies rapides à 500ms/poll
|
||||
- Au-delà, le risque de collision est négligeable (SHA-256)
|
||||
- Garde la mémoire constante et prévisible
|
||||
|
||||
Reference in New Issue
Block a user