Untitled
unknown
golang
a year ago
6.5 kB
7
Indexable
package main
import (
"flag"
"io"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/studio-b12/gowebdav"
)
var (
watcher *fsnotify.Watcher
dav *gowebdav.Client
localPath string
serverBase string
serverPath string
user string
password string
syncMutex sync.Mutex
periodicCheck *time.Ticker
)
func main() {
// Parse command line flags
flagLocalPath := flag.String("local_path", "", "Local directory path (env: WEBDAV_LOCAL_PATH)")
flagURI := flag.String("uri", "", "WebDAV server URI (env: WEBDAV_URI)")
flagUser := flag.String("user", "", "WebDAV username (env: WEBDAV_USER)")
flagPassword := flag.String("password", "", "WebDAV password (env: WEBDAV_PASSWORD)")
flag.Parse()
// Get values from environment if flags are empty
localPath = getEnvOrDefault(*flagLocalPath, "WEBDAV_LOCAL_PATH")
uri := getEnvOrDefault(*flagURI, "WEBDAV_URI")
user = getEnvOrDefault(*flagUser, "WEBDAV_USER")
password = getEnvOrDefault(*flagPassword, "WEBDAV_PASSWORD")
// Validate required parameters
if localPath == "" || uri == "" || user == "" || password == "" {
log.Fatal("Missing required parameters. Provide via flags or environment variables")
}
// Parse server URI
parsedURI, err := url.Parse(uri)
if err != nil {
log.Fatalf("Invalid URI: %v", err)
}
serverBase = parsedURI.Scheme + "://" + parsedURI.Host
serverPath = parsedURI.Path
// Initialize WebDAV client
dav = gowebdav.NewClient(serverBase, user, password)
// Initial sync from server
log.Println("Starting initial sync from server...")
if err := syncFromServer(serverPath, localPath); err != nil {
log.Fatalf("Initial sync failed: %v", err)
}
log.Println("Initial sync completed")
// Initialize file watcher
watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Watcher init failed: %v", err)
}
defer watcher.Close()
// Add watchers for all directories
if err := filepath.Walk(localPath, watchDir); err != nil {
log.Fatalf("Failed to initialize watchers: %v", err)
}
// Start periodic server checks
periodicCheck = time.NewTicker(5 * time.Minute)
defer periodicCheck.Stop()
go periodicSync()
// Start watching for changes
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
processEvent(event)
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Printf("Watcher error: %v", err)
}
}
}
func getEnvOrDefault(flagValue, envVar string) string {
if flagValue != "" {
return flagValue
}
return os.Getenv(envVar)
}
func watchDir(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if fi.Mode().IsDir() {
return watcher.Add(path)
}
return nil
}
func syncFromServer(remotePath, localPath string) error {
items, err := dav.ReadDir(remotePath)
if err != nil {
return err
}
for _, item := range items {
remoteFull := filepath.Join(remotePath, item.Name())
localFull := filepath.Join(localPath, item.Name())
if item.IsDir() {
if err := os.MkdirAll(localFull, 0755); err != nil {
return err
}
if err := syncFromServer(remoteFull, localFull); err != nil {
return err
}
} else {
if needsDownload(localFull, item.Size()) {
if err := downloadFile(remoteFull, localFull); err != nil {
return err
}
}
}
}
return nil
}
func needsDownload(localPath string, remoteSize int64) bool {
info, err := os.Stat(localPath)
if os.IsNotExist(err) {
return true
}
if err != nil || info.Size() != remoteSize {
return true
}
return false
}
func downloadFile(remotePath, localPath string) error {
reader, err := dav.ReadStream(remotePath)
if err != nil {
return err
}
defer reader.Close()
file, err := os.Create(localPath)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, reader)
return err
}
func processEvent(event fsnotify.Event) {
syncMutex.Lock()
defer syncMutex.Unlock()
// Ignore temporary files and directories
if strings.HasSuffix(event.Name, "~") || event.Op == fsnotify.Chmod {
return
}
relPath, err := filepath.Rel(localPath, event.Name)
if err != nil {
log.Printf("Error getting relative path: %v", err)
return
}
webdavPath := filepath.Join(serverPath, relPath)
switch {
case event.Op&fsnotify.Write == fsnotify.Write:
handleWrite(event.Name, webdavPath)
case event.Op&fsnotify.Create == fsnotify.Create:
handleCreate(event.Name, webdavPath)
case event.Op&fsnotify.Remove == fsnotify.Remove:
handleRemove(webdavPath)
case event.Op&fsnotify.Rename == fsnotify.Rename:
handleRemove(webdavPath)
}
}
func handleWrite(localPath, webdavPath string) {
if isSameSize(localPath, webdavPath) {
return
}
file, err := os.Open(localPath)
if err != nil {
log.Printf("Error opening file: %v", err)
return
}
defer file.Close()
if err := dav.WriteStream(webdavPath, file, 0644); err != nil {
log.Printf("Upload failed: %v", err)
} else {
log.Printf("Uploaded: %s", webdavPath)
}
}
func handleCreate(localPath, webdavPath string) {
if isDir(localPath) {
if err := dav.MkdirAll(webdavPath, 0755); err != nil {
log.Printf("Directory creation failed: %v", err)
} else {
watcher.Add(localPath)
log.Printf("Created directory: %s", webdavPath)
}
} else {
handleWrite(localPath, webdavPath)
}
}
func handleRemove(webdavPath string) {
if err := dav.Remove(webdavPath); err != nil {
log.Printf("Deletion failed: %v", err)
} else {
log.Printf("Deleted: %s", webdavPath)
}
}
func isDir(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}
func isSameSize(localPath, webdavPath string) bool {
localInfo, err := os.Stat(localPath)
if err != nil {
return false
}
remoteInfo, err := dav.Stat(webdavPath)
if err != nil {
return false
}
return localInfo.Size() == remoteInfo.Size()
}
func periodicSync() {
for range periodicCheck.C {
syncMutex.Lock()
log.Println("Starting periodic server sync...")
if err := syncFromServer(serverPath, localPath); err != nil {
log.Printf("Periodic sync failed: %v", err)
} else {
log.Println("Periodic sync completed")
}
syncMutex.Unlock()
}
}Editor is loading...
Leave a Comment