diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 98a80933..cbf2da76 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -149,6 +149,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro postRestMux.HandleFunc("/rest/shutdown", restPostShutdown) postRestMux.HandleFunc("/rest/upgrade", restPostUpgrade) postRestMux.HandleFunc("/rest/scan", withModel(m, restPostScan)) + postRestMux.HandleFunc("/rest/bump", withModel(m, restPostBump)) // A handler that splits requests between the two above and disables // caching @@ -314,19 +315,12 @@ func restGetNeed(m *model.Model, w http.ResponseWriter, r *http.Request) { var qs = r.URL.Query() var folder = qs.Get("folder") - files := m.NeedFolderFilesLimited(folder, 100) // max 100 files + progress, queued, rest := m.NeedFolderFiles(folder, 100) // Convert the struct to a more loose structure, and inject the size. - output := make([]map[string]interface{}, 0, len(files)) - for _, file := range files { - output = append(output, map[string]interface{}{ - "Name": file.Name, - "Flags": file.Flags, - "Modified": file.Modified, - "Version": file.Version, - "LocalVersion": file.LocalVersion, - "NumBlocks": file.NumBlocks, - "Size": protocol.BlocksToSize(file.NumBlocks), - }) + output := map[string][]map[string]interface{}{ + "progress": toNeedSlice(progress), + "queued": toNeedSlice(queued), + "rest": toNeedSlice(rest), } w.Header().Set("Content-Type", "application/json; charset=utf-8") @@ -650,6 +644,14 @@ func restPostScan(m *model.Model, w http.ResponseWriter, r *http.Request) { } } +func restPostBump(m *model.Model, w http.ResponseWriter, r *http.Request) { + qs := r.URL.Query() + folder := qs.Get("folder") + file := qs.Get("file") + m.BringToFront(folder, file) + restGetNeed(m, w, r) +} + func getQR(w http.ResponseWriter, r *http.Request) { var qs = r.URL.Query() var text = qs.Get("text") @@ -775,3 +777,19 @@ func mimeTypeForFile(file string) string { return mime.TypeByExtension(ext) } } + +func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} { + output := make([]map[string]interface{}, len(files)) + for i, file := range files { + output[i] = map[string]interface{}{ + "Name": file.Name, + "Flags": file.Flags, + "Modified": file.Modified, + "Version": file.Version, + "LocalVersion": file.LocalVersion, + "NumBlocks": file.NumBlocks, + "Size": protocol.BlocksToSize(file.NumBlocks), + } + } + return output +} diff --git a/gui/index.html b/gui/index.html index 890c8da3..b1fb4251 100644 --- a/gui/index.html +++ b/gui/index.html @@ -801,21 +801,37 @@
- + - + + + + + + + + + + +
{{needActions[a]}} {{f.Name | basename}} - -
-
-
-
-
-
- {{progress[neededFolder][f.Name].BytesDone | binary}}B / {{progress[neededFolder][f.Name].BytesTotal | binary}}B -
-
+
+
+
+
+
+
+
+ + {{progress[neededFolder][f.Name].BytesDone | binary}}B / {{progress[neededFolder][f.Name].BytesTotal | binary}}B + +
+ {{f.Size | binary}}B +
{{needActions[a]}}{{f.Name | basename}} + + {{f.Size | binary}}B +
{{needActions[a]}}{{f.Name | basename}}{{f.Size | binary}}B
diff --git a/gui/scripts/syncthing/core/controllers/syncthingController.js b/gui/scripts/syncthing/core/controllers/syncthingController.js index 14142c1f..387030d3 100644 --- a/gui/scripts/syncthing/core/controllers/syncthingController.js +++ b/gui/scripts/syncthing/core/controllers/syncthingController.js @@ -1056,6 +1056,15 @@ angular.module('syncthing.core') $http.post(urlbase + "/scan?folder=" + encodeURIComponent(folder)); }; + $scope.bumpFile = function (folder, file) { + $http.post(urlbase + "/bump?folder=" + encodeURIComponent(folder) + "&file=" + encodeURIComponent(file)).success(function (data) { + if ($scope.neededFolder == folder) { + console.log("bumpFile", folder, data); + $scope.needed = data; + } + }); + }; + // pseudo main. called on all definitions assigned initController(); }); diff --git a/internal/auto/gui.files.go b/internal/auto/gui.files.go index 6f7d1b08..7b5b2ad0 100644 --- a/internal/auto/gui.files.go +++ b/internal/auto/gui.files.go @@ -147,7 +147,7 @@ func Assets() map[string][]byte { bs, _ = ioutil.ReadAll(gr) assets["assets/lang/valid-langs.js"] = bs - bs, _ = base64.StdEncoding.DecodeString("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") + bs, _ = base64.StdEncoding.DecodeString("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") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) assets["index.html"] = bs @@ -167,7 +167,7 @@ func Assets() map[string][]byte { bs, _ = ioutil.ReadAll(gr) assets["scripts/syncthing/core/controllers/eventController.js"] = bs - bs, _ = base64.StdEncoding.DecodeString("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") + bs, _ = base64.StdEncoding.DecodeString("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") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) assets["scripts/syncthing/core/controllers/syncthingController.js"] = bs diff --git a/internal/model/model.go b/internal/model/model.go index c1a64d1e..c17bc366 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -79,6 +79,8 @@ const ( type service interface { Serve() Stop() + Jobs() ([]string, []string) // In progress, Queued + BringToFront(string) } type Model struct { @@ -189,6 +191,7 @@ func (m *Model) StartFolderRW(folder string) { copiers: cfg.Copiers, pullers: cfg.Pullers, finishers: cfg.Finishers, + queue: newJobQueue(), } m.folderRunners[folder] = p m.fmut.Unlock() @@ -416,22 +419,50 @@ func (m *Model) NeedSize(folder string) (files int, bytes int64) { return } -// NeedFiles returns the list of currently needed files, stopping at maxFiles -// files. Limit <= 0 is ignored. -func (m *Model) NeedFolderFilesLimited(folder string, maxFiles int) []protocol.FileInfoTruncated { +// NeedFiles returns the list of currently needed files in progress, queued, +// and to be queued on next puller iteration. Also takes a soft cap which is +// only respected when adding files from the model rather than the runner queue. +func (m *Model) NeedFolderFiles(folder string, max int) ([]protocol.FileInfoTruncated, []protocol.FileInfoTruncated, []protocol.FileInfoTruncated) { defer m.leveldbPanicWorkaround() m.fmut.RLock() defer m.fmut.RUnlock() if rf, ok := m.folderFiles[folder]; ok { - fs := make([]protocol.FileInfoTruncated, 0, maxFiles) - rf.WithNeedTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool { - fs = append(fs, f.(protocol.FileInfoTruncated)) - return maxFiles <= 0 || len(fs) < maxFiles - }) - return fs + var progress, queued, rest []protocol.FileInfoTruncated + var seen map[string]bool + + runner, ok := m.folderRunners[folder] + if ok { + progressNames, queuedNames := runner.Jobs() + + progress = make([]protocol.FileInfoTruncated, len(progressNames)) + queued = make([]protocol.FileInfoTruncated, len(queuedNames)) + seen = make(map[string]bool, len(progressNames)+len(queuedNames)) + + for i, name := range progressNames { + progress[i] = rf.GetGlobal(name).ToTruncated() /// XXX: Should implement GetGlobalTruncated directly + seen[name] = true + } + + for i, name := range queuedNames { + queued[i] = rf.GetGlobal(name).ToTruncated() /// XXX: Should implement GetGlobalTruncated directly + seen[name] = true + } + } + left := max - len(progress) - len(queued) + if max < 1 || left > 0 { + rf.WithNeedTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool { + left-- + ft := f.(protocol.FileInfoTruncated) + if !seen[ft.Name] { + rest = append(rest, ft) + } + return max < 1 || left > 0 + }) + } + return progress, queued, rest } - return nil + return nil, nil, nil } // Index is called when a new device is connected and we receive their full index. @@ -1336,7 +1367,7 @@ func (m *Model) RemoteLocalVersion(folder string) uint64 { return ver } -func (m *Model) availability(folder string, file string) []protocol.DeviceID { +func (m *Model) availability(folder, file string) []protocol.DeviceID { // Acquire this lock first, as the value returned from foldersFiles can // gen heavily modified on Close() m.pmut.RLock() @@ -1359,6 +1390,17 @@ func (m *Model) availability(folder string, file string) []protocol.DeviceID { return availableDevices } +// Bump the given files priority in the job queue +func (m *Model) BringToFront(folder, file string) { + m.pmut.RLock() + defer m.pmut.RUnlock() + + runner, ok := m.folderRunners[folder] + if ok { + runner.BringToFront(file) + } +} + func (m *Model) String() string { return fmt.Sprintf("model@%p", m) } diff --git a/internal/model/puller.go b/internal/model/puller.go index 1d9fea68..e1d063dd 100644 --- a/internal/model/puller.go +++ b/internal/model/puller.go @@ -78,6 +78,7 @@ type Puller struct { copiers int pullers int finishers int + queue *jobQueue } // Serve will run scans and pulls. It will return when Stop()ed or on a @@ -337,15 +338,23 @@ func (p *Puller) pullerIteration(checksum bool, ignores *ignore.Matcher) int { p.handleDir(file) default: // A new or changed file or symlink. This is the only case where we - // do stuff in the background; the other three are done - // synchronously. - p.handleFile(file, copyChan, finisherChan) + // do stuff concurrently in the background + p.queue.Push(file.Name) } changed++ return true }) + for { + fileName, ok := p.queue.Pop() + if !ok { + break + } + f := p.model.CurrentGlobalFile(p.folder, fileName) + p.handleFile(f, copyChan, finisherChan) + } + // Signal copy and puller routines that we are done with the in data for // this iteration. Wait for them to finish. close(copyChan) @@ -483,6 +492,7 @@ func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksSt if debug { l.Debugln(p, "taking shortcut on", file.Name) } + p.queue.Done(file.Name) if file.IsSymlink() { p.shortcutSymlink(curFile, file) } else { @@ -850,6 +860,7 @@ func (p *Puller) finisherRoutine(in <-chan *sharedPullerState) { continue } + p.queue.Done(state.file.Name) p.performFinish(state) p.model.receivedFile(p.folder, state.file.Name) if p.progressEmitter != nil { @@ -859,6 +870,15 @@ func (p *Puller) finisherRoutine(in <-chan *sharedPullerState) { } } +// Moves the given filename to the front of the job queue +func (p *Puller) BringToFront(filename string) { + p.queue.BringToFront(filename) +} + +func (p *Puller) Jobs() ([]string, []string) { + return p.queue.Jobs() +} + func invalidateFolder(cfg *config.Configuration, folderID string, err error) { for i := range cfg.Folders { folder := &cfg.Folders[i] diff --git a/internal/model/queue.go b/internal/model/queue.go new file mode 100644 index 00000000..a4f34dab --- /dev/null +++ b/internal/model/queue.go @@ -0,0 +1,94 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . + +package model + +import "sync" + +type jobQueue struct { + progress []string + queued []string + mut sync.Mutex +} + +func newJobQueue() *jobQueue { + return &jobQueue{} +} + +func (q *jobQueue) Push(file string) { + q.mut.Lock() + q.queued = append(q.queued, file) + q.mut.Unlock() +} + +func (q *jobQueue) Pop() (string, bool) { + q.mut.Lock() + defer q.mut.Unlock() + + if len(q.queued) == 0 { + return "", false + } + + var f string + f = q.queued[0] + q.queued = q.queued[1:] + q.progress = append(q.progress, f) + + return f, true +} + +func (q *jobQueue) BringToFront(filename string) { + q.mut.Lock() + defer q.mut.Unlock() + + for i, cur := range q.queued { + if cur == filename { + if i > 0 { + // Shift the elements before the selected element one step to + // the right, overwriting the selected element + copy(q.queued[1:i+1], q.queued[0:]) + // Put the selected element at the front + q.queued[0] = cur + } + return + } + } +} + +func (q *jobQueue) Done(file string) { + q.mut.Lock() + defer q.mut.Unlock() + + for i := range q.progress { + if q.progress[i] == file { + copy(q.progress[i:], q.progress[i+1:]) + q.progress = q.progress[:len(q.progress)-1] + return + } + } +} + +func (q *jobQueue) Jobs() ([]string, []string) { + q.mut.Lock() + defer q.mut.Unlock() + + progress := make([]string, len(q.progress)) + copy(progress, q.progress) + + queued := make([]string, len(q.queued)) + copy(queued, q.queued) + + return progress, queued +} diff --git a/internal/model/queue_test.go b/internal/model/queue_test.go new file mode 100644 index 00000000..37456644 --- /dev/null +++ b/internal/model/queue_test.go @@ -0,0 +1,200 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . + +package model + +import ( + "fmt" + "reflect" + "testing" +) + +func TestJobQueue(t *testing.T) { + // Some random actions + q := newJobQueue() + q.Push("f1") + q.Push("f2") + q.Push("f3") + q.Push("f4") + + progress, queued := q.Jobs() + if len(progress) != 0 || len(queued) != 4 { + t.Fatal("Wrong length") + } + + for i := 1; i < 5; i++ { + n, ok := q.Pop() + if !ok || n != fmt.Sprintf("f%d", i) { + t.Fatal("Wrong element") + } + progress, queued = q.Jobs() + if len(progress) != 1 || len(queued) != 3 { + t.Log(progress) + t.Log(queued) + t.Fatal("Wrong length") + } + + q.Done(n) + progress, queued = q.Jobs() + if len(progress) != 0 || len(queued) != 3 { + t.Fatal("Wrong length", len(progress), len(queued)) + } + + q.Push(n) + progress, queued = q.Jobs() + if len(progress) != 0 || len(queued) != 4 { + t.Fatal("Wrong length") + } + + q.Done("f5") // Does not exist + progress, queued = q.Jobs() + if len(progress) != 0 || len(queued) != 4 { + t.Fatal("Wrong length") + } + } + + if len(q.progress) > 0 || len(q.queued) != 4 { + t.Fatal("Wrong length") + } + + for i := 4; i > 0; i-- { + progress, queued = q.Jobs() + if len(progress) != 4-i || len(queued) != i { + t.Fatal("Wrong length") + } + + s := fmt.Sprintf("f%d", i) + + q.BringToFront(s) + progress, queued = q.Jobs() + if len(progress) != 4-i || len(queued) != i { + t.Fatal("Wrong length") + } + + n, ok := q.Pop() + if !ok || n != s { + t.Fatal("Wrong element") + } + progress, queued = q.Jobs() + if len(progress) != 5-i || len(queued) != i-1 { + t.Fatal("Wrong length") + } + + q.Done("f5") // Does not exist + progress, queued = q.Jobs() + if len(progress) != 5-i || len(queued) != i-1 { + t.Fatal("Wrong length") + } + } + + _, ok := q.Pop() + if len(q.progress) != 4 || ok { + t.Fatal("Wrong length") + } + + q.Done("f1") + q.Done("f2") + q.Done("f3") + q.Done("f4") + q.Done("f5") // Does not exist + + _, ok = q.Pop() + if len(q.progress) != 0 || ok { + t.Fatal("Wrong length") + } + + progress, queued = q.Jobs() + if len(progress) != 0 || len(queued) != 0 { + t.Fatal("Wrong length") + } + q.BringToFront("") + q.Done("f5") // Does not exist + progress, queued = q.Jobs() + if len(progress) != 0 || len(queued) != 0 { + t.Fatal("Wrong length") + } +} + +func TestBringToFront(t *testing.T) { + q := newJobQueue() + q.Push("f1") + q.Push("f2") + q.Push("f3") + q.Push("f4") + + _, queued := q.Jobs() + if !reflect.DeepEqual(queued, []string{"f1", "f2", "f3", "f4"}) { + t.Errorf("Incorrect order %v at start", queued) + } + + q.BringToFront("f1") // corner case: does nothing + + _, queued = q.Jobs() + if !reflect.DeepEqual(queued, []string{"f1", "f2", "f3", "f4"}) { + t.Errorf("Incorrect order %v", queued) + } + + q.BringToFront("f3") + + _, queued = q.Jobs() + if !reflect.DeepEqual(queued, []string{"f3", "f1", "f2", "f4"}) { + t.Errorf("Incorrect order %v", queued) + } + + q.BringToFront("f2") + + _, queued = q.Jobs() + if !reflect.DeepEqual(queued, []string{"f2", "f3", "f1", "f4"}) { + t.Errorf("Incorrect order %v", queued) + } + + q.BringToFront("f4") // corner case: last element + + _, queued = q.Jobs() + if !reflect.DeepEqual(queued, []string{"f4", "f2", "f3", "f1"}) { + t.Errorf("Incorrect order %v", queued) + } +} + +func BenchmarkJobQueueBump(b *testing.B) { + files := genFiles(b.N) + + q := newJobQueue() + for _, f := range files { + q.Push(f.Name) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + q.BringToFront(files[i].Name) + } +} + +func BenchmarkJobQueuePushPopDone10k(b *testing.B) { + files := genFiles(10000) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + q := newJobQueue() + for _, f := range files { + q.Push(f.Name) + } + for range files { + n, _ := q.Pop() + q.Done(n) + } + } + +} diff --git a/internal/model/scanner.go b/internal/model/scanner.go index 98c1fc2d..c2824b60 100644 --- a/internal/model/scanner.go +++ b/internal/model/scanner.go @@ -75,3 +75,9 @@ func (s *Scanner) Stop() { func (s *Scanner) String() string { return fmt.Sprintf("scanner/%s@%p", s.folder, s) } + +func (s *Scanner) BringToFront(string) {} + +func (s *Scanner) Jobs() ([]string, []string) { + return nil, nil +} diff --git a/internal/protocol/message.go b/internal/protocol/message.go index 8cc191d8..ae04a9da 100644 --- a/internal/protocol/message.go +++ b/internal/protocol/message.go @@ -69,6 +69,17 @@ func (f FileInfo) HasPermissionBits() bool { return f.Flags&FlagNoPermBits == 0 } +func (f FileInfo) ToTruncated() FileInfoTruncated { + return FileInfoTruncated{ + Name: f.Name, + Flags: f.Flags, + Modified: f.Modified, + Version: f.Version, + LocalVersion: f.LocalVersion, + NumBlocks: uint32(len(f.Blocks)), + } +} + // Used for unmarshalling a FileInfo structure but skipping the actual block list type FileInfoTruncated struct { Name string // max:8192