diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index a6e0e956a..49c20e4d2 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -109,7 +109,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro // The GET handlers getRestMux := http.NewServeMux() getRestMux.HandleFunc("/rest/db/completion", withModel(m, restGetDBCompletion)) // device folder - getRestMux.HandleFunc("/rest/db/file", withModel(m, restGetDBFile)) // folder file [blocks] + getRestMux.HandleFunc("/rest/db/file", withModel(m, restGetDBFile)) // folder file getRestMux.HandleFunc("/rest/db/ignores", withModel(m, restGetDBIgnores)) // folder getRestMux.HandleFunc("/rest/db/need", withModel(m, restGetDBNeed)) // folder getRestMux.HandleFunc("/rest/db/status", withModel(m, restGetDBStatus)) // folder @@ -384,7 +384,7 @@ func restGetDBNeed(m *model.Model, w http.ResponseWriter, r *http.Request) { progress, queued, rest := m.NeedFolderFiles(folder, 100) // Convert the struct to a more loose structure, and inject the size. - output := map[string][]map[string]interface{}{ + output := map[string][]jsonDBFileInfo{ "progress": toNeedSlice(progress), "queued": toNeedSlice(queued), "rest": toNeedSlice(rest), @@ -416,19 +416,13 @@ func restGetDBFile(m *model.Model, w http.ResponseWriter, r *http.Request) { qs := r.URL.Query() folder := qs.Get("folder") file := qs.Get("file") - withBlocks := qs.Get("blocks") != "" gf, _ := m.CurrentGlobalFile(folder, file) lf, _ := m.CurrentFolderFile(folder, file) - if !withBlocks { - gf.Blocks = nil - lf.Blocks = nil - } - av := m.Availability(folder, file) json.NewEncoder(w).Encode(map[string]interface{}{ - "global": gf, - "local": lf, + "global": jsonFileInfo(gf), + "local": jsonFileInfo(lf), "availability": av, }) } @@ -910,17 +904,49 @@ func mimeTypeForFile(file string) string { } } -func toNeedSlice(fs []db.FileInfoTruncated) []map[string]interface{} { - output := make([]map[string]interface{}, len(fs)) - for i, file := range fs { - output[i] = map[string]interface{}{ - "name": file.Name, - "flags": file.Flags, - "modified": file.Modified, - "version": file.Version, - "localVersion": file.LocalVersion, - "size": file.Size(), - } +func toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo { + res := make([]jsonDBFileInfo, len(fs)) + for i, f := range fs { + res[i] = jsonDBFileInfo(f) } - return output + return res +} + +// Type wrappers for nice JSON serialization + +type jsonFileInfo protocol.FileInfo + +func (f jsonFileInfo) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "name": f.Name, + "size": protocol.FileInfo(f).Size(), + "flags": fmt.Sprintf("%#o", f.Flags), + "modified": time.Unix(f.Modified, 0), + "localVersion": f.LocalVersion, + "numBlocks": len(f.Blocks), + "version": jsonVersionVector(f.Version), + }) +} + +type jsonDBFileInfo db.FileInfoTruncated + +func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "name": f.Name, + "size": db.FileInfoTruncated(f).Size(), + "flags": fmt.Sprintf("%#o", f.Flags), + "modified": time.Unix(f.Modified, 0), + "localVersion": f.LocalVersion, + "version": jsonVersionVector(f.Version), + }) +} + +type jsonVersionVector protocol.Vector + +func (v jsonVersionVector) MarshalJSON() ([]byte, error) { + res := make([]string, len(v)) + for i, c := range v { + res[i] = fmt.Sprintf("%d:%d", c.ID, c.Value) + } + return json.Marshal(res) } diff --git a/internal/model/model.go b/internal/model/model.go index 01577e75e..c149c27ef 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -1482,8 +1482,8 @@ func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly } if !dirsonly && base != "" { - last[base] = []int64{ - f.Modified, f.Size(), + last[base] = []interface{}{ + time.Unix(f.Modified, 0), f.Size(), } } diff --git a/internal/model/model_test.go b/internal/model/model_test.go index 7a1071559..0e85ae0aa 100644 --- a/internal/model/model_test.go +++ b/internal/model/model_test.go @@ -14,7 +14,6 @@ import ( "math/rand" "os" "path/filepath" - "reflect" "strconv" "testing" "time" @@ -775,7 +774,7 @@ func TestGlobalDirectoryTree(t *testing.T) { } } - filedata := []int64{0x666, 0xa} + filedata := []interface{}{time.Unix(0x666, 0), 0xa} testdata := []protocol.FileInfo{ b(false, "another"), @@ -847,13 +846,13 @@ func TestGlobalDirectoryTree(t *testing.T) { result := m.GlobalDirectoryTree("default", "", -1, false) - if !reflect.DeepEqual(result, expectedResult) { - t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult)) + if mm(result) != mm(expectedResult) { + t.Errorf("Does not match:\n%#v\n%#v", result, expectedResult) } result = m.GlobalDirectoryTree("default", "another", -1, false) - if !reflect.DeepEqual(result, expectedResult["another"]) { + if mm(result) != mm(expectedResult["another"]) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult["another"])) } @@ -865,7 +864,7 @@ func TestGlobalDirectoryTree(t *testing.T) { "rootfile": filedata, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -886,7 +885,7 @@ func TestGlobalDirectoryTree(t *testing.T) { "rootfile": filedata, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -916,7 +915,7 @@ func TestGlobalDirectoryTree(t *testing.T) { }, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -935,7 +934,7 @@ func TestGlobalDirectoryTree(t *testing.T) { }, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -945,7 +944,7 @@ func TestGlobalDirectoryTree(t *testing.T) { "file": filedata, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -954,7 +953,7 @@ func TestGlobalDirectoryTree(t *testing.T) { "with": map[string]interface{}{}, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -965,7 +964,7 @@ func TestGlobalDirectoryTree(t *testing.T) { }, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -978,7 +977,7 @@ func TestGlobalDirectoryTree(t *testing.T) { }, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -991,7 +990,7 @@ func TestGlobalDirectoryTree(t *testing.T) { }, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -999,7 +998,7 @@ func TestGlobalDirectoryTree(t *testing.T) { result = m.GlobalDirectoryTree("default", "som", -1, false) currentResult = map[string]interface{}{} - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } } @@ -1024,7 +1023,7 @@ func TestGlobalDirectorySelfFixing(t *testing.T) { } } - filedata := []int64{0x666, 0xa} + filedata := []interface{}{time.Unix(0x666, 0).Format(time.RFC3339), 0xa} testdata := []protocol.FileInfo{ b(true, "another", "directory", "afile"), @@ -1105,7 +1104,7 @@ func TestGlobalDirectorySelfFixing(t *testing.T) { result := m.GlobalDirectoryTree("default", "", -1, false) - if !reflect.DeepEqual(result, expectedResult) { + if mm(result) != mm(expectedResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult)) } @@ -1116,7 +1115,7 @@ func TestGlobalDirectorySelfFixing(t *testing.T) { }, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -1125,7 +1124,7 @@ func TestGlobalDirectorySelfFixing(t *testing.T) { "invalid": map[string]interface{}{}, } - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } @@ -1134,7 +1133,7 @@ func TestGlobalDirectorySelfFixing(t *testing.T) { result = m.GlobalDirectoryTree("default", "xthis", 1, false) currentResult = map[string]interface{}{} - if !reflect.DeepEqual(result, currentResult) { + if mm(result) != mm(currentResult) { t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult)) } }