From f2e43b7c14c7a0374b61185ebf5fd7f55102f33a Mon Sep 17 00:00:00 2001 From: TowyTowy Date: Wed, 8 Jul 2026 21:58:52 +0200 Subject: [PATCH] seafile: don't implement IDer as the id is a content hash not a unique ID Seafile returns a content-addressed id (a git-like hash of the file contents) rather than a unique per-object identifier. Two different files with identical contents are therefore given the same id, even on different servers. operations.SameObject assumes IDs uniquely identify an object, so when syncing between Seafile remotes it treated two distinct but identical-content files as the same object. The guard in NeedTransfer if Equal(ctx, src, dst) && !SameObject(src, dst) then evaluated to false for unchanged files (Equal was true because the sizes matched, and SameObject was true because the ids matched), so rclone re-uploaded them on every sync. Seafile has no modtime and no usable content hash, so dropping the IDer implementation makes SameObject fall back to path comparison and lets unchanged files be skipped correctly. Fixes #8300 Co-Authored-By: Claude Fable 5 --- backend/seafile/object.go | 9 --------- backend/seafile/seafile.go | 3 --- 2 files changed, 12 deletions(-) diff --git a/backend/seafile/object.go b/backend/seafile/object.go index 25257825a..97933b1a4 100644 --- a/backend/seafile/object.go +++ b/backend/seafile/object.go @@ -12,7 +12,6 @@ import ( // Object describes a seafile object (also commonly called a file) type Object struct { fs *Fs // what this object is part of - id string // internal ID of object remote string // The remote path (full path containing library name if target at root) pathInLibrary string // Path of the object without the library name size int64 // size of the object @@ -107,7 +106,6 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op } // Set the properties from the upload back to the object o.size = uploaded.Size - o.id = uploaded.ID return nil } @@ -118,10 +116,3 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op func (o *Object) Remove(ctx context.Context) error { return o.fs.deleteFile(ctx, o.libraryID, o.pathInLibrary) } - -// ==================== Optional Interface fs.IDer ==================== - -// ID returns the ID of the Object if known, or "" if not -func (o *Object) ID() string { - return o.id -} diff --git a/backend/seafile/seafile.go b/backend/seafile/seafile.go index 1151def5f..fbdad6b58 100644 --- a/backend/seafile/seafile.go +++ b/backend/seafile/seafile.go @@ -550,7 +550,6 @@ func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) { o := &Object{ fs: f, libraryID: libraryID, - id: fileDetails.ID, remote: remote, pathInLibrary: filePath, modTime: modTime, @@ -1239,7 +1238,6 @@ func (f *Fs) buildDirEntries(parentPath, libraryID, parentPathInLibrary string, } else if entry.Type == api.FileTypeFile { object := &Object{ fs: f, - id: entry.ID, remote: filePath, pathInLibrary: filePathInLibrary, size: entry.Size, @@ -1359,5 +1357,4 @@ var ( _ fs.UserInfoer = &Fs{} _ fs.Shutdowner = &Fs{} _ fs.Object = &Object{} - _ fs.IDer = &Object{} )