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
85 lines
2.0 KiB
Go
85 lines
2.0 KiB
Go
// Package peer sends clipboard content to remote clip-sync peers via HTTP POST.
|
|
package peer
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.dracodev.net/Projets/clip-sync/internal/config"
|
|
"git.dracodev.net/Projets/clip-sync/internal/dedup"
|
|
)
|
|
|
|
const clipPath = "/clip"
|
|
|
|
// Broadcaster sends clip payloads to all configured peers concurrently.
|
|
type Broadcaster struct {
|
|
peers []config.PeerConfig
|
|
origin string
|
|
client *http.Client
|
|
}
|
|
|
|
// NewBroadcaster creates a Broadcaster for the configured peer list.
|
|
func NewBroadcaster(peers []config.PeerConfig, origin string) *Broadcaster {
|
|
return &Broadcaster{
|
|
peers: peers,
|
|
origin: origin,
|
|
client: &http.Client{
|
|
Timeout: 5 * time.Second,
|
|
Transport: &http.Transport{
|
|
DialContext: (&net.Dialer{
|
|
Timeout: 3 * time.Second,
|
|
}).DialContext,
|
|
MaxIdleConnsPerHost: 2,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Broadcast sends the given text to all configured peers in parallel.
|
|
// Errors are logged but not returned — a single unreachable peer should not
|
|
// block other peers or the daemon loop.
|
|
func (b *Broadcaster) Broadcast(text string) {
|
|
payload := dedup.Payload{
|
|
Text: text,
|
|
Ts: time.Now().UnixNano(),
|
|
Origin: b.origin,
|
|
}
|
|
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
log.Printf("peer: marshal payload: %v", err)
|
|
return
|
|
}
|
|
|
|
for i := range b.peers {
|
|
go b.sendToPeer(b.peers[i], body)
|
|
}
|
|
}
|
|
|
|
func (b *Broadcaster) sendToPeer(p config.PeerConfig, body []byte) {
|
|
url := fmt.Sprintf("http://%s%s", p.Addr, clipPath)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
|
if err != nil {
|
|
log.Printf("peer [%s]: create request: %v", p.Name, err)
|
|
return
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := b.client.Do(req)
|
|
if err != nil {
|
|
log.Printf("peer [%s] %s: %v", p.Name, p.Addr, err)
|
|
return
|
|
}
|
|
resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusNoContent {
|
|
log.Printf("peer [%s] %s: unexpected status %d", p.Name, p.Addr, resp.StatusCode)
|
|
}
|
|
}
|