cmd/syncthing, lib/db, lib/model: Track more detailed file/dirs/links/deleted counts

This commit is contained in:
Jakob Borg 2016-10-17 14:10:17 +02:00
parent b8a90b7eaa
commit 4e8c8d7e2c
8 changed files with 143 additions and 103 deletions

View File

@ -72,7 +72,7 @@ type modelIntf interface {
Completion(device protocol.DeviceID, folder string) model.FolderCompletion Completion(device protocol.DeviceID, folder string) model.FolderCompletion
Override(folder string) Override(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int)
NeedSize(folder string) (nfiles, ndeletes int, bytes int64) NeedSize(folder string) db.Counts
ConnectionStats() map[string]interface{} ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics FolderStatistics() map[string]stats.FolderStatistics
@ -90,8 +90,8 @@ type modelIntf interface {
ScanFolderSubdirs(folder string, subs []string) error ScanFolderSubdirs(folder string, subs []string) error
BringToFront(folder, file string) BringToFront(folder, file string)
ConnectedTo(deviceID protocol.DeviceID) bool ConnectedTo(deviceID protocol.DeviceID) bool
GlobalSize(folder string) (nfiles, deleted int, bytes int64) GlobalSize(folder string) db.Counts
LocalSize(folder string) (nfiles, deleted int, bytes int64) LocalSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool) CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool) RemoteSequence(folder string) (int64, bool)
State(folder string) (string, time.Time, error) State(folder string) (string, time.Time, error)
@ -634,16 +634,16 @@ func folderSummary(cfg configIntf, m modelIntf, folder string) map[string]interf
res["invalid"] = "" // Deprecated, retains external API for now res["invalid"] = "" // Deprecated, retains external API for now
globalFiles, globalDeleted, globalBytes := m.GlobalSize(folder) global := m.GlobalSize(folder)
res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes res["globalFiles"], res["globalDirectories"], res["globalSymlinks"], res["globalDeleted"], res["globalBytes"] = global.Files, global.Directories, global.Symlinks, global.Deleted, global.Bytes
localFiles, localDeleted, localBytes := m.LocalSize(folder) local := m.LocalSize(folder)
res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes res["localFiles"], res["localDirectories"], res["localSymlinks"], res["localDeleted"], res["localBytes"] = local.Files, local.Directories, local.Symlinks, local.Deleted, local.Bytes
needFiles, needDeletes, needBytes := m.NeedSize(folder) need := m.NeedSize(folder)
res["needFiles"], res["needDeletes"], res["needBytes"] = needFiles, needDeletes, needBytes res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes res["inSyncFiles"], res["inSyncBytes"] = global.Files-need.Files, global.Bytes-need.Bytes
var err error var err error
res["state"], res["stateChanged"], err = m.State(folder) res["state"], res["stateChanged"], err = m.State(folder)

View File

