|
5 | 5 | "encoding/json" |
6 | 6 | "fmt" |
7 | 7 | "net/http" |
| 8 | + "net/url" |
8 | 9 | "os" |
9 | 10 | "os/exec" |
10 | 11 | "path" |
@@ -72,6 +73,14 @@ func (ExecRunner) Run(ctx context.Context, spec CommandSpec) error { |
72 | 73 | return cmd.Run() |
73 | 74 | } |
74 | 75 |
|
| 76 | +type sourceExistsResult int |
| 77 | + |
| 78 | +const ( |
| 79 | + sourceExistsUnknown sourceExistsResult = iota |
| 80 | + sourceExistsYes |
| 81 | + sourceExistsNo |
| 82 | +) |
| 83 | + |
75 | 84 | func NormalizeEventPath(p string) (string, error) { |
76 | 85 | p = strings.TrimSpace(strings.ReplaceAll(p, "\\", "/")) |
77 | 86 | p = strings.TrimPrefix(p, "/") |
@@ -241,18 +250,72 @@ func ApplyEventBatch(ctx context.Context, runner Runner, pool Pool, batch EventB |
241 | 250 | default: |
242 | 251 | sourcePath := path.Join(safeSegment(batch.Namespace), safeSegment(batch.Volume), event.Path) |
243 | 252 | targetPath := filepath.Join(targetRoot, filepath.FromSlash(event.Path)) |
| 253 | + exists, err := sourcePathExists(ctx, batch.Source, sourcePath) |
| 254 | + if err != nil { |
| 255 | + return err |
| 256 | + } |
| 257 | + if exists == sourceExistsNo { |
| 258 | + if err := os.RemoveAll(targetPath); err != nil { |
| 259 | + return err |
| 260 | + } |
| 261 | + continue |
| 262 | + } |
244 | 263 | spec, err := BuildRcloneCopyToCommand(batch.Source, sourcePath, targetPath) |
245 | 264 | if err != nil { |
246 | 265 | return err |
247 | 266 | } |
248 | 267 | if err := runner.Run(ctx, spec); err != nil { |
| 268 | + exists, existsErr := sourcePathExists(ctx, batch.Source, sourcePath) |
| 269 | + if existsErr != nil { |
| 270 | + return err |
| 271 | + } |
| 272 | + if exists == sourceExistsNo { |
| 273 | + if removeErr := os.RemoveAll(targetPath); removeErr != nil { |
| 274 | + return removeErr |
| 275 | + } |
| 276 | + continue |
| 277 | + } |
249 | 278 | return err |
250 | 279 | } |
251 | 280 | } |
252 | 281 | } |
253 | 282 | return nil |
254 | 283 | } |
255 | 284 |
|
| 285 | +func sourcePathExists(ctx context.Context, source SourceRef, sourcePath string) (sourceExistsResult, error) { |
| 286 | + if strings.TrimSpace(source.WebDAVURL) == "" { |
| 287 | + return sourceExistsUnknown, nil |
| 288 | + } |
| 289 | + base, err := url.Parse(strings.TrimRight(source.WebDAVURL, "/") + "/") |
| 290 | + if err != nil { |
| 291 | + return sourceExistsUnknown, err |
| 292 | + } |
| 293 | + rel, err := url.Parse(path.Join(strings.Trim(sourcePath, "/"))) |
| 294 | + if err != nil { |
| 295 | + return sourceExistsUnknown, err |
| 296 | + } |
| 297 | + req, err := http.NewRequestWithContext(ctx, http.MethodHead, base.ResolveReference(rel).String(), nil) |
| 298 | + if err != nil { |
| 299 | + return sourceExistsUnknown, err |
| 300 | + } |
| 301 | + if source.User != "" || source.Password != "" { |
| 302 | + req.SetBasicAuth(source.User, source.Password) |
| 303 | + } |
| 304 | + resp, err := http.DefaultClient.Do(req) |
| 305 | + if err != nil { |
| 306 | + return sourceExistsUnknown, nil |
| 307 | + } |
| 308 | + defer resp.Body.Close() |
| 309 | + switch resp.StatusCode { |
| 310 | + case http.StatusOK, http.StatusNoContent, http.StatusPartialContent: |
| 311 | + return sourceExistsYes, nil |
| 312 | + case http.StatusNotFound, http.StatusGone: |
| 313 | + return sourceExistsNo, nil |
| 314 | + default: |
| 315 | + return sourceExistsUnknown, nil |
| 316 | + } |
| 317 | +} |
| 318 | + |
256 | 319 | func ApplyFullSync(ctx context.Context, runner Runner, pool Pool, req FullSyncRequest) error { |
257 | 320 | if runner == nil { |
258 | 321 | runner = ExecRunner{} |
|
0 commit comments