gcal-sync/config/config.go
2024-10-01 13:44:45 -04:00

104 lines
2.6 KiB
Go

package config
import (
"context"
"fmt"
"net/http"
"os"
"github.com/emersion/go-ical"
"github.com/emersion/go-webdav"
"github.com/emersion/go-webdav/caldav"
"gopkg.in/yaml.v3"
)
type Config struct {
Server string `yaml:"server"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Calendars []CalendarSync `yaml:"calendars"`
}
type CalendarSync struct {
Name string `yaml:"name"`
Destination string `yaml:"destination"`
URL string `yaml:"url"`
}
func (item *CalendarSync) Sync(ctx context.Context, cfg *Config) error {
webdavClient := webdav.HTTPClientWithBasicAuth(http.DefaultClient, cfg.Username, cfg.Password)
cc, err := caldav.NewClient(webdavClient, cfg.Server)
if err != nil {
return fmt.Errorf("failed to construct a CalDAV client: %w", err)
}
calPath, err := CalendarPath(ctx, cc, cfg.Username, item.Destination)
if err != nil {
return fmt.Errorf("failed to find destination calendar path: %w", err)
}
resp, err := http.Get(item.URL)
if err != nil {
return fmt.Errorf("failed to sync %s: %w", item.Name, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("source returned non-200 status code: %s", resp.Status)
}
calendarData, err := ical.NewDecoder(resp.Body).Decode()
if err != nil {
return fmt.Errorf("failed to decode source calendar data: %w", err)
}
// if err := ical.NewEncoder(os.Stdout).Encode(calendarData); err == nil {
// os.Exit(0)
// }
if _, err := cc.PutCalendarObject(ctx, calPath, calendarData); err != nil {
return fmt.Errorf("failed to push calendar data to destination: %w", err)
}
return nil
}
func CalendarPath(ctx context.Context, cc *caldav.Client, username, calendarName string) (string, error) {
homeSet, err := cc.FindCalendarHomeSet(ctx, username)
if err != nil {
return "", fmt.Errorf("failed to find home set for %s: %w", username, err)
}
calendars, err := cc.FindCalendars(ctx, homeSet)
if err != nil {
return "", fmt.Errorf("failed to retrieve calendars: %w", err)
}
for _, cal := range calendars {
if cal.Name == calendarName {
return cal.Path, nil
}
}
return "", fmt.Errorf("calendar with name %s not found", calendarName)
}
// Load a configuration file
func Load(configFile string) (*Config, error) {
cfg := new(Config)
f, err := os.Open(configFile)
if err != nil {
return nil, fmt.Errorf("failed to open config file %s: %w", configFile, err)
}
defer f.Close()
if err = yaml.NewDecoder(f).Decode(cfg); err != nil {
return nil, fmt.Errorf("failed to decode config file %s: %w", configFile, err)
}
return cfg, nil
}