@ -31,8 +31,8 @@ func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.Fi
return nil, nil, nil, 0 return nil, nil, nil, 0
} }
func (m *mockedModel) NeedSize(folder string) (nfiles, ndeletes int, bytes int64) { func (m *mockedModel) NeedSize(folder string) db.Counts {
return 0, 0, 0 return db.Counts{}
} }
func (m *mockedModel) ConnectionStats() map[string]interface{} { func (m *mockedModel) ConnectionStats() map[string]interface{} {
@ -95,12 +95,12 @@ func (m *mockedModel) ConnectedTo(deviceID protocol.DeviceID) bool {
return false return false
} }
func (m *mockedModel) GlobalSize(folder string) (nfiles, deleted int, bytes int64) { func (m *mockedModel) GlobalSize(folder string) db.Counts {
return 0, 0, 0 return db.Counts{}
} }
func (m *mockedModel) LocalSize(folder string) (nfiles, deleted int, bytes int64) { func (m *mockedModel) LocalSize(folder string) db.Counts {
return 0, 0, 0 return db.Counts{}
} }
func (m *mockedModel) CurrentSequence(folder string) (int64, bool) { func (m *mockedModel) CurrentSequence(folder string) (int64, bool) {

View File

@ -93,14 +93,14 @@ func reportData(cfg configIntf, m modelIntf) map[string]interface{} {
var totFiles, maxFiles int var totFiles, maxFiles int
var totBytes, maxBytes int64 var totBytes, maxBytes int64
for folderID := range cfg.Folders() { for folderID := range cfg.Folders() {
files, _, bytes := m.GlobalSize(folderID) global := m.GlobalSize(folderID)
totFiles += files totFiles += global.Files
totBytes += bytes totBytes += global.Bytes
if files > maxFiles { if global.Files > maxFiles {
maxFiles = files maxFiles = global.Files
} }
if bytes > maxBytes { if global.Bytes > maxBytes {
maxBytes = bytes maxBytes = global.Bytes
} }
} }

View File

@ -310,16 +310,24 @@
</tr> </tr>
<tr> <tr>
<th><span class="fa fa-fw fa-globe"></span>&nbsp;<span translate>Global State</span></th> <th><span class="fa fa-fw fa-globe"></span>&nbsp;<span translate>Global State</span></th>
<td class="text-right">{{model[folder.id].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].globalBytes | binary}}B</td> <td class="text-right">
{{model[folder.id].globalFiles | alwaysNumber}} <span translate>files</span>,
{{model[folder.id].globalDirectories | alwaysNumber}} <span translate>directories</span>,
~{{model[folder.id].globalBytes | binary}}B
</td>
</tr> </tr>
<tr> <tr>
<th><span class="fa fa-fw fa-home"></span>&nbsp;<span translate>Local State</span></th> <th><span class="fa fa-fw fa-home"></span>&nbsp;<span translate>Local State</span></th>
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td> <td class="text-right">
{{model[folder.id].localFiles | alwaysNumber}} <span translate>files</span>,
{{model[folder.id].localDirectories | alwaysNumber}} <span translate>directories</span>,
~{{model[folder.id].localBytes | binary}}B
</td>
</tr> </tr>
<tr ng-if="model[folder.id].needFiles > 0"> <tr ng-if="model[folder.id].needFiles > 0">
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync Items</span></th> <th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right"> <td class="text-right">
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a> <a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needFiles+model[folder.id].needDirectories+model[folder.id].needSymlinks+model[folder.id].needDeletes | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
</td> </td>
</tr> </tr>
<tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0"> <tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0">

View File

@ -51,11 +51,17 @@ type FileIntf interface {
// continue iteration, false to stop. // continue iteration, false to stop.
type Iterator func(f FileIntf) bool type Iterator func(f FileIntf) bool
type Counts struct {
Files int
Directories int
Symlinks int
Deleted int
Bytes int64
}
type sizeTracker struct { type sizeTracker struct {
files int Counts
deleted int mut stdsync.Mutex
bytes int64
mut stdsync.Mutex
} }
func (s *sizeTracker) addFile(f FileIntf) { func (s *sizeTracker) addFile(f FileIntf) {
@ -64,12 +70,17 @@ func (s *sizeTracker) addFile(f FileIntf) {
} }
s.mut.Lock() s.mut.Lock()
if f.IsDeleted() { switch {
s.deleted++ case f.IsDeleted():
} else { s.Deleted++
s.files++ case f.IsDirectory():
s.Directories++
case f.IsSymlink():
s.Symlinks++
default:
s.Files++
} }
s.bytes += f.FileSize() s.Bytes += f.FileSize()
s.mut.Unlock() s.mut.Unlock()
} }
@ -79,22 +90,27 @@ func (s *sizeTracker) removeFile(f FileIntf) {
} }
s.mut.Lock() s.mut.Lock()
if f.IsDeleted() { switch {
s.deleted-- case f.IsDeleted():
} else { s.Deleted--
s.files-- case f.IsDirectory():
s.Directories--
case f.IsSymlink():
s.Symlinks--
default:
s.Files--
} }
s.bytes -= f.FileSize() s.Bytes -= f.FileSize()
if s.deleted < 0 || s.files < 0 { if s.Deleted < 0 || s.Files < 0 || s.Directories < 0 || s.Symlinks < 0 {
panic("bug: removed more than added") panic("bug: removed more than added")
} }
s.mut.Unlock() s.mut.Unlock()
} }
func (s *sizeTracker) Size() (files, deleted int, bytes int64) { func (s *sizeTracker) Size() Counts {
s.mut.Lock() s.mut.Lock()
defer s.mut.Unlock() defer s.mut.Unlock()
return s.files, s.deleted, s.bytes return s.Counts
} }
func NewFileSet(folder string, db *Instance) *FileSet { func NewFileSet(folder string, db *Instance) *FileSet {
@ -259,11 +275,11 @@ func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
return s.remoteSequence[device] return s.remoteSequence[device]
} }
func (s *FileSet) LocalSize() (files, deleted int, bytes int64) { func (s *FileSet) LocalSize() Counts {
return s.localSize.Size() return s.localSize.Size()
} }
func (s *FileSet) GlobalSize() (files, deleted int, bytes int64) { func (s *FileSet) GlobalSize() Counts {
return s.globalSize.Size() return s.globalSize.Size()
} }

View File

