chore: stop treating dirs as having size 128 (#10750)

Resp. directories for the database, and both dirs and symlinks for
`FileInfo.FileSize`. Instead handle it in the UI progress percentage. We
even already have special cases there for deletions, might as well
handle directories just like any other zero-sized needed item there.

This went through the very thorough testing of running it on my laptop,
the migration was applied and it seemed to be working fine after.

---------

Signed-off-by: Simon Frei <freisim93@gmail.com>
This commit is contained in:
Simon Frei
2026-06-22 20:32:03 +02:00
committed by GitHub
parent 5313c75eba
commit c9236b1adc
12 changed files with 44 additions and 41 deletions
@@ -1139,20 +1139,20 @@ angular.module('syncthing.core')
};
$scope.syncPercentage = function (folder) {
if (typeof $scope.model[folder] === 'undefined') {
var model = $scope.model[folder];
if (typeof model === 'undefined') {
return 100;
}
if ($scope.model[folder].needTotalItems === 0) {
if (model.needTotalItems === 0) {
return 100;
}
if (($scope.model[folder].needBytes == 0 && $scope.model[folder].needDeletes > 0) || $scope.model[folder].globalBytes == 0) {
// We don't need any data, but we have deletes that we need
// to do. Drop down the completion percentage to indicate
// that we have stuff to do.
// Do the same thing in case we only have zero byte files to sync.
if (model.needBytes == 0 && model.needTotalItems > 0) {
// We don't need any data, but we have deletes, directories,
// symlinks or zero byte files that we need to do. Drop down the
// completion percentage to indicate that we have stuff to do.
return 95;
}
return progressIntegerPercentage($scope.model[folder].inSyncBytes, $scope.model[folder].globalBytes);
return progressIntegerPercentage(model.inSyncBytes, model.globalBytes);
};
$scope.scanPercentage = function (folder) {
+3 -4
View File
@@ -29,7 +29,6 @@ import (
const (
folderID = "test"
blockSize = 128 << 10
dirSize = 128
)
func TestBasics(t *testing.T) {
@@ -72,11 +71,11 @@ func TestBasics(t *testing.T) {
t.Fatal(err)
}
const (
localSize = (1+2+3)*blockSize + dirSize
localSize = (1+2+3)*blockSize
remoteSize = (3 + 4 + 5) * blockSize
globalSize = (2+3+3+4+5)*blockSize + dirSize
globalSize = (2+3+3+4+5)*blockSize
needSizeLocal = remoteSize
needSizeRemote = (2+3)*blockSize + dirSize
needSizeRemote = (2+3)*blockSize
)
t.Run("SchemaVersion", func(t *testing.T) {
-4
View File
@@ -111,10 +111,6 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo, opti
f.BlocksHash = nil
}
if f.Type == protocol.FileInfoTypeDirectory {
f.Size = 128 // synthetic directory size
}
// Insert the file.
//
// If it is a remote file, set remote_sequence otherwise leave it at
@@ -0,0 +1,12 @@
-- Copyright (C) 2026 The Syncthing Authors.
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this file,
-- You can obtain one at https://mozilla.org/MPL/2.0/.
-- All non-file entries should have size zero.
-- Directories were previously stored with a "synthetic" size of 128.
UPDATE files
SET size = 0
WHERE type != 0
;
+4
View File
@@ -106,6 +106,10 @@ func (i indirectFI) FileInfo() (protocol.FileInfo, error) {
fi.Blocks = bl.Blocks
}
fi.Name = osutil.NativeFilename(fi.Name)
// Undo earlier behaviour of setting a synthetic size on dirs.
if fi.Type != protocol.FileInfoTypeFile && fi.Size > 0 {
fi.Size = 0
}
return protocol.FileInfoFromDB(&fi), nil
}
+1 -1
View File
@@ -1774,7 +1774,7 @@ func fileIntfJSONMap(f protocol.FileInfo) map[string]interface{} {
out := map[string]interface{}{
"name": f.FileName(),
"type": f.FileType().String(),
"size": f.FileSize(),
"size": f.Size,
"deleted": f.IsDeleted(),
"invalid": f.IsInvalid(),
"ignored": f.IsIgnored(),
+4 -5
View File
@@ -163,12 +163,11 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// We now have a newer file than the rest of the cluster. Global state should reflect this.
size = mustV(m.GlobalSize("ro"))
const sizeOfDir = 128
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
if size.Files != 1 || size.Bytes != int64(len(oldData)) {
t.Fatalf("Global: expected no change due to the new file: %+v", size)
}
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
if size.Files != 1 || size.Bytes != int64(len(newData)) {
t.Fatalf("Local: expected the new file to be reflected: %+v", size)
}
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
@@ -185,11 +184,11 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
m.Revert("ro")
size = mustV(m.GlobalSize("ro"))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
if size.Files != 1 || size.Bytes != int64(len(oldData)) {
t.Fatalf("Global: expected the global size to revert: %+v", size)
}
size = mustV(m.LocalSize("ro", protocol.LocalDeviceID))
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
if size.Files != 1 || size.Bytes != int64(len(newData)) {
t.Fatalf("Local: expected the local size to remain: %+v", size)
}
size = mustV(m.NeedSize("ro", protocol.LocalDeviceID))
+4 -2
View File
@@ -1787,10 +1787,12 @@ func TestGlobalDirectoryTree(t *testing.T) {
b := func(isfile bool, path ...string) protocol.FileInfo {
typ := protocol.FileInfoTypeDirectory
var blocks []protocol.BlockInfo
var size int64
if isfile {
typ = protocol.FileInfoTypeFile
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
size = 0xa
}
seq++
return protocol.FileInfo{
@@ -1798,7 +1800,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
Type: typ,
ModifiedS: 0x666,
Blocks: blocks,
Size: 0xa,
Size: size,
Sequence: seq,
}
}
@@ -1814,7 +1816,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
return &TreeEntry{
Name: name,
ModTime: time.Unix(0x666, 0),
Size: 128,
Size: 0,
Type: protocol.FileInfoTypeDirectory.String(),
Children: entries,
}
-10
View File
@@ -389,16 +389,6 @@ func (f FileInfo) HasPermissionBits() bool {
return !f.NoPermissions
}
func (f FileInfo) FileSize() int64 {
if f.Deleted {
return 0
}
if f.IsDirectory() || f.IsSymlink() {
return SyntheticDirectorySize
}
return f.Size
}
func (f FileInfo) BlockSize() int {
if f.RawBlockSize < MinBlockSize {
return MinBlockSize
+3 -2
View File
@@ -56,7 +56,8 @@ const (
// DesiredPerFileBlocks is the number of blocks we aim for per file
DesiredPerFileBlocks = 2000
SyntheticDirectorySize = 128
// We used to send this size for directories and still accept that for compat.
deprecatedSyntheticDirectorySize = 128
// don't bother compressing messages smaller than this many bytes
compressionThreshold = 128
@@ -640,7 +641,7 @@ func checkFileInfoConsistency(f FileInfo) error {
// Only files should have blocks
return errNonFileHasBlocks
case f.IsDirectory() && f.Size != 0 && f.Size != SyntheticDirectorySize:
case f.IsDirectory() && f.Size != 0 && f.Size != deprecatedSyntheticDirectorySize:
// Directories should be size zero or the synthetic directory size
return errNonFileHasSize
+2 -2
View File
@@ -464,7 +464,7 @@ func TestCheckConsistency(t *testing.T) {
fi: FileInfo{
Name: "foo",
Type: FileInfoTypeDirectory,
Size: SyntheticDirectorySize,
Size: deprecatedSyntheticDirectorySize,
},
ok: true,
},
@@ -492,7 +492,7 @@ func TestCheckConsistency(t *testing.T) {
Name: "foo",
Type: FileInfoTypeSymlink,
SymlinkTarget: []byte("bar"),
Size: SyntheticDirectorySize,
Size: deprecatedSyntheticDirectorySize,
},
ok: false,
},
+3 -3
View File
@@ -41,9 +41,9 @@ type testfileList []testfile
var testdata = testfileList{
{"afile", 4, "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
{"dir1", 128, ""},
{"dir1", 0, ""},
{filepath.Join("dir1", "dfile"), 5, "49ae93732fcf8d63fe1cce759664982dbd5b23161f007dba8561862adc96d063"},
{"dir2", 128, ""},
{"dir2", 0, ""},
{filepath.Join("dir2", "cfile"), 4, "bf07a7fbb825fc0aae7bf4a1177b2b31fcf8a3feeaf7092761e18c859ee52a9c"},
{"excludes", 37, "df90b52f0c55dba7a7a940affe482571563b1ac57bd5be4d8a0291e7de928e06"},
{"further-excludes", 5, "7eb0a548094fa6295f7fd9200d69973e5f5ec5c04f2a86d998080ac43ecf89f1"},
@@ -601,7 +601,7 @@ func (l fileList) testfiles() testfileList {
if len(f.Blocks) > 1 {
panic("simple test case stuff only supports a single block per file")
}
testfiles[i] = testfile{name: f.Name, length: f.FileSize()}
testfiles[i] = testfile{name: f.Name, length: f.Size}
if len(f.Blocks) == 1 {
testfiles[i].hash = fmt.Sprintf("%x", f.Blocks[0].Hash)
}