Untitled
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() } }
Leave a Comment