mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 08:48:32 +02:00
refactor: improve error handling and response formatting in API
This commit is contained in:
68
internal/homepage/integrations/qbittorrent/client.go
Normal file
68
internal/homepage/integrations/qbittorrent/client.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/homepage/widgets"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
URL string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (c *Client) Initialize(ctx context.Context, url string, cfg map[string]any) error {
|
||||
c.URL = url
|
||||
c.Username = cfg["username"].(string)
|
||||
c.Password = cfg["password"].(string)
|
||||
|
||||
_, err := c.Version(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(ctx context.Context, method, endpoint string, query url.Values, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, c.URL+endpoint+query.Encode(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Username != "" && c.Password != "" {
|
||||
req.SetBasicAuth(c.Username, c.Password)
|
||||
}
|
||||
|
||||
resp, err := widgets.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, gperr.Errorf("%w: %d %s", widgets.ErrHTTPStatus, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func jsonRequest[T any](ctx context.Context, client *Client, endpoint string, query url.Values) (result T, err error) {
|
||||
resp, err := client.doRequest(ctx, http.MethodGet, endpoint, query, nil)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
102
internal/homepage/integrations/qbittorrent/logs.go
Normal file
102
internal/homepage/integrations/qbittorrent/logs.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const endpointLogs = "/api/v2/log/main"
|
||||
|
||||
type LogEntry struct {
|
||||
ID int `json:"id"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Type int `json:"type"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
const (
|
||||
LogSeverityNormal = 1 << iota
|
||||
LogSeverityInfo
|
||||
LogSeverityWarning
|
||||
LogSeverityCritical
|
||||
)
|
||||
|
||||
func (l *LogEntry) Time() time.Time {
|
||||
return time.Unix(int64(l.Timestamp), 0)
|
||||
}
|
||||
|
||||
func (l *LogEntry) Level() string {
|
||||
switch l.Type {
|
||||
case LogSeverityNormal:
|
||||
return "Normal"
|
||||
case LogSeverityInfo:
|
||||
return "Info"
|
||||
case LogSeverityWarning:
|
||||
return "Warning"
|
||||
case LogSeverityCritical:
|
||||
return "Critical"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogEntry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"id": l.ID,
|
||||
"timestamp": l.Timestamp,
|
||||
"level": l.Level(),
|
||||
"message": l.Message,
|
||||
})
|
||||
}
|
||||
|
||||
// params:
|
||||
//
|
||||
// normal: bool
|
||||
// info: bool
|
||||
// warning: bool
|
||||
// critical: bool
|
||||
// last_known_id: int
|
||||
func (c *Client) GetLogs(ctx context.Context, lastKnownID int) ([]*LogEntry, error) {
|
||||
return jsonRequest[[]*LogEntry](ctx, c, endpointLogs, url.Values{
|
||||
"last_known_id": {strconv.Itoa(lastKnownID)},
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) WatchLogs(ctx context.Context) (<-chan *LogEntry, <-chan error) {
|
||||
ch := make(chan *LogEntry)
|
||||
errCh := make(chan error)
|
||||
|
||||
lastKnownID := -1
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
defer close(errCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
logs, err := c.GetLogs(ctx, lastKnownID)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
|
||||
if len(logs) == 0 {
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, log := range logs {
|
||||
ch <- log
|
||||
}
|
||||
lastKnownID = logs[len(logs)-1].ID
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return ch, errCh
|
||||
}
|
||||
32
internal/homepage/integrations/qbittorrent/transfer_info.go
Normal file
32
internal/homepage/integrations/qbittorrent/transfer_info.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/homepage/widgets"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
)
|
||||
|
||||
const endpointTransferInfo = "/api/v2/transfer/info"
|
||||
|
||||
type TransferInfo struct {
|
||||
ConnectionStatus string `json:"connection_status"`
|
||||
SessionDownloads uint64 `json:"dl_info_data"`
|
||||
SessionUploads uint64 `json:"up_info_data"`
|
||||
DownloadSpeed uint64 `json:"dl_info_speed"`
|
||||
UploadSpeed uint64 `json:"up_info_speed"`
|
||||
}
|
||||
|
||||
func (c *Client) Data(ctx context.Context) ([]widgets.NameValue, error) {
|
||||
info, err := jsonRequest[TransferInfo](ctx, c, endpointTransferInfo, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []widgets.NameValue{
|
||||
{Name: "Status", Value: info.ConnectionStatus},
|
||||
{Name: "Download", Value: strutils.FormatByteSize(info.SessionDownloads)},
|
||||
{Name: "Upload", Value: strutils.FormatByteSize(info.SessionUploads)},
|
||||
{Name: "Download Speed", Value: strutils.FormatByteSize(info.DownloadSpeed) + "/s"},
|
||||
{Name: "Upload Speed", Value: strutils.FormatByteSize(info.UploadSpeed) + "/s"},
|
||||
}, nil
|
||||
}
|
||||
21
internal/homepage/integrations/qbittorrent/version.go
Normal file
21
internal/homepage/integrations/qbittorrent/version.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
func (c *Client) Version(ctx context.Context) (string, error) {
|
||||
resp, err := c.doRequest(ctx, "GET", "/api/v2/app/version", nil, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
Reference in New Issue
Block a user