package main import ( "errors" "io" "os" "runtime" "strings" "syscall" "time" ) func moveFile(src, dst string) error { // zuerst Rename (schnell) if err := os.Rename(src, dst); err == nil { return nil } else { // Fallback: Copy+Remove (z.B. bei EXDEV) in, err2 := os.Open(src) if err2 != nil { return err } defer in.Close() out, err2 := os.Create(dst) if err2 != nil { return err } if _, err2 := io.Copy(out, in); err2 != nil { out.Close() return err2 } if err2 := out.Close(); err2 != nil { return err2 } return os.Remove(src) } } const windowsSharingViolation syscall.Errno = 32 // ERROR_SHARING_VIOLATION func isSharingViolation(err error) bool { if runtime.GOOS != "windows" { return false } // Windows: ERROR_SHARING_VIOLATION = 32, ERROR_LOCK_VIOLATION = 33 var pe *os.PathError if errors.As(err, &pe) { if errno, ok := pe.Err.(syscall.Errno); ok { return errno == syscall.Errno(32) || errno == syscall.Errno(33) } } // Fallback über Text s := strings.ToLower(err.Error()) return strings.Contains(s, "sharing violation") || strings.Contains(s, "used by another process") || strings.Contains(s, "wird von einem anderen prozess verwendet") } func removeWithRetry(path string) error { var err error for i := 0; i < 40; i++ { // ~4s bei 100ms err = os.Remove(path) if err == nil { return nil } if isSharingViolation(err) { time.Sleep(100 * time.Millisecond) continue } return err } return err } func renameWithRetry(oldPath, newPath string) error { var err error for i := 0; i < 40; i++ { err = os.Rename(oldPath, newPath) if err == nil { return nil } if isSharingViolation(err) { time.Sleep(100 * time.Millisecond) continue } return err } return err }