From 292ae22dcdf864935a66c9b2dcf892e7c2ef5d9e Mon Sep 17 00:00:00 2001 From: truewhile <62226914+truewhile@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:36:54 +0800 Subject: [PATCH] fix(sync): deduplicate remoteMeta by file ID to prevent deleting valid remote metadata copies --- internal/service/strm_sync.go | 32 ++++++++++++++++++++---------- internal/service/strm_sync_test.go | 24 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/internal/service/strm_sync.go b/internal/service/strm_sync.go index e06bbbc..2c84904 100644 --- a/internal/service/strm_sync.go +++ b/internal/service/strm_sync.go @@ -1341,6 +1341,7 @@ func (st *strmSyncState) localSha1Matches(path, remoteSha1 string) bool { } // recordRemoteMeta 记录远端存在的元数据索引、文件大小、文件引用及内容 SHA1(多副本聚合追加)。 +// 对同一文件 ID 严格去重,避免当 download_meta 与 upload_meta 同时开启时因重复记录引发误判与自杀式删除。 func (st *strmSyncState) recordRemoteMeta(entry cloud.FileEntry, rel string) { st.mu.Lock() defer st.mu.Unlock() @@ -1349,6 +1350,13 @@ func (st *strmSyncState) recordRemoteMeta(entry cloud.FileEntry, rel string) { } key := "m:" + rel st.seenMeta[key] = true + if entry.ID != "" { + for _, existing := range st.remoteMeta[key] { + if existing.ID == entry.ID { + return + } + } + } st.remoteMeta[key] = append(st.remoteMeta[key], remoteMetaItem{ ID: entry.ID, Size: entry.Size, @@ -1624,21 +1632,23 @@ func (st *strmSyncState) scanLocalMetaForUpload() error { } } } - if matchedIdx >= 0 { - // 远端已存在完全一致的副本,跳过上传! - // 若远端还存在其他同名脏副本(副本总数 > 1),在 115 下收集待删除 ID,稍后按目录批量删除 - if len(entries) > 1 && st.p.Provider == model.StrmProvider115 { - parentCID := st.uploadRemoteTarget(rel) - if parentCID != "" { - for i, it := range entries { - if i != matchedIdx && it.ID != "" { - pendingDeletes[parentCID] = append(pendingDeletes[parentCID], it.ID) + if matchedIdx >= 0 { + // 远端已存在完全一致的副本,跳过上传! + // 若远端还存在其他同名脏副本(副本总数 > 1),在 115 下收集待删除 ID,稍后按目录批量删除。 + // 严禁将已命中的最新副本 ID (matchedID) 放入待删列表,杜绝误杀唯一有效副本。 + if len(entries) > 1 && st.p.Provider == model.StrmProvider115 { + parentCID := st.uploadRemoteTarget(rel) + if parentCID != "" { + matchedID := entries[matchedIdx].ID + for i, it := range entries { + if i != matchedIdx && it.ID != "" && it.ID != matchedID { + pendingDeletes[parentCID] = append(pendingDeletes[parentCID], it.ID) + } } } } + return nil } - return nil - } } remoteTarget := st.uploadRemoteTarget(rel) diff --git a/internal/service/strm_sync_test.go b/internal/service/strm_sync_test.go index 8673234..23dedf0 100644 --- a/internal/service/strm_sync_test.go +++ b/internal/service/strm_sync_test.go @@ -1320,6 +1320,30 @@ func TestScanLocalMetaForUploadMultipleCopiesAllStale(t *testing.T) { } } +// TestRecordRemoteMetaDeduplication 验证当 download_meta 与 upload_meta 同时开启时, +// 同一远端元数据被多次入账不会在 remoteMeta 中生成重复副本,杜绝误判自杀式删除。 +func TestRecordRemoteMetaDeduplication(t *testing.T) { + st := &strmSyncState{ + seenMeta: map[string]bool{}, + remoteMeta: map[string][]remoteMetaItem{}, + } + entry := cloud.FileEntry{ + ID: "unique-fid-1", + Name: "test.nfo", + Size: 100, + Sha1: "AAAABBBBCCCC", + MTime: 12345, + } + // 连续记录两次同一文件 + st.recordRemoteMeta(entry, "dir/test.nfo") + st.recordRemoteMeta(entry, "dir/test.nfo") + + items := st.remoteMeta["m:dir/test.nfo"] + if len(items) != 1 { + t.Fatalf("expected 1 item after duplicate record, got %d", len(items)) + } +} + // TestWalk115AdaptiveHierarchicalFlatScan 验证自适应分治扁平化扫描: // 当根目录探测总数 >= 9500 时,系统自动分治展开单层直接子项,对各子目录分别执行扁平拉取, // 正确合并根目录直属文件与各子目录深层文件,突破 115 开放平台 10000 深度限制。