@ -168,27 +168,33 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Global incorrect;\n A: %v !=\n E: %v", g, expectedGlobal) t.Errorf("Global incorrect;\n A: %v !=\n E: %v", g, expectedGlobal)
} }
globalFiles, globalDeleted, globalBytes := 0, 0, int64(0) globalFiles, globalDirectories, globalDeleted, globalBytes := 0, 0, 0, int64(0)
for _, f := range g { for _, f := range g {
if f.IsInvalid() { if f.IsInvalid() {
continue continue
} }
if f.IsDeleted() { switch {
case f.IsDeleted():
globalDeleted++ globalDeleted++
} else { case f.IsDirectory():
globalDirectories++
default:
globalFiles++ globalFiles++
} }
globalBytes += f.FileSize() globalBytes += f.FileSize()
} }
gsFiles, gsDeleted, gsBytes := m.GlobalSize() gs := m.GlobalSize()
if gsFiles != globalFiles { if gs.Files != globalFiles {
t.Errorf("Incorrect GlobalSize files; %d != %d", gsFiles, globalFiles) t.Errorf("Incorrect GlobalSize files; %d != %d", gs.Files, globalFiles)
} }
if gsDeleted != globalDeleted { if gs.Directories != globalDirectories {
t.Errorf("Incorrect GlobalSize deleted; %d != %d", gsDeleted, globalDeleted) t.Errorf("Incorrect GlobalSize directories; %d != %d", gs.Directories, globalDirectories)
} }
if gsBytes != globalBytes { if gs.Deleted != globalDeleted {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gsBytes, globalBytes) t.Errorf("Incorrect GlobalSize deleted; %d != %d", gs.Deleted, globalDeleted)
}
if gs.Bytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
} }
h := fileList(haveList(m, protocol.LocalDeviceID)) h := fileList(haveList(m, protocol.LocalDeviceID))
@ -198,27 +204,33 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Have incorrect;\n A: %v !=\n E: %v", h, localTot) t.Errorf("Have incorrect;\n A: %v !=\n E: %v", h, localTot)
} }
haveFiles, haveDeleted, haveBytes := 0, 0, int64(0) haveFiles, haveDirectories, haveDeleted, haveBytes := 0, 0, 0, int64(0)
for _, f := range h { for _, f := range h {
if f.IsInvalid() { if f.IsInvalid() {
continue continue
} }
if f.IsDeleted() { switch {
case f.IsDeleted():
haveDeleted++ haveDeleted++
} else { case f.IsDirectory():
haveDirectories++
default:
haveFiles++ haveFiles++
} }
haveBytes += f.FileSize() haveBytes += f.FileSize()
} }
lsFiles, lsDeleted, lsBytes := m.LocalSize() ls := m.LocalSize()
if lsFiles != haveFiles { if ls.Files != haveFiles {
t.Errorf("Incorrect LocalSize files; %d != %d", lsFiles, haveFiles) t.Errorf("Incorrect LocalSize files; %d != %d", ls.Files, haveFiles)
} }
if lsDeleted != haveDeleted { if ls.Directories != haveDirectories {
t.Errorf("Incorrect LocalSize deleted; %d != %d", lsDeleted, haveDeleted) t.Errorf("Incorrect LocalSize directories; %d != %d", ls.Directories, haveDirectories)
} }
if lsBytes != haveBytes { if ls.Deleted != haveDeleted {
t.Errorf("Incorrect LocalSize bytes; %d != %d", lsBytes, haveBytes) t.Errorf("Incorrect LocalSize deleted; %d != %d", ls.Deleted, haveDeleted)
}
if ls.Bytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
} }
h = fileList(haveList(m, remoteDevice0)) h = fileList(haveList(m, remoteDevice0))

View File

