From 4047eec8c8dfb7d0cad366dc826707cd6d8c650b Mon Sep 17 00:00:00 2001 From: Aditya Date: Sun, 30 Aug 2026 03:04:12 +0530 Subject: [PATCH] fix: delete the previous content when a file is replaced ReplaceFile rewrites the metadata entry in place to point at the new content, which leaves the data the file used to hold with no entry referencing it. CleanUp walks metadata and only frees a source it can still reach from an entry, so that data is never reclaimed, with or without deleteNewFile. Capture the entry before it is rewritten and delete its source, unless another entry still shares the same SHA1. Closes #407 --- internal/storage/FileServing.go | 16 ++++++++++++ internal/storage/FileServing_test.go | 38 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/internal/storage/FileServing.go b/internal/storage/FileServing.go index 64ce9f70..e0b76788 100644 --- a/internal/storage/FileServing.go +++ b/internal/storage/FileServing.go @@ -384,6 +384,7 @@ func ReplaceFile(fileId, newFileContentId string, delete bool) (models.File, err return models.File{}, ErrorReplaceE2EFile } + previousContent := file file.Name = newFileContent.Name file.Size = newFileContent.Size file.SHA1 = newFileContent.SHA1 @@ -392,12 +393,27 @@ func ReplaceFile(fileId, newFileContentId string, delete bool) (models.File, err file.SizeBytes = newFileContent.SizeBytes file.Encryption = newFileContent.Encryption database.SaveMetaData(file) + deleteUnreferencedSource(previousContent) if delete { DeleteFile(newFileContent.Id, true) } return file, nil } +// deleteUnreferencedSource deletes the stored data of a file that no metadata entry points at anymore. +// CleanUp only reaches sources that are still referenced by an entry, so the content a replaced file +// used to hold would otherwise stay in storage for good. +func deleteUnreferencedSource(file models.File) { + for _, metadata := range database.GetAllMetadata() { + if metadata.SHA1 == file.SHA1 { + return + } + } + if FileExists(file, configuration.Get().DataDir) { + deleteSource(file, configuration.Get().DataDir) + } +} + func isChangeRequested(parametersToChange, parameter int) bool { return parametersToChange¶meter != 0 } diff --git a/internal/storage/FileServing_test.go b/internal/storage/FileServing_test.go index d8369d62..1cfdddd5 100644 --- a/internal/storage/FileServing_test.go +++ b/internal/storage/FileServing_test.go @@ -895,6 +895,44 @@ func TestReplaceFile(t *testing.T) { _, ok = GetFile(newFile.Id) test.IsEqualBool(t, ok, false) } +func TestReplaceFileDeletesPreviousContent(t *testing.T) { + dataDir := configuration.Get().DataDir + previousSha1 := "replacedoldcontent" + newSha1 := "replacednewcontent" + test.IsNil(t, os.WriteFile(dataDir+"/"+previousSha1, []byte("old"), 0600)) + test.IsNil(t, os.WriteFile(dataDir+"/"+newSha1, []byte("new"), 0600)) + + original := models.File{ + Id: "replacesourceoriginal", + Name: "old.txt", + Size: "3 B", + SHA1: previousSha1, + ContentType: "text/plain", + UnlimitedDownloads: true, + UnlimitedTime: true, + SizeBytes: 3, + } + replacement := models.File{ + Id: "replacesourcenew", + Name: "new.txt", + Size: "3 B", + SHA1: newSha1, + ContentType: "text/plain", + UnlimitedDownloads: true, + UnlimitedTime: true, + SizeBytes: 3, + } + database.SaveMetaData(original) + database.SaveMetaData(replacement) + + _, err := ReplaceFile(original.Id, replacement.Id, false) + test.IsNil(t, err) + + _, err = os.Stat(dataDir + "/" + previousSha1) + test.IsEqualBool(t, os.IsNotExist(err), true) + test.FileExists(t, dataDir+"/"+newSha1) +} + func TestParallelDownloads(t *testing.T) { const allowedDownloads = 5