128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const DefaultUserAgent = "pan.baidu.com"
|
|
|
|
type Config struct {
|
|
ClientID string `json:"client_id"`
|
|
ClientSecret string `json:"client_secret"`
|
|
RedirectURI string `json:"redirect_uri"`
|
|
AccessToken string `json:"access_token,omitempty"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
|
Root string `json:"root"`
|
|
UserAgent string `json:"user_agent"`
|
|
UploadParts int `json:"upload_parts"`
|
|
PartSize int64 `json:"part_size,omitempty"`
|
|
}
|
|
|
|
func DefaultPath() (string, error) {
|
|
dir, err := os.UserConfigDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("find config directory: %w", err)
|
|
}
|
|
return filepath.Join(dir, "bdrclone", "config.json"), nil
|
|
}
|
|
|
|
func Load(path string) (*Config, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil, fmt.Errorf("config %q does not exist; run `bdrclone config` first", path)
|
|
}
|
|
return nil, fmt.Errorf("read config: %w", err)
|
|
}
|
|
var cfg Config
|
|
if err := json.Unmarshal(b, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse config: %w", err)
|
|
}
|
|
cfg.applyDefaults()
|
|
return &cfg, cfg.Validate(false)
|
|
}
|
|
|
|
func Save(path string, cfg *Config) error {
|
|
cfg.applyDefaults()
|
|
if err := cfg.Validate(false); err != nil {
|
|
return err
|
|
}
|
|
b, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("encode config: %w", err)
|
|
}
|
|
b = append(b, '\n')
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
|
return fmt.Errorf("create config directory: %w", err)
|
|
}
|
|
tmp, err := os.CreateTemp(filepath.Dir(path), ".config-*")
|
|
if err != nil {
|
|
return fmt.Errorf("create temporary config: %w", err)
|
|
}
|
|
tmpName := tmp.Name()
|
|
defer os.Remove(tmpName)
|
|
if err := tmp.Chmod(0o600); err != nil {
|
|
tmp.Close()
|
|
return fmt.Errorf("secure config: %w", err)
|
|
}
|
|
if _, err := tmp.Write(b); err != nil {
|
|
tmp.Close()
|
|
return fmt.Errorf("write config: %w", err)
|
|
}
|
|
if err := tmp.Close(); err != nil {
|
|
return fmt.Errorf("close config: %w", err)
|
|
}
|
|
if err := os.Rename(tmpName, path); err != nil {
|
|
return fmt.Errorf("replace config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) Validate(requireToken bool) error {
|
|
if strings.TrimSpace(c.ClientID) == "" || strings.TrimSpace(c.ClientSecret) == "" {
|
|
return errors.New("client_id and client_secret are required")
|
|
}
|
|
if requireToken && c.RefreshToken == "" && c.AccessToken == "" {
|
|
return errors.New("no OAuth token; run `bdrclone auth` first")
|
|
}
|
|
if c.UploadParts < 1 || c.UploadParts > 32 {
|
|
return errors.New("upload_parts must be between 1 and 32")
|
|
}
|
|
if c.PartSize != 0 && c.PartSize < 4<<20 {
|
|
return errors.New("part_size must be 0 or at least 4 MiB")
|
|
}
|
|
if c.PartSize > 32<<20 {
|
|
return errors.New("part_size cannot exceed Baidu's 32 MiB maximum")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) applyDefaults() {
|
|
if c.RedirectURI == "" {
|
|
c.RedirectURI = "http://127.0.0.1:53682/callback"
|
|
}
|
|
if c.Root == "" {
|
|
c.Root = "/"
|
|
}
|
|
if !strings.HasPrefix(c.Root, "/") {
|
|
c.Root = "/" + c.Root
|
|
}
|
|
c.Root = strings.TrimSuffix(c.Root, "/")
|
|
if c.Root == "" {
|
|
c.Root = "/"
|
|
}
|
|
if c.UserAgent == "" {
|
|
c.UserAgent = DefaultUserAgent
|
|
}
|
|
if c.UploadParts == 0 {
|
|
c.UploadParts = 3
|
|
}
|
|
}
|