@ -476,7 +476,7 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
return FolderCompletion{} // Folder doesn't exist, so we hardly have any of it return FolderCompletion{} // Folder doesn't exist, so we hardly have any of it
} }
_, _, tot := rf.GlobalSize() tot := rf.GlobalSize().Bytes
if tot == 0 { if tot == 0 {
// Folder is empty, so we have all of it // Folder is empty, so we have all of it
return FolderCompletion{ return FolderCompletion{
@ -531,42 +531,49 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
} }
} }
func sizeOfFile(f db.FileIntf) (files, deleted int, bytes int64) { func addSizeOfFile(s *db.Counts, f db.FileIntf) {
if !f.IsDeleted() { switch {
files++ case f.IsDeleted():
} else { s.Deleted++
deleted++ case f.IsDirectory():
s.Directories++
case f.IsSymlink():
s.Symlinks++
default:
s.Files++
} }
bytes += f.FileSize() s.Bytes += f.FileSize()
return return
} }
// GlobalSize returns the number of files, deleted files and total bytes for all // GlobalSize returns the number of files, deleted files and total bytes for all
// files in the global model. // files in the global model.
func (m *Model) GlobalSize(folder string) (nfiles, deleted int, bytes int64) { func (m *Model) GlobalSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
nfiles, deleted, bytes = rf.GlobalSize() return rf.GlobalSize()
} }
return return db.Counts{}
} }
// LocalSize returns the number of files, deleted files and total bytes for all // LocalSize returns the number of files, deleted files and total bytes for all
// files in the local folder. // files in the local folder.
func (m *Model) LocalSize(folder string) (nfiles, deleted int, bytes int64) { func (m *Model) LocalSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
nfiles, deleted, bytes = rf.LocalSize() return rf.LocalSize()
} }
return return db.Counts{}
} }
// NeedSize returns the number and total size of currently needed files. // NeedSize returns the number and total size of currently needed files.
func (m *Model) NeedSize(folder string) (nfiles, ndeletes int, bytes int64) { func (m *Model) NeedSize(folder string) db.Counts {
m.fmut.RLock() m.fmut.RLock()
defer m.fmut.RUnlock() defer m.fmut.RUnlock()
var result db.Counts
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
ignores := m.folderIgnores[folder] ignores := m.folderIgnores[folder]
cfg := m.folderCfgs[folder] cfg := m.folderCfgs[folder]
@ -575,16 +582,13 @@ func (m *Model) NeedSize(folder string) (nfiles, ndeletes int, bytes int64) {
return true return true
} }
fs, de, by := sizeOfFile(f) addSizeOfFile(&result, f)
nfiles += fs
ndeletes += de
bytes += by
return true return true
}) })
} }
bytes -= m.progressEmitter.BytesCompleted(folder) result.Bytes -= m.progressEmitter.BytesCompleted(folder)
l.Debugf("%v NeedSize(%q): %d %d", m, folder, nfiles, bytes) l.Debugf("%v NeedSize(%q): %v", m, folder, result)
return return result
} }
// NeedFolderFiles returns paginated list of currently needed files in // NeedFolderFiles returns paginated list of currently needed files in

View File

@ -1324,8 +1324,8 @@ func TestIssue3028(t *testing.T) {
// Get a count of how many files are there now // Get a count of how many files are there now
locorigfiles, _, _ := m.LocalSize("default") locorigfiles := m.LocalSize("default").Files
globorigfiles, _, _ := m.GlobalSize("default") globorigfiles := m.GlobalSize("default").Files
// Delete and rescan specifically these two // Delete and rescan specifically these two
@ -1336,19 +1336,19 @@ func TestIssue3028(t *testing.T) {
// Verify that the number of files decreased by two and the number of // Verify that the number of files decreased by two and the number of
// deleted files increases by two // deleted files increases by two
locnowfiles, locdelfiles, _ := m.LocalSize("default") loc := m.LocalSize("default")
globnowfiles, globdelfiles, _ := m.GlobalSize("default") glob := m.GlobalSize("default")
if locnowfiles != locorigfiles-2 { if loc.Files != locorigfiles-2 {
t.Errorf("Incorrect local accounting; got %d current files, expected %d", locnowfiles, locorigfiles-2) t.Errorf("Incorrect local accounting; got %d current files, expected %d", loc.Files, locorigfiles-2)
} }
if globnowfiles != globorigfiles-2 { if glob.Files != globorigfiles-2 {
t.Errorf("Incorrect global accounting; got %d current files, expected %d", globnowfiles, globorigfiles-2) t.Errorf("Incorrect global accounting; got %d current files, expected %d", glob.Files, globorigfiles-2)
} }
if locdelfiles != 2 { if loc.Deleted != 2 {
t.Errorf("Incorrect local accounting; got %d deleted files, expected 2", locdelfiles) t.Errorf("Incorrect local accounting; got %d deleted files, expected 2", loc.Deleted)
} }
if globdelfiles != 2 { if glob.Deleted != 2 {
t.Errorf("Incorrect global accounting; got %d deleted files, expected 2", globdelfiles) t.Errorf("Incorrect global accounting; got %d deleted files, expected 2", glob.Deleted)
} }
} }
@ -1722,14 +1722,14 @@ func TestIssue3496(t *testing.T) {
t.Log(comp) t.Log(comp)
// Check that NeedSize does the correct thing // Check that NeedSize does the correct thing
files, deletes, bytes := m.NeedSize("default") need := m.NeedSize("default")
if files != 1 || bytes != 1234 { if need.Files != 1 || need.Bytes != 1234 {
// The one we added synthetically above // The one we added synthetically above
t.Errorf("Incorrect need size; %d, %d != 1, 1234", files, bytes) t.Errorf("Incorrect need size; %d, %d != 1, 1234", need.Files, need.Bytes)
} }
if deletes != len(localFiles)-1 { if need.Deleted != len(localFiles)-1 {
// The rest // The rest
t.Errorf("Incorrect need deletes; %d != %d", deletes, len(localFiles)-1) t.Errorf("Incorrect need deletes; %d != %d", need.Deleted, len(localFiles)-1)
} }
} }