diff --git a/internal/db/counts.go b/internal/db/counts.go index a24ecdb2f..814950b6e 100644 --- a/internal/db/counts.go +++ b/internal/db/counts.go @@ -19,9 +19,9 @@ type Counts struct { Symlinks int Deleted int Bytes int64 - Sequence int64 // zero for the global state - DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global - LocalFlags uint32 // the local flag for this count bucket + Sequence int64 // zero for the global state + DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global + LocalFlags protocol.FlagLocal // the local flag for this count bucket } func (c Counts) Add(other Counts) Counts { diff --git a/internal/db/interface.go b/internal/db/interface.go index 2fc43ec23..a1d63a4e4 100644 --- a/internal/db/interface.go +++ b/internal/db/interface.go @@ -100,7 +100,7 @@ type FileMetadata struct { Sequence int64 ModNanos int64 Size int64 - LocalFlags int64 + LocalFlags protocol.FlagLocal Type protocol.FileInfoType Deleted bool Invalid bool diff --git a/internal/db/sqlite/basedb.go b/internal/db/sqlite/basedb.go index c2d1b8808..1c69d9548 100644 --- a/internal/db/sqlite/basedb.go +++ b/internal/db/sqlite/basedb.go @@ -103,6 +103,7 @@ func openBase(path string, maxConns int, pragmas, schemaScripts, migrationScript "FlagLocalReceiveOnly": protocol.FlagLocalReceiveOnly, "FlagLocalGlobal": protocol.FlagLocalGlobal, "FlagLocalNeeded": protocol.FlagLocalNeeded, + "LocalInvalidFlags": protocol.LocalInvalidFlags, "SyncthingVersion": build.LongVersion, } diff --git a/internal/db/sqlite/db_global_test.go b/internal/db/sqlite/db_global_test.go index 3a3bae606..c565668f6 100644 --- a/internal/db/sqlite/db_global_test.go +++ b/internal/db/sqlite/db_global_test.go @@ -268,6 +268,57 @@ func TestDontNeedIgnored(t *testing.T) { } } +func TestDontNeedRemoteInvalid(t *testing.T) { + t.Parallel() + + db, err := OpenTemp() + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatal(err) + } + }) + + // A remote file with the invalid bit set + files := []protocol.FileInfo{ + genFile("test1", 1, 103), + } + files[0].LocalFlags = protocol.FlagLocalRemoteInvalid + err = db.Update(folderID, protocol.DeviceID{42}, files) + if err != nil { + t.Fatal(err) + } + + // It's not part of the global size + s, err := db.CountGlobal(folderID) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 { + t.Log(s) + t.Error("bad global") + } + + // We don't need it + s, err = db.CountNeed(folderID, protocol.LocalDeviceID) + if err != nil { + t.Fatal(err) + } + if s.Bytes != 0 || s.Files != 0 { + t.Log(s) + t.Error("bad need") + } + + // It shouldn't show up in the need list + names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)) + if len(names) != 0 { + t.Log(names) + t.Error("need no files") + } +} + func TestRemoteDontNeedLocalIgnored(t *testing.T) { t.Parallel() diff --git a/internal/db/sqlite/folderdb_counts.go b/internal/db/sqlite/folderdb_counts.go index 1c6dfd56f..e4bb0d637 100644 --- a/internal/db/sqlite/folderdb_counts.go +++ b/internal/db/sqlite/folderdb_counts.go @@ -39,13 +39,10 @@ func (s *folderDB) CountNeed(device protocol.DeviceID) (db.Counts, error) { } func (s *folderDB) CountGlobal() (db.Counts, error) { - // Exclude ignored and receive-only changed files from the global count - // (legacy expectation? it's a bit weird since those files can in fact - // be global and you can get them with GetGlobal etc.) var res []countsRow err := s.stmt(` SELECT s.type, s.count, s.size, s.local_flags, s.deleted FROM counts s - WHERE s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{or .FlagLocalReceiveOnly .FlagLocalIgnored}} = 0 + WHERE s.local_flags & {{.FlagLocalGlobal}} != 0 AND s.local_flags & {{.LocalInvalidFlags}} = 0 `).Select(&res) if err != nil { return db.Counts{}, wrap(err) @@ -84,7 +81,7 @@ func (s *folderDB) needSizeRemote(device protocol.DeviceID) (db.Counts, error) { // See neededGlobalFilesRemote for commentary as that is the same query without summing if err := s.stmt(` SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g - WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND NOT EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx WHERE f.name = g.name AND f.version = g.version AND d.device_id = ? @@ -94,10 +91,10 @@ func (s *folderDB) needSizeRemote(device protocol.DeviceID) (db.Counts, error) { UNION ALL SELECT g.type, count(*) as count, sum(g.size) as size, g.local_flags, g.deleted FROM files g - WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND NOT f.invalid + WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND f.local_flags & {{.LocalInvalidFlags}} = 0 ) GROUP BY g.type, g.local_flags, g.deleted `).Select(&res, device.String(), diff --git a/internal/db/sqlite/folderdb_global.go b/internal/db/sqlite/folderdb_global.go index 5dd6c42a3..256c164bc 100644 --- a/internal/db/sqlite/folderdb_global.go +++ b/internal/db/sqlite/folderdb_global.go @@ -74,7 +74,7 @@ func (s *folderDB) GetGlobalAvailability(file string) ([]protocol.DeviceID, erro func (s *folderDB) AllGlobalFiles() (iter.Seq[db.FileMetadata], func() error) { it, errFn := iterStructs[db.FileMetadata](s.stmt(` - SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f WHERE f.local_flags & {{.FlagLocalGlobal}} != 0 ORDER BY f.name `).Queryx()) @@ -93,7 +93,7 @@ func (s *folderDB) AllGlobalFilesPrefix(prefix string) (iter.Seq[db.FileMetadata end := prefixEnd(prefix) it, errFn := iterStructs[db.FileMetadata](s.stmt(` - SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f WHERE f.name >= ? AND f.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0 ORDER BY f.name `).Queryx(prefix, end)) @@ -158,7 +158,7 @@ func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi INNER JOIN files g on fi.sequence = g.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash - WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND NOT EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx WHERE f.name = g.name AND f.version = g.version AND d.device_id = ? @@ -169,10 +169,10 @@ func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi INNER JOIN files g on fi.sequence = g.sequence LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash - WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS ( + WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND EXISTS ( SELECT 1 FROM FILES f INNER JOIN devices d ON d.idx = f.device_idx - WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND NOT f.invalid + WHERE f.name = g.name AND d.device_id = ? AND NOT f.deleted AND f.local_flags & {{.LocalInvalidFlags}} = 0 ) `+selectOpts).Queryx( device.String(), diff --git a/internal/db/sqlite/folderdb_local.go b/internal/db/sqlite/folderdb_local.go index 563174a73..51d47ebe0 100644 --- a/internal/db/sqlite/folderdb_local.go +++ b/internal/db/sqlite/folderdb_local.go @@ -89,7 +89,7 @@ func (s *folderDB) AllLocalFilesWithPrefix(device protocol.DeviceID, prefix stri func (s *folderDB) AllLocalFilesWithBlocksHash(h []byte) (iter.Seq[db.FileMetadata], func() error) { return iterStructs[db.FileMetadata](s.stmt(` - SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f + SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ? `).Queryx(h)) } diff --git a/internal/db/sqlite/folderdb_update.go b/internal/db/sqlite/folderdb_update.go index f787ab8c3..0490da974 100644 --- a/internal/db/sqlite/folderdb_update.go +++ b/internal/db/sqlite/folderdb_update.go @@ -46,8 +46,8 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) erro //nolint:sqlclosecheck insertFileStmt, err := txp.Preparex(` - INSERT OR REPLACE INTO files (device_idx, remote_sequence, name, type, modified, size, version, deleted, invalid, local_flags, blocklist_hash) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT OR REPLACE INTO files (device_idx, remote_sequence, name, type, modified, size, version, deleted, local_flags, blocklist_hash) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING sequence `) if err != nil { @@ -101,7 +101,7 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) erro remoteSeq = &f.Sequence } var localSeq int64 - if err := insertFileStmt.Get(&localSeq, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.IsInvalid(), f.LocalFlags, blockshash); err != nil { + if err := insertFileStmt.Get(&localSeq, deviceIdx, remoteSeq, f.Name, f.Type, f.ModTime().UnixNano(), f.Size, f.Version.String(), f.IsDeleted(), f.LocalFlags, blockshash); err != nil { return wrap(err, "insert file") } @@ -329,7 +329,7 @@ func (s *folderDB) recalcGlobalForFolder(txp *txPreparedStmts) error { func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error { //nolint:sqlclosecheck selStmt, err := txp.Preparex(` - SELECT name, device_idx, sequence, modified, version, deleted, invalid, local_flags FROM files + SELECT name, device_idx, sequence, modified, version, deleted, local_flags FROM files WHERE name = ? `) if err != nil { @@ -350,7 +350,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error // The global version is the first one in the list that is not invalid, // or just the first one in the list if all are invalid. var global fileRow - globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.Invalid }) + globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() }) if globIdx < 0 { globIdx = 0 } @@ -368,7 +368,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error // Set the global flag on the global entry. Set the need flag if the // local device needs this file, unless it's invalid. global.LocalFlags |= protocol.FlagLocalGlobal - if hasLocal || global.Invalid { + if hasLocal || global.IsInvalid() { global.LocalFlags &= ^protocol.FlagLocalNeeded } else { global.LocalFlags |= protocol.FlagLocalNeeded @@ -426,9 +426,8 @@ type fileRow struct { Sequence int64 Modified int64 Size int64 - LocalFlags int64 `db:"local_flags"` + LocalFlags protocol.FlagLocal `db:"local_flags"` Deleted bool - Invalid bool } func (e fileRow) Compare(other fileRow) int { @@ -436,8 +435,8 @@ func (e fileRow) Compare(other fileRow) int { vc := e.Version.Compare(other.Version.Vector) switch vc { case protocol.Equal: - if e.Invalid != other.Invalid { - if e.Invalid { + if e.IsInvalid() != other.IsInvalid() { + if e.IsInvalid() { return 1 } return -1 @@ -453,8 +452,8 @@ func (e fileRow) Compare(other fileRow) int { case protocol.Lesser: // we are older return 1 case protocol.ConcurrentGreater, protocol.ConcurrentLesser: // there is a conflict - if e.Invalid != other.Invalid { - if e.Invalid { // we are invalid, we lose + if e.IsInvalid() != other.IsInvalid() { + if e.IsInvalid() { // we are invalid, we lose return 1 } return -1 // they are invalid, we win @@ -477,6 +476,10 @@ func (e fileRow) Compare(other fileRow) int { } } +func (e fileRow) IsInvalid() bool { + return e.LocalFlags.IsInvalid() +} + func (s *folderDB) periodicCheckpointLocked(fs []protocol.FileInfo) { // Induce periodic checkpoints. We add points for each file and block, // and checkpoint when we've written more than a threshold of points. diff --git a/internal/db/sqlite/sql/schema/folder/20-files.sql b/internal/db/sqlite/sql/schema/folder/20-files.sql index d826f6340..de35649d4 100644 --- a/internal/db/sqlite/sql/schema/folder/20-files.sql +++ b/internal/db/sqlite/sql/schema/folder/20-files.sql @@ -31,7 +31,6 @@ CREATE TABLE IF NOT EXISTS files ( size INTEGER NOT NULL, version TEXT NOT NULL COLLATE BINARY, deleted INTEGER NOT NULL, -- boolean - invalid INTEGER NOT NULL, -- boolean local_flags INTEGER NOT NULL, blocklist_hash BLOB, -- null when there are no blocks FOREIGN KEY(device_idx) REFERENCES devices(idx) ON DELETE CASCADE diff --git a/lib/model/fakeconns_test.go b/lib/model/fakeconns_test.go index 524aa62d5..65b77cb6b 100644 --- a/lib/model/fakeconns_test.go +++ b/lib/model/fakeconns_test.go @@ -74,7 +74,7 @@ func (f *fakeConnection) DownloadProgress(_ context.Context, dp *protocol.Downlo }) } -func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol.FileInfoType, data []byte, version protocol.Vector, localFlags uint32) { +func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol.FileInfoType, data []byte, version protocol.Vector, localFlags protocol.FlagLocal) { blockSize := protocol.BlockSize(int64(len(data))) blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), blockSize, int64(len(data)), nil) diff --git a/lib/model/folder.go b/lib/model/folder.go index 8d98e8495..f0d30b403 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -44,7 +44,7 @@ type folder struct { *stats.FolderStatisticsReference ioLimiter *semaphore.Semaphore - localFlags uint32 + localFlags protocol.FlagLocal model *model shortID protocol.ShortID diff --git a/lib/model/indexhandler.go b/lib/model/indexhandler.go index 4149721d0..739557e7f 100644 --- a/lib/model/indexhandler.go +++ b/lib/model/indexhandler.go @@ -481,8 +481,6 @@ func (s *indexHandler) logSequenceAnomaly(msg string, extra map[string]any) { } func prepareFileInfoForIndex(f protocol.FileInfo) protocol.FileInfo { - // Mark the file as invalid if any of the local bad stuff flags are set. - f.RawInvalid = f.IsInvalid() // If the file is marked LocalReceive (i.e., changed locally on a // receive only folder) we do not want it to ever become the // globally best version, invalid or not. diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 34a76a7ee..8a46ade09 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -3609,7 +3609,7 @@ func TestIssue6961(t *testing.T) { // Remote, valid and existing file must(t, m.Index(conn1, &protocol.Index{Folder: fcfg.ID, Files: []protocol.FileInfo{{Name: name, Version: version, Sequence: 1}}})) // Remote, invalid (receive-only) and existing file - must(t, m.Index(conn2, &protocol.Index{Folder: fcfg.ID, Files: []protocol.FileInfo{{Name: name, RawInvalid: true, Sequence: 1}}})) + must(t, m.Index(conn2, &protocol.Index{Folder: fcfg.ID, Files: []protocol.FileInfo{{Name: name, LocalFlags: protocol.FlagLocalRemoteInvalid, Sequence: 1}}})) // Create a local file if fd, err := tfs.OpenFile(name, fs.OptCreate, 0o666); err != nil { t.Fatal(err) @@ -3635,7 +3635,7 @@ func TestIssue6961(t *testing.T) { m.ScanFolders() // Drop the remote index, add some other file. - must(t, m.Index(conn2, &protocol.Index{Folder: fcfg.ID, Files: []protocol.FileInfo{{Name: "bar", RawInvalid: true, Sequence: 1}}})) + must(t, m.Index(conn2, &protocol.Index{Folder: fcfg.ID, Files: []protocol.FileInfo{{Name: "bar", LocalFlags: protocol.FlagLocalRemoteInvalid, Sequence: 1}}})) // Pause and unpause folder to create new db.FileSet and thus recalculate everything pauseFolder(t, wcfg, fcfg.ID, true) diff --git a/lib/protocol/bep_fileinfo.go b/lib/protocol/bep_fileinfo.go index 7925d14d6..fe88e3894 100644 --- a/lib/protocol/bep_fileinfo.go +++ b/lib/protocol/bep_fileinfo.go @@ -17,26 +17,33 @@ import ( "github.com/syncthing/syncthing/lib/build" ) +type FlagLocal uint32 + // FileInfo.LocalFlags flags const ( - FlagLocalUnsupported = 1 << 0 // 1: The kind is unsupported, e.g. symlinks on Windows - FlagLocalIgnored = 1 << 1 // 2: Matches local ignore patterns - FlagLocalMustRescan = 1 << 2 // 4: Doesn't match content on disk, must be rechecked fully - FlagLocalReceiveOnly = 1 << 3 // 8: Change detected on receive only folder - FlagLocalGlobal = 1 << 4 // 16: This is the global file version - FlagLocalNeeded = 1 << 5 // 32: We need this file + FlagLocalUnsupported FlagLocal = 1 << 0 // 1: The kind is unsupported, e.g. symlinks on Windows + FlagLocalIgnored FlagLocal = 1 << 1 // 2: Matches local ignore patterns + FlagLocalMustRescan FlagLocal = 1 << 2 // 4: Doesn't match content on disk, must be rechecked fully + FlagLocalReceiveOnly FlagLocal = 1 << 3 // 8: Change detected on receive only folder + FlagLocalGlobal FlagLocal = 1 << 4 // 16: This is the global file version + FlagLocalNeeded FlagLocal = 1 << 5 // 32: We need this file + FlagLocalRemoteInvalid FlagLocal = 1 << 6 // 64: The remote marked this as invalid - // Flags that should result in the Invalid bit on outgoing updates - LocalInvalidFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly + // Flags that should result in the Invalid bit on outgoing updates (or had it on ingoing ones) + LocalInvalidFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly | FlagLocalRemoteInvalid // Flags that should result in a file being in conflict with its // successor, due to us not having an up to date picture of its state on // disk. LocalConflictFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalReceiveOnly - LocalAllFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly | FlagLocalGlobal | FlagLocalNeeded + LocalAllFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly | FlagLocalGlobal | FlagLocalNeeded | FlagLocalRemoteInvalid ) +func (f FlagLocal) IsInvalid() bool { + return f&LocalInvalidFlags != 0 +} + // BlockSizes is the list of valid block sizes, from min to max var BlockSizes []int @@ -82,7 +89,9 @@ type FileInfo struct { // host only. It is not part of the protocol, doesn't get sent or // received (we make sure to zero it), nonetheless we need it on our // struct and to be able to serialize it to/from the database. - LocalFlags uint32 + // It does carry the info to decide if the file is invalid, which is part of + // the protocol. + LocalFlags FlagLocal // The version_hash is an implementation detail and not part of the wire // format. @@ -97,7 +106,6 @@ type FileInfo struct { EncryptionTrailerSize int Deleted bool - RawInvalid bool NoPermissions bool truncated bool // was created from a truncated file info without blocks @@ -128,11 +136,11 @@ func (f *FileInfo) ToWire(withInternalFields bool) *bep.FileInfo { BlockSize: f.RawBlockSize, Platform: f.Platform.toWire(), Deleted: f.Deleted, - Invalid: f.RawInvalid, + Invalid: f.IsInvalid(), NoPermissions: f.NoPermissions, } if withInternalFields { - w.LocalFlags = f.LocalFlags + w.LocalFlags = uint32(f.LocalFlags) w.VersionHash = f.VersionHash w.InodeChangeNs = f.InodeChangeNs w.EncryptionTrailerSize = int32(f.EncryptionTrailerSize) @@ -207,6 +215,10 @@ type FileInfoWithoutBlocks interface { } func fileInfoFromWireWithBlocks(w FileInfoWithoutBlocks, blocks []BlockInfo) FileInfo { + var localFlags FlagLocal + if w.GetInvalid() { + localFlags = FlagLocalRemoteInvalid + } return FileInfo{ Name: w.GetName(), Size: w.GetSize(), @@ -224,14 +236,14 @@ func fileInfoFromWireWithBlocks(w FileInfoWithoutBlocks, blocks []BlockInfo) Fil RawBlockSize: w.GetBlockSize(), Platform: platformDataFromWire(w.GetPlatform()), Deleted: w.GetDeleted(), - RawInvalid: w.GetInvalid(), + LocalFlags: localFlags, NoPermissions: w.GetNoPermissions(), } } func FileInfoFromDB(w *bep.FileInfo) FileInfo { f := FileInfoFromWire(w) - f.LocalFlags = w.LocalFlags + f.LocalFlags = FlagLocal(w.LocalFlags) f.VersionHash = w.VersionHash f.InodeChangeNs = w.InodeChangeNs f.EncryptionTrailerSize = int(w.EncryptionTrailerSize) @@ -240,7 +252,7 @@ func FileInfoFromDB(w *bep.FileInfo) FileInfo { func FileInfoFromDBTruncated(w FileInfoWithoutBlocks) FileInfo { f := fileInfoFromWireWithBlocks(w, nil) - f.LocalFlags = w.GetLocalFlags() + f.LocalFlags = FlagLocal(w.GetLocalFlags()) f.VersionHash = w.GetVersionHash() f.InodeChangeNs = w.GetInodeChangeNs() f.EncryptionTrailerSize = int(w.GetEncryptionTrailerSize()) @@ -252,13 +264,13 @@ func (f FileInfo) String() string { switch f.Type { case FileInfoTypeDirectory: return fmt.Sprintf("Directory{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, VersionHash:%x, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, Platform:%v, InodeChangeTime:%v}", - f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.Platform, f.InodeChangeTime()) + f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.Platform, f.InodeChangeTime()) case FileInfoTypeFile: return fmt.Sprintf("File{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, VersionHash:%x, Length:%d, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, BlockSize:%d, NumBlocks:%d, BlocksHash:%x, Platform:%v, InodeChangeTime:%v}", - f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Size, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.RawBlockSize, len(f.Blocks), f.BlocksHash, f.Platform, f.InodeChangeTime()) + f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Size, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.RawBlockSize, len(f.Blocks), f.BlocksHash, f.Platform, f.InodeChangeTime()) case FileInfoTypeSymlink, FileInfoTypeSymlinkDirectory, FileInfoTypeSymlinkFile: return fmt.Sprintf("Symlink{Name:%q, Type:%v, Sequence:%d, Version:%v, VersionHash:%x, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, SymlinkTarget:%q, Platform:%v, InodeChangeTime:%v}", - f.Name, f.Type, f.Sequence, f.Version, f.VersionHash, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.SymlinkTarget, f.Platform, f.InodeChangeTime()) + f.Name, f.Type, f.Sequence, f.Version, f.VersionHash, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.SymlinkTarget, f.Platform, f.InodeChangeTime()) default: panic("mystery file type detected") } @@ -269,7 +281,7 @@ func (f FileInfo) IsDeleted() bool { } func (f FileInfo) IsInvalid() bool { - return f.RawInvalid || f.LocalFlags&LocalInvalidFlags != 0 + return f.LocalFlags.IsInvalid() } func (f FileInfo) IsUnsupported() bool { @@ -342,7 +354,7 @@ func (f FileInfo) FileName() string { return f.Name } -func (f FileInfo) FileLocalFlags() uint32 { +func (f FileInfo) FileLocalFlags() FlagLocal { return f.LocalFlags } @@ -386,7 +398,7 @@ type FileInfoComparison struct { ModTimeWindow time.Duration IgnorePerms bool IgnoreBlocks bool - IgnoreFlags uint32 + IgnoreFlags FlagLocal IgnoreOwnership bool IgnoreXattrs bool } @@ -533,8 +545,7 @@ func (f *FileInfo) SetDeleted(by ShortID) { f.setNoContent() } -func (f *FileInfo) setLocalFlags(flags uint32) { - f.RawInvalid = false +func (f *FileInfo) setLocalFlags(flags FlagLocal) { f.LocalFlags = flags f.setNoContent() } diff --git a/lib/protocol/bep_fileinfo_test.go b/lib/protocol/bep_fileinfo_test.go index ec241db68..fb50e24be 100644 --- a/lib/protocol/bep_fileinfo_test.go +++ b/lib/protocol/bep_fileinfo_test.go @@ -45,7 +45,7 @@ func TestIsEquivalent(t *testing.T) { b FileInfo ignPerms *bool // nil means should not matter, we'll test both variants ignBlocks *bool - ignFlags uint32 + ignFlags FlagLocal eq bool } cases := []testCase{ @@ -75,8 +75,8 @@ func TestIsEquivalent(t *testing.T) { eq: false, }, { - a: FileInfo{RawInvalid: false}, - b: FileInfo{RawInvalid: true}, + a: FileInfo{LocalFlags: 0}, + b: FileInfo{LocalFlags: FlagLocalRemoteInvalid}, eq: false, }, { @@ -100,8 +100,8 @@ func TestIsEquivalent(t *testing.T) { eq: false, }, { - a: FileInfo{RawInvalid: true}, - b: FileInfo{RawInvalid: true}, + a: FileInfo{LocalFlags: FlagLocalRemoteInvalid}, + b: FileInfo{LocalFlags: FlagLocalRemoteInvalid}, eq: true, }, { @@ -110,7 +110,7 @@ func TestIsEquivalent(t *testing.T) { eq: true, }, { - a: FileInfo{RawInvalid: true}, + a: FileInfo{LocalFlags: FlagLocalRemoteInvalid}, b: FileInfo{LocalFlags: FlagLocalUnsupported}, eq: true, }, diff --git a/lib/protocol/conflict_test.go b/lib/protocol/conflict_test.go index ec7e9961b..b68382252 100644 --- a/lib/protocol/conflict_test.go +++ b/lib/protocol/conflict_test.go @@ -15,7 +15,7 @@ func TestWinsConflict(t *testing.T) { // The first should always win over the second {{ModifiedS: 42}, {ModifiedS: 41}}, {{ModifiedS: 41}, {ModifiedS: 42, Deleted: true}}, - {{Deleted: true}, {ModifiedS: 10, RawInvalid: true}}, + {{Deleted: true}, {ModifiedS: 10, LocalFlags: FlagLocalRemoteInvalid}}, {{ModifiedS: 41, Version: Vector{Counters: []Counter{{ID: 42, Value: 2}, {ID: 43, Value: 1}}}}, {ModifiedS: 41, Version: Vector{Counters: []Counter{{ID: 42, Value: 1}, {ID: 43, Value: 2}}}}}, } diff --git a/lib/protocol/encryption.go b/lib/protocol/encryption.go index b9282c32f..311e49e0c 100644 --- a/lib/protocol/encryption.go +++ b/lib/protocol/encryption.go @@ -357,11 +357,13 @@ func encryptFileInfo(keyGen *KeyGenerator, fi FileInfo, folderKey *[keySize]byte Permissions: 0o644, ModifiedS: 1234567890, // Sat Feb 14 00:31:30 CET 2009 Deleted: fi.Deleted, - RawInvalid: fi.IsInvalid(), Version: version, Sequence: fi.Sequence, Encrypted: encryptedFI, } + if fi.IsInvalid() { + enc.LocalFlags = FlagLocalRemoteInvalid + } if typ == FileInfoTypeFile { enc.Size = offset // new total file size enc.Blocks = blocks diff --git a/lib/scanner/walk.go b/lib/scanner/walk.go index a0bb03503..ebdf65c9f 100644 --- a/lib/scanner/walk.go +++ b/lib/scanner/walk.go @@ -54,7 +54,7 @@ type Config struct { // events are emitted. Negative number means disabled. ProgressTickIntervalS int // Local flags to set on scanned files - LocalFlags uint32 + LocalFlags protocol.FlagLocal // Modification time is to be considered unchanged if the difference is lower. ModTimeWindow time.Duration // Event logger to which the scan progress events are sent diff --git a/lib/scanner/walk_test.go b/lib/scanner/walk_test.go index c644edaaa..820d0988c 100644 --- a/lib/scanner/walk_test.go +++ b/lib/scanner/walk_test.go @@ -567,7 +567,7 @@ func TestScanOwnershipWindows(t *testing.T) { } } -func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.Matcher, localFlags uint32) []protocol.FileInfo { +func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.Matcher, localFlags protocol.FlagLocal) []protocol.FileInfo { cfg, cancel := testConfig() defer cancel() cfg.Filesystem = fs