211 lines
4.9 KiB
Go
211 lines
4.9 KiB
Go
package syncthing
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Client talks to the Syncthing REST API.
|
|
type Client struct {
|
|
baseURL string
|
|
apiKey string
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// NewClient creates a Syncthing API client.
|
|
func NewClient(baseURL, apiKey string) *Client {
|
|
return &Client{
|
|
baseURL: baseURL,
|
|
apiKey: apiKey,
|
|
httpClient: &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// SetAPIKey updates the API key.
|
|
func (c *Client) SetAPIKey(key string) {
|
|
c.apiKey = key
|
|
}
|
|
|
|
// SetBaseURL updates the base URL.
|
|
func (c *Client) SetBaseURL(url string) {
|
|
c.baseURL = url
|
|
}
|
|
|
|
func (c *Client) get(path string, noAuth bool) ([]byte, error) {
|
|
req, err := http.NewRequest("GET", c.baseURL+path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !noAuth && c.apiKey != "" {
|
|
req.Header.Set("X-API-Key", c.apiKey)
|
|
}
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
return io.ReadAll(resp.Body)
|
|
}
|
|
|
|
func (c *Client) post(path string) error {
|
|
req, err := http.NewRequest("POST", c.baseURL+path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c.apiKey != "" {
|
|
req.Header.Set("X-API-Key", c.apiKey)
|
|
}
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Health checks if Syncthing is reachable (no auth required).
|
|
func (c *Client) Health() (*HealthStatus, error) {
|
|
data, err := c.get("/rest/noauth/health", true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s HealthStatus
|
|
return &s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// SystemStatus returns system status info.
|
|
func (c *Client) SystemStatus() (*SystemStatus, error) {
|
|
data, err := c.get("/rest/system/status", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s SystemStatus
|
|
return &s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// SystemVersion returns version info.
|
|
func (c *Client) SystemVersion() (*SystemVersion, error) {
|
|
data, err := c.get("/rest/system/version", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s SystemVersion
|
|
return &s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// SystemConnections returns all device connections.
|
|
func (c *Client) SystemConnections() (*Connections, error) {
|
|
data, err := c.get("/rest/system/connections", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s Connections
|
|
return &s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// Config returns the full Syncthing config (folders + devices).
|
|
func (c *Client) Config() (*FullConfig, error) {
|
|
data, err := c.get("/rest/config", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s FullConfig
|
|
return &s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// FolderStatus returns the sync status for a folder.
|
|
func (c *Client) FolderStatus(folderID string) (*FolderStatus, error) {
|
|
data, err := c.get("/rest/db/status?folder="+folderID, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s FolderStatus
|
|
return &s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// Events long-polls for new events since the given ID.
|
|
func (c *Client) Events(since int, timeout int) ([]Event, error) {
|
|
path := fmt.Sprintf("/rest/events?since=%d&timeout=%d", since, timeout)
|
|
// Use a longer HTTP timeout for long-polling
|
|
client := &http.Client{
|
|
Timeout: time.Duration(timeout+10) * time.Second,
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
},
|
|
}
|
|
req, err := http.NewRequest("GET", c.baseURL+path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if c.apiKey != "" {
|
|
req.Header.Set("X-API-Key", c.apiKey)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
var events []Event
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return events, json.Unmarshal(data, &events)
|
|
}
|
|
|
|
// PendingDevices returns new device requests.
|
|
func (c *Client) PendingDevices() (map[string]PendingDevice, error) {
|
|
data, err := c.get("/rest/cluster/pending/devices", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var s map[string]PendingDevice
|
|
return s, json.Unmarshal(data, &s)
|
|
}
|
|
|
|
// PauseAll pauses all devices.
|
|
func (c *Client) PauseAll() error {
|
|
return c.post("/rest/system/pause")
|
|
}
|
|
|
|
// ResumeAll resumes all devices.
|
|
func (c *Client) ResumeAll() error {
|
|
return c.post("/rest/system/resume")
|
|
}
|
|
|
|
// RescanAll triggers a rescan of all folders.
|
|
func (c *Client) RescanAll() error {
|
|
return c.post("/rest/db/scan")
|
|
}
|
|
|
|
// Restart restarts Syncthing.
|
|
func (c *Client) Restart() error {
|
|
return c.post("/rest/system/restart")
|
|
}
|
|
|
|
// Shutdown stops Syncthing.
|
|
func (c *Client) Shutdown() error {
|
|
return c.post("/rest/system/shutdown")
|
|
}
|