Compare commits

...
Author SHA1 Message Date
Jakob Borg b74df18a4a Translation update 2015-04-10 13:32:23 +02:00
Jakob Borg 5cd2906a39 Fix NICKS and authors in index.html 2015-04-10 12:57:43 +02:00
Jakob Borg bc37b69d17 Add ARM to GUI architectures, and fallback for unknowns 2015-04-10 12:45:53 +02:00
Francois-Xavier Gsell 94f6e400ad fix '~' completion in add folder build assets (fix #1478) 2015-04-10 15:42:52 +08:00
Francois-Xavier Gsell b95a6ccf80 fix '~' completion in add folder (fix #1478) 2015-04-10 15:42:52 +08:00
Jakob Borg 7df9c1b6e4 Merge pull request #1621 from Zillode/fix-no-upgrade
Fix compilation of -noupgrade builds
2015-04-10 08:30:50 +02:00
Lode Hoste 75348c0158 Fix compilation of -noupgrade builds 2015-04-09 22:44:46 +02:00
Audrius Butkevicius 75fb14acaf Fix integration tests 2015-04-09 16:16:39 +01:00
Audrius Butkevicius 5350315b68 Merge pull request #1614 from calmh/new-short-id
Index reset should generate file conflicts (fixes #1613)
2015-04-09 13:48:37 +01:00
Audrius Butkevicius 658e39c270 Merge pull request #1618 from calmh/id-conflict
Check for short ID conflict at startup
2015-04-09 13:40:32 +01:00
Audrius Butkevicius ef7ce6c7e1 Merge pull request #1619 from calmh/gui-version
GUI version string includes OS and Arch
2015-04-09 12:29:58 +01:00
Jakob Borg 509e2411bf Merge pull request #1616 from syncthing/rates
Fix total transfer rates (fixes #1615)
2015-04-09 13:08:49 +02:00
Jakob Borg 65c906f951 Merge pull request #1617 from syncthing/integ
Try capturing panics
2015-04-09 13:07:46 +02:00
Audrius Butkevicius 1f159e8233 Fix total transfer rates (fixes #1615) 2015-04-09 12:07:21 +01:00
Jakob Borg 936c76119d Index reset should generate file conflicts (fixes #1613) 2015-04-09 13:06:09 +02:00
Jakob Borg f45865606a Add initial merge and reset conflict tests 2015-04-09 13:06:09 +02:00
Jakob Borg cfc9776bae Check for short ID conflict at startup 2015-04-09 13:06:00 +02:00
Audrius Butkevicius 0cb7ed9e4e Try capturing panics 2015-04-09 11:49:02 +01:00
Jakob Borg 4b07609458 GUI version string includes OS and Arch
(Useful when debugging via screenshots...)
2015-04-09 11:33:24 +02:00
Audrius Butkevicius e41e58e781 Merge pull request #1608 from calmh/xdr-update
Update XDR dependency (fixes #1606)
2015-04-08 14:33:53 +01:00
Jakob Borg f5030f1c2c Update XDR dependency (fixes #1606) 2015-04-08 14:49:29 +02:00
Jakob Borg 2a48fb8e87 Merge pull request #1607 from syncthing/deadlock
Don't run deadlock detection in release mode unless asked to (fixes #1536)
2015-04-08 14:48:20 +02:00
Audrius Butkevicius df6dbc5fa4 Only run deadlock detection if asked or non-release/beta (fixes #1536) 2015-04-08 13:40:05 +01:00
Jakob Borg 4b1d2839e8 Correct override PATH in test 2015-04-08 14:23:26 +02:00
Audrius Butkevicius a892f80e86 Merge pull request #1590 from calmh/long-filenames
Handle long filenames on Windows (fixes #1295)
2015-04-08 13:12:25 +01:00
Jakob Borg b2a79855ae Handle long filenames on Windows (fixes #1295) 2015-04-08 14:05:39 +02:00
Audrius Butkevicius ff4974178a Merge pull request #1605 from calmh/http-trace
Add HTTP request tracing
2015-04-07 21:10:51 +01:00
Jakob Borg d7100fd9bc Add HTTP request tracing 2015-04-07 21:52:47 +02:00
Jakob Borg 0bfb40ae51 discourse -> forum 2015-04-07 16:07:16 +02:00
Jakob Borg 11c83670d6 Merge pull request #1601 from syncthing/conns
Fix GUI
2015-04-07 15:35:54 +02:00
Audrius Butkevicius 68ff4f3842 Fix GUI 2015-04-07 14:24:34 +01:00
Jakob Borg ab25cd09ed Merge pull request #1600 from syncthing/conns
Change /rest/system/connections output (fixes #1487)
2015-04-07 14:29:40 +02:00
Audrius Butkevicius 8f05b8f982 Change /rest/system/connections output (fixes #1487) 2015-04-07 13:21:03 +01:00
Jakob Borg 63ae2f64cf Woops: /rest/system/errors -> /rest/system/error 2015-04-07 13:46:39 +02:00
Jakob Borg 105103fae0 Woops: /rest/system/report -> /rest/svc/report 2015-04-07 13:33:37 +02:00
39 changed files with 774 additions and 190 deletions
+1
View File
@@ -21,6 +21,7 @@ Emil Hessman <emil@hessman.se>
Federico Castagnini <federico.castagnini@gmail.com>
Felix Ableitner <me@nutomic.com>
Felix Unterpaintner <bigbear2nd@gmail.com>
Francois-Xavier Gsell <fxgsell@gmail.com>
Gilli Sigurdsson <gilli@vx.is>
Jakob Borg <jakob@nym.se>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
+2 -2
View File
@@ -19,7 +19,7 @@
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "bccf335c34c01760bdc89f98c952fcda696e27d2"
"Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
},
{
"ImportPath": "github.com/juju/ratelimit",
@@ -31,7 +31,7 @@
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "6277c0595c18d42e9db75dfe900463ef093a82d2"
"Rev": "e7db2648034fb71b051902a02bc25d4468ed492e"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
+2 -2
View File
@@ -67,7 +67,7 @@ func BenchmarkThisEncode(b *testing.B) {
func BenchmarkThisEncoder(b *testing.B) {
w := xdr.NewWriter(ioutil.Discard)
for i := 0; i < b.N; i++ {
_, err := s.encodeXDR(w)
_, err := s.EncodeXDRInto(w)
if err != nil {
b.Fatal(err)
}
@@ -108,7 +108,7 @@ func BenchmarkThisDecoder(b *testing.B) {
r := xdr.NewReader(rr)
var t XDRBenchStruct
for i := 0; i < b.N; i++ {
err := t.decodeXDR(r)
err := t.DecodeXDRFrom(r)
if err != nil {
b.Fatal(err)
}
+15 -13
View File
@@ -26,7 +26,9 @@ XDRBenchStruct Structure:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0000 | I3 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| uint8 |
/ /
\ uint8 Structure \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Bs0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -69,7 +71,7 @@ struct XDRBenchStruct {
func (o XDRBenchStruct) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.encodeXDR(xw)
return o.EncodeXDRInto(xw)
}
func (o XDRBenchStruct) MarshalXDR() ([]byte, error) {
@@ -87,11 +89,11 @@ func (o XDRBenchStruct) MustMarshalXDR() []byte {
func (o XDRBenchStruct) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.encodeXDR(xw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o XDRBenchStruct) encodeXDR(xw *xdr.Writer) (int, error) {
func (o XDRBenchStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteUint64(o.I1)
xw.WriteUint32(o.I2)
xw.WriteUint16(o.I3)
@@ -111,16 +113,16 @@ func (o XDRBenchStruct) encodeXDR(xw *xdr.Writer) (int, error) {
func (o *XDRBenchStruct) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.decodeXDR(xr)
return o.DecodeXDRFrom(xr)
}
func (o *XDRBenchStruct) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.decodeXDR(xr)
return o.DecodeXDRFrom(xr)
}
func (o *XDRBenchStruct) decodeXDR(xr *xdr.Reader) error {
func (o *XDRBenchStruct) DecodeXDRFrom(xr *xdr.Reader) error {
o.I1 = xr.ReadUint64()
o.I2 = xr.ReadUint32()
o.I3 = xr.ReadUint16()
@@ -155,7 +157,7 @@ struct repeatReader {
func (o repeatReader) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.encodeXDR(xw)
return o.EncodeXDRInto(xw)
}
func (o repeatReader) MarshalXDR() ([]byte, error) {
@@ -173,27 +175,27 @@ func (o repeatReader) MustMarshalXDR() []byte {
func (o repeatReader) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.encodeXDR(xw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o repeatReader) encodeXDR(xw *xdr.Writer) (int, error) {
func (o repeatReader) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteBytes(o.data)
return xw.Tot(), xw.Error()
}
func (o *repeatReader) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.decodeXDR(xr)
return o.DecodeXDRFrom(xr)
}
func (o *repeatReader) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.decodeXDR(xr)
return o.DecodeXDRFrom(xr)
}
func (o *repeatReader) decodeXDR(xr *xdr.Reader) error {
func (o *repeatReader) DecodeXDRFrom(xr *xdr.Reader) error {
o.data = xr.ReadBytes()
return xr.Error()
}
+3
View File
@@ -143,6 +143,9 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
{{end}}
{{else}}
_{{$fieldInfo.Name}}Size := int(xr.ReadUint32())
if _{{$fieldInfo.Name}}Size < 0 {
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
}
{{if ge $fieldInfo.Max 1}}
if _{{$fieldInfo.Name}}Size > {{$fieldInfo.Max}} {
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
+2 -2
View File
@@ -32,11 +32,11 @@ type TestStruct struct {
type Opaque [32]byte
func (u *Opaque) encodeXDR(w *xdr.Writer) (int, error) {
func (u *Opaque) EncodeXDRInto(w *xdr.Writer) (int, error) {
return w.WriteRaw(u[:])
}
func (u *Opaque) decodeXDR(r *xdr.Reader) (int, error) {
func (u *Opaque) DecodeXDRFrom(r *xdr.Reader) (int, error) {
return r.ReadRaw(u[:])
}
+27 -16
View File
@@ -18,17 +18,23 @@ TestStruct Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| int |
/ /
\ int Structure \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| int8 |
/ /
\ int8 Structure \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| uint8 |
/ /
\ uint8 Structure \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| int16 |
| 0x0000 | I16 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0000 | UI16 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| int32 |
| I32 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| UI32 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -52,7 +58,9 @@ TestStruct Structure:
\ S (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Opaque |
/ /
\ Opaque Structure \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of SS |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -68,9 +76,9 @@ struct TestStruct {
int I;
int8 I8;
uint8 UI8;
int16 I16;
int I16;
unsigned int UI16;
int32 I32;
int I32;
unsigned int UI32;
hyper I64;
unsigned hyper UI64;
@@ -84,7 +92,7 @@ struct TestStruct {
func (o TestStruct) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.encodeXDR(xw)
return o.EncodeXDRInto(xw)
}
func (o TestStruct) MarshalXDR() ([]byte, error) {
@@ -102,11 +110,11 @@ func (o TestStruct) MustMarshalXDR() []byte {
func (o TestStruct) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.encodeXDR(xw)
_, err := o.EncodeXDRInto(xw)
return []byte(aw), err
}
func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
func (o TestStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteUint64(uint64(o.I))
xw.WriteUint8(uint8(o.I8))
xw.WriteUint8(o.UI8)
@@ -124,7 +132,7 @@ func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("S", l, 1024)
}
xw.WriteString(o.S)
_, err := o.C.encodeXDR(xw)
_, err := o.C.EncodeXDRInto(xw)
if err != nil {
return xw.Tot(), err
}
@@ -140,16 +148,16 @@ func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
func (o *TestStruct) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.decodeXDR(xr)
return o.DecodeXDRFrom(xr)
}
func (o *TestStruct) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.decodeXDR(xr)
return o.DecodeXDRFrom(xr)
}
func (o *TestStruct) decodeXDR(xr *xdr.Reader) error {
func (o *TestStruct) DecodeXDRFrom(xr *xdr.Reader) error {
o.I = int(xr.ReadUint64())
o.I8 = int8(xr.ReadUint8())
o.UI8 = xr.ReadUint8()
@@ -161,8 +169,11 @@ func (o *TestStruct) decodeXDR(xr *xdr.Reader) error {
o.UI64 = xr.ReadUint64()
o.BS = xr.ReadBytesMax(1024)
o.S = xr.ReadStringMax(1024)
(&o.C).decodeXDR(xr)
(&o.C).DecodeXDRFrom(xr)
_SSSize := int(xr.ReadUint32())
if _SSSize < 0 {
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
}
if _SSSize > 1024 {
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
}
+2 -1
View File
@@ -68,7 +68,8 @@ func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
if r.err != nil {
return nil
}
if max > 0 && l > max {
if l < 0 || max > 0 && l > max {
// l may be negative on 32 bit builds
r.err = ElementSizeExceeded("bytes field", l, max)
return nil
}
+27
View File
@@ -110,12 +110,18 @@ func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.Folder = xr.ReadString()
_FilesSize := int(xr.ReadUint32())
if _FilesSize < 0 {
return xdr.ElementSizeExceeded("Files", _FilesSize, 0)
}
o.Files = make([]FileInfo, _FilesSize)
for i := range o.Files {
(&o.Files[i]).DecodeXDRFrom(xr)
}
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize < 0 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
@@ -236,6 +242,9 @@ func (o *FileInfo) DecodeXDRFrom(xr *xdr.Reader) error {
(&o.Version).DecodeXDRFrom(xr)
o.LocalVersion = int64(xr.ReadUint64())
_BlocksSize := int(xr.ReadUint32())
if _BlocksSize < 0 {
return xdr.ElementSizeExceeded("Blocks", _BlocksSize, 0)
}
o.Blocks = make([]BlockInfo, _BlocksSize)
for i := range o.Blocks {
(&o.Blocks[i]).DecodeXDRFrom(xr)
@@ -442,6 +451,9 @@ func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.Hash = xr.ReadBytesMax(64)
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize < 0 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
@@ -633,11 +645,17 @@ func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.ClientName = xr.ReadStringMax(64)
o.ClientVersion = xr.ReadStringMax(64)
_FoldersSize := int(xr.ReadUint32())
if _FoldersSize < 0 {
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 0)
}
o.Folders = make([]Folder, _FoldersSize)
for i := range o.Folders {
(&o.Folders[i]).DecodeXDRFrom(xr)
}
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize < 0 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
@@ -750,12 +768,18 @@ func (o *Folder) UnmarshalXDR(bs []byte) error {
func (o *Folder) DecodeXDRFrom(xr *xdr.Reader) error {
o.ID = xr.ReadStringMax(64)
_DevicesSize := int(xr.ReadUint32())
if _DevicesSize < 0 {
return xdr.ElementSizeExceeded("Devices", _DevicesSize, 0)
}
o.Devices = make([]Device, _DevicesSize)
for i := range o.Devices {
(&o.Devices[i]).DecodeXDRFrom(xr)
}
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize < 0 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
@@ -862,6 +886,9 @@ func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
o.MaxLocalVersion = int64(xr.ReadUint64())
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize < 0 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
+10
View File
@@ -103,3 +103,13 @@ func (a Vector) Concurrent(b Vector) bool {
comp := a.Compare(b)
return comp == ConcurrentGreater || comp == ConcurrentLesser
}
// Counter returns the current value of the given counter ID.
func (v Vector) Counter(id uint64) uint64 {
for _, c := range v {
if c.ID == id {
return c.Value
}
}
return 0
}
+12
View File
@@ -118,5 +118,17 @@ func TestMerge(t *testing.T) {
t.Errorf("%d: %+v.Merge(%+v) == %+v (expected %+v)", i, tc.a, tc.b, m, tc.m)
}
}
}
func TestCounterValue(t *testing.T) {
v0 := Vector{Counter{42, 1}, Counter{64, 5}}
if v0.Counter(42) != 1 {
t.Error("Counter error, %d != %d", v0.Counter(42), 1)
}
if v0.Counter(64) != 5 {
t.Error("Counter error, %d != %d", v0.Counter(64), 5)
}
if v0.Counter(72) != 0 {
t.Error("Counter error, %d != %d", v0.Counter(72), 0)
}
}
+1
View File
@@ -9,3 +9,4 @@ if [ ! -z "$GOLINTOUT" -o "$?" != 0 ]; then
fi
go test
+1
View File
@@ -50,3 +50,4 @@ tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>
zukoo <fxgsell@gmail.com>
+2 -1
View File
@@ -12,5 +12,6 @@ import (
)
var (
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
debugNet = strings.Contains(os.Getenv("STTRACE"), "net") || os.Getenv("STTRACE") == "all"
debugHTTP = strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all"
)
+31 -2
View File
@@ -18,6 +18,7 @@ import (
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
@@ -180,6 +181,10 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
handler = redirectToHTTPSMiddleware(handler)
}
if debugHTTP {
handler = debugMiddleware(handler)
}
srv := http.Server{
Handler: handler,
ReadTimeout: 10 * time.Second,
@@ -210,6 +215,30 @@ func getPostHandler(get, post http.Handler) http.Handler {
})
}
func debugMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t0 := time.Now()
h.ServeHTTP(w, r)
ms := 1000 * time.Since(t0).Seconds()
// The variable `w` is most likely a *http.response, which we can't do
// much with since it's a non exported type. We can however peek into
// it with reflection to get at the status code and number of bytes
// written.
var status, written int64
if rw := reflect.Indirect(reflect.ValueOf(w)); rw.IsValid() && rw.Kind() == reflect.Struct {
if rf := rw.FieldByName("status"); rf.IsValid() && rf.Kind() == reflect.Int {
status = rf.Int()
}
if rf := rw.FieldByName("written"); rf.IsValid() && rf.Kind() == reflect.Int64 {
written = rf.Int()
}
}
l.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
})
}
func redirectToHTTPSMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add a generous access-control-allow-origin header since we may be
@@ -652,7 +681,7 @@ func restGetSystemUpgrade(w http.ResponseWriter, r *http.Request) {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return
}
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(Version)
if err != nil {
http.Error(w, err.Error(), 500)
return
@@ -694,7 +723,7 @@ func restGetLang(w http.ResponseWriter, r *http.Request) {
}
func restPostSystemUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(Version)
if err != nil {
l.Warnln("getting latest release:", err)
http.Error(w, err.Error(), 500)
+34 -3
View File
@@ -50,6 +50,7 @@ var (
BuildHost = "unknown"
BuildUser = "unknown"
IsRelease bool
IsBeta bool
LongVersion string
)
@@ -80,6 +81,7 @@ func init() {
// Check for a clean release build.
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-beta[\d\.]+)?$`)
IsRelease = exp.MatchString(Version)
IsBeta = strings.Contains(Version, "beta")
stamp, _ := strconv.Atoi(BuildStamp)
BuildDate = time.Unix(int64(stamp), 0)
@@ -143,6 +145,7 @@ are mostly useful for developers. Use with care.
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "http" (the main package; HTTP requests)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
@@ -322,7 +325,7 @@ func main() {
}
if doUpgrade || doUpgradeCheck {
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(Version)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
@@ -432,6 +435,10 @@ func syncthingMain() {
cfg.Save()
}
if err := checkShortIDs(cfg); err != nil {
l.Fatalln("Short device IDs are in conflict. Unlucky!\n Regenerate the device ID of one if the following:\n ", err)
}
if len(profiler) > 0 {
go func() {
l.Debugln("Starting profiler on", profiler)
@@ -509,6 +516,15 @@ func syncthingMain() {
m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb)
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
it, err := strconv.Atoi(t)
if err == nil {
m.StartDeadlockDetector(time.Duration(it) * time.Second)
}
} else if !IsRelease || IsBeta {
m.StartDeadlockDetector(20 * 60 * time.Second)
}
// GUI
setupGUI(cfg, m)
@@ -669,7 +685,7 @@ func defaultConfig(myName string) config.Configuration {
newCfg.Folders = []config.FolderConfiguration{
{
ID: "default",
Path: locations[locDefFolder],
RawPath: locations[locDefFolder],
RescanIntervalS: 60,
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
},
@@ -949,7 +965,7 @@ func autoUpgrade() {
case <-timer.C:
}
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(Version)
if err == upgrade.ErrUpgradeUnsupported {
events.Default.Unsubscribe(sub)
return
@@ -1019,3 +1035,18 @@ func cleanConfigDirectory() {
}
}
}
// checkShortIDs verifies that the configuration won't result in duplicate
// short ID:s; that is, that the devices in the cluster all have unique
// initial 64 bits.
func checkShortIDs(cfg *config.Wrapper) error {
exists := make(map[uint64]protocol.DeviceID)
for deviceID := range cfg.Devices() {
shortID := deviceID.Short()
if otherID, ok := exists[shortID]; ok {
return fmt.Errorf("%v in conflict with %v", deviceID, otherID)
}
exists[shortID] = deviceID
}
return nil
}
+28 -4
View File
@@ -21,8 +21,8 @@ import (
func TestFolderErrors(t *testing.T) {
fcfg := config.FolderConfiguration{
ID: "folder",
Path: "testdata/testfolder",
ID: "folder",
RawPath: "testdata/testfolder",
}
cfg := config.Wrap("/tmp/test", config.Configuration{
Folders: []config.FolderConfiguration{fcfg},
@@ -62,7 +62,7 @@ func TestFolderErrors(t *testing.T) {
// Case 2 - new folder, marker created
fcfg.Path = "testdata/"
fcfg.RawPath = "testdata/"
cfg = config.Wrap("/tmp/test", config.Configuration{
Folders: []config.FolderConfiguration{fcfg},
})
@@ -110,7 +110,7 @@ func TestFolderErrors(t *testing.T) {
os.Remove("testdata/testfolder/.stfolder")
os.Remove("testdata/testfolder/")
fcfg.Path = "testdata/testfolder"
fcfg.RawPath = "testdata/testfolder"
cfg = config.Wrap("testdata/subfolder", config.Configuration{
Folders: []config.FolderConfiguration{fcfg},
})
@@ -140,3 +140,27 @@ func TestFolderErrors(t *testing.T) {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
}
}
func TestShortIDCheck(t *testing.T) {
cfg := config.Wrap("/tmp/test", config.Configuration{
Devices: []config.DeviceConfiguration{
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
},
})
if err := checkShortIDs(cfg); err != nil {
t.Error("Unexpected error:", err)
}
cfg = config.Wrap("/tmp/test", config.Configuration{
Devices: []config.DeviceConfiguration{
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 0}},
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 64, 1}}, // first 64 bits same
},
})
if err := checkShortIDs(cfg); err == nil {
t.Error("Should have gotten an error")
}
}
+5 -5
View File
@@ -9,7 +9,7 @@
"Addresses": "Adresy",
"All Data": "Všechna data",
"Allow Anonymous Usage Reporting?": "Povolit anonymní hlášení o používání?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Verzování obstarává externí příkaz. Musí odstranit soubor ze sdíleného adresáře.",
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
"Any devices configured on an introducer device will be added to this device as well.": "Jakékoliv přístroje nakonfigurované na zavaděči budou přidány také na tento přístroj.",
"Automatic upgrades": "Automatický upgrade",
@@ -23,7 +23,7 @@
"Connection Error": "Chyba připojení",
"Copied from elsewhere": "Zkopírováno odjinud",
"Copied from original": "Zkopírováno z originálu",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 následující přispěvatelé:",
"Delete": "Smazat",
"Device ID": "ID přístroje",
"Device Identification": "Identifikace přístroje",
@@ -45,8 +45,8 @@
"Error": "Chyba",
"External File Versioning": "Externí verzování souborů",
"File Versioning": "Verze souborů",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Bity označující práva souborů jsou při hledání změn ignorovány. Použít pro souborové systémy FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do verzí označených daty v adresáři .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Soubory jsou chráněny před změnami na ostatních přístrojích, ale změny provedené z tohoto přístroje budou rozeslány na zbytek clusteru.",
"Folder ID": "ID adresáře",
"Folder Master": "Master adresář",
@@ -132,7 +132,7 @@
"Syncthing is restarting.": "Syncthing se restartuje.",
"Syncthing is upgrading.": "Syncthing se aktualizuje.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing se zdá být nefunkční, nebo je problém s připojením k Internetu. Opakuji...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing má nejspíše problém s provedením vašeho požadavku. Pokud problém přetrvává, obnovte stránku v prohlížeči nebo restartujte Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Souhrnné statistiky jsou veřejně dostupné na {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurace byla uložena, ale není aktivována. Pro aktivaci nové konfigurace je třeba restartovat Syncthing.",
"The device ID cannot be blank.": "ID přístroje nemůže být prázdné.",
+9 -9
View File
@@ -9,7 +9,7 @@
"Addresses": "Adressen",
"All Data": "Alle Daten",
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem Ordner entfernen.",
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteiler eingetragen sind, werden auch bei diesem Gerät eingetragen",
"Automatic upgrades": "automatische Updates",
@@ -17,13 +17,13 @@
"CPU Utilization": "Prozessorauslastung",
"Changelog": "Versionsinfo",
"Close": "Schließen",
"Command": "Command",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
"Compression": "Komprimierung",
"Connection Error": "Verbindungsfehler",
"Copied from elsewhere": "Von woanders kopiert",
"Copied from original": "Vom Original kopiert",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 die folgenden Unterstützer:",
"Delete": "Löschen",
"Device ID": "Geräte ID",
"Device Identification": "Gerät Identifikation",
@@ -43,10 +43,10 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Trage durch ein Komma getrennte \"IP:Port\" Adressen oder \"dynamic\" ein um automatische Adresserkennung durchzuführen.",
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
"Error": "Fehler",
"External File Versioning": "External File Versioning",
"External File Versioning": "Externe Dateiversionierung",
"File Versioning": "Dateiversionierung",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in einen Ordner namens .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt, auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Folder ID": "Verzeichnis ID",
"Folder Master": "Keine Veränderungen zulassen",
@@ -132,14 +132,14 @@
"Syncthing is restarting.": "Syncthing wird neu gestartet",
"Syncthing is upgrading.": "Syncthing wird aktualisiert",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Es scheint als ob Syncthing ein Problem mit der Verarbeitung ihrer Eingabe hat. Bitte laden sie die Seite neu oder führen sie einen Neustart von Syncthing durch, falls das Problem weiterhin besteht.",
"The aggregated statistics are publicly available at {%url%}.": "Die gesammelten Statistiken sind öffentlich verfügbar unter {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu aktivieren.",
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird benutzt um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Die eingegebene Geräte ID scheint nicht gültig zu sein. Es sollte eine 52 oder 56 stellige Zeichenkette aus Buchstaben und Nummern sein. Leerzeichen und Bindestriche sind optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Der erste Kommandozeilenparameter ist der Verzeichnis-Pfad und der zweite Parameter ist der relative Pfad in diesem Ordner.",
"The folder ID cannot be blank.": "Die Verzeichnis ID darf nicht leer sein.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Kennung (64 Zeichen oder weniger) sein. Sie kann nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
"The folder ID must be unique.": "Die Verzeichnis ID muss eindeutig sein.",
@@ -149,7 +149,7 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
"The path cannot be blank.": "The path cannot be blank.",
"The path cannot be blank.": "Der Pfad darf nicht leer sein",
"The rescan interval must be a non-negative number of seconds.": "Das Suchintervall muss eine nicht negative Anzahl von Sekunden sein.",
"Unknown": "Unbekannt",
"Unshared": "Ungeteilt",
+5 -4
View File
@@ -293,11 +293,11 @@
<tbody>
<tr>
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Download Rate</span></th>
<td class="text-right">{{connections['total'].inbps | binary}}B/s ({{connections['total'].inBytesTotal | binary}}B)</td>
<td class="text-right">{{connectionsTotal.inbps | binary}}B/s ({{connectionsTotal.inBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-cloud-upload"></span>&emsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections['total'].outbps | binary}}B/s ({{connections['total'].outBytesTotal | binary}}B)</td>
<td class="text-right">{{connectionsTotal.outbps | binary}}B/s ({{connectionsTotal.outBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-th"></span>&emsp;<span translate>RAM Utilization</span></th>
@@ -326,7 +326,7 @@
</tr>
<tr>
<th><span class="glyphicon glyphicon-tag"></span>&emsp;<span translate>Version</span></th>
<td class="text-right">{{version}}</td>
<td class="text-right">{{versionString()}}</td>
</tr>
</tbody>
</table>
@@ -417,7 +417,7 @@
<div class="container">
<ul class="nav navbar-nav">
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/wiki" target="_blank"><span class="glyphicon glyphicon-book"></span>&ensp;<span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://discourse.syncthing.net" target="_blank"><span class="glyphicon glyphicon-question-sign"></span>&ensp;<span translate>Support</span></a></li>
<li><a class="navbar-link" href="https://forum.syncthing.net" target="_blank"><span class="glyphicon glyphicon-question-sign"></span>&ensp;<span translate>Support</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases" target="_blank"><span class="glyphicon glyphicon-info-sign"></span>&ensp;<span translate>Changelog</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues" target="_blank"><span class="glyphicon glyphicon-warning-sign"></span>&ensp;<span translate>Bugs</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing" target="_blank"><span class="glyphicon glyphicon-wrench"></span>&ensp;<span translate>Source Code</span></a></li>
@@ -973,6 +973,7 @@
<li class="auto-generated">Federico Castagnini</li>
<li class="auto-generated">Felix Ableitner</li>
<li class="auto-generated">Felix Unterpaintner</li>
<li class="auto-generated">Francois-Xavier Gsell</li>
<li class="auto-generated">Gilli Sigurdsson</li>
<li class="auto-generated">Jakob Borg</li>
<li class="auto-generated">James Patterson</li>
@@ -39,6 +39,7 @@ angular.module('syncthing.core')
$scope.deviceStats = {};
$scope.folderStats = {};
$scope.progress = {};
$scope.version = {};
$(window).bind('beforeunload', function () {
navigatingAway = true;
@@ -75,10 +76,10 @@ angular.module('syncthing.core')
refreshFolderStats();
$http.get(urlbase + '/system/version').success(function (data) {
$scope.version = data.version;
$scope.version = data;
}).error($scope.emitHTTPError);
$http.get(urlbase + '/system/report').success(function (data) {
$http.get(urlbase + '/svc/report').success(function (data) {
$scope.reportData = data;
}).error($scope.emitHTTPError);
@@ -367,6 +368,17 @@ angular.module('syncthing.core')
id;
prevDate = now;
try {
data.total.inbps = Math.max(0, (data.total.inBytesTotal - $scope.connectionsTotal.inBytesTotal) / td);
data.total.outbps = Math.max(0, (data.total.outBytesTotal - $scope.connectionsTotal.outBytesTotal) / td);
} catch (e) {
data.total.inbps = 0;
data.total.outbps = 0;
}
$scope.connectionsTotal = data.total;
data = data.connections;
for (id in data) {
if (!data.hasOwnProperty(id)) {
continue;
@@ -385,7 +397,7 @@ angular.module('syncthing.core')
}
function refreshErrors() {
$http.get(urlbase + '/system/errors').success(function (data) {
$http.get(urlbase + '/system/error').success(function (data) {
$scope.errors = data.errors;
console.log("refreshErrors", data);
}).error($scope.emitHTTPError);
@@ -889,6 +901,9 @@ angular.module('syncthing.core')
$scope.directoryList = [];
$scope.$watch('currentFolder.path', function (newvalue) {
if (newvalue && newvalue.trim().charAt(0) == '~') {
$scope.currentFolder.path = $scope.system.tilde + newvalue.trim().substring(1)
}
$http.get(urlbase + '/system/browse', {
params: { current: newvalue }
}).success(function (data) {
@@ -1201,6 +1216,31 @@ angular.module('syncthing.core')
}).error($scope.emitHTTPError);
};
$scope.versionString = function () {
if (!$scope.version.version) {
return '';
}
var os = {
'darwin': 'Mac OS X',
'dragonfly': 'DragonFly BSD',
'freebsd': 'FreeBSD',
'openbsd': 'OpenBSD',
'netbsd': 'NetBSD',
'linux': 'Linux',
'windows': 'Windows',
'solaris': 'Solaris',
}[$scope.version.os] || $scope.version.os;
var arch ={
'386': '32 bit',
'amd64': '64 bit',
'arm': 'ARM',
}[$scope.version.arch] || $scope.version.arch;
return $scope.version.version + ', ' + os + ' (' + arch + ')';
};
// pseudo main. called on all definitions assigned
initController();
});
@@ -8,7 +8,7 @@ angular.module('syncthing.core')
// we shouldn't validate
ctrl.$setValidity('validDeviceid', true);
} else {
$http.get(urlbase + '/deviceid?id=' + viewValue).success(function (resp) {
$http.get(urlbase + '/svc/deviceid?id=' + viewValue).success(function (resp) {
if (resp.error) {
ctrl.$setValidity('validDeviceid', false);
} else {
@@ -28,7 +28,7 @@ angular.module('syncthing.core')
function readBrowserLocales() {
// @TODO: check if there is nice way to utilize window.navigator.languages or similiar api.
return $http.get(urlbase + "/lang");
return $http.get(urlbase + "/svc/lang");
}
function autoConfigLocale() {
@@ -84,7 +84,7 @@ angular.module('syncthing.core')
function useLocale(language, save2Storage) {
if (language) {
$translate.use(language).then(function () {
if (save2Storage && typeof(localStorage) != 'undefined')
if (save2Storage && typeof(localStorage) != 'undefined')
localStorage[_SYNLANG] = language;
});
}
File diff suppressed because one or more lines are too long
+34 -5
View File
@@ -15,6 +15,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
@@ -70,7 +71,7 @@ func (orig Configuration) Copy() Configuration {
type FolderConfiguration struct {
ID string `xml:"id,attr" json:"id"`
Path string `xml:"path,attr" json:"path"`
RawPath string `xml:"path,attr" json:"path"`
Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
ReadOnly bool `xml:"ro,attr" json:"readOnly"`
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
@@ -94,9 +95,37 @@ func (orig FolderConfiguration) Copy() FolderConfiguration {
return c
}
func (f FolderConfiguration) Path() string {
// This is intentionally not a pointer method, because things like
// cfg.Folders["default"].Path() should be valid.
// Attempt tilde expansion; leave unchanged in case of error
if path, err := osutil.ExpandTilde(f.RawPath); err == nil {
f.RawPath = path
}
// Attempt absolutification; leave unchanged in case of error
if !filepath.IsAbs(f.RawPath) {
// Abs() looks like a fairly expensive syscall on Windows, while
// IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
// somewhat faster in the general case, hence the outer if...
if path, err := filepath.Abs(f.RawPath); err == nil {
f.RawPath = path
}
}
// Attempt to enable long filename support on Windows. We may still not
// have an absolute path here if the previous steps failed.
if runtime.GOOS == "windows" && filepath.IsAbs(f.RawPath) && !strings.HasPrefix(f.RawPath, `\\`) {
return `\\?\` + f.RawPath
}
return f.RawPath
}
func (f *FolderConfiguration) CreateMarker() error {
if !f.HasMarker() {
marker := filepath.Join(f.Path, ".stfolder")
marker := filepath.Join(f.Path(), ".stfolder")
fd, err := os.Create(marker)
if err != nil {
return err
@@ -109,7 +138,7 @@ func (f *FolderConfiguration) CreateMarker() error {
}
func (f *FolderConfiguration) HasMarker() bool {
_, err := os.Stat(filepath.Join(f.Path, ".stfolder"))
_, err := os.Stat(filepath.Join(f.Path(), ".stfolder"))
if err != nil {
return false
}
@@ -285,7 +314,7 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
for i := range cfg.Folders {
folder := &cfg.Folders[i]
if len(folder.Path) == 0 {
if len(folder.RawPath) == 0 {
folder.Invalid = "no directory configured"
continue
}
@@ -296,7 +325,7 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
folder.Path = filepath.Dir(folder.Path + string(filepath.Separator))
folder.RawPath = filepath.Dir(folder.RawPath + string(filepath.Separator))
if folder.ID == "" {
folder.ID = "default"
+53 -6
View File
@@ -11,8 +11,10 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/syncthing/protocol"
@@ -78,7 +80,7 @@ func TestDeviceConfig(t *testing.T) {
expectedFolders := []FolderConfiguration{
{
ID: "test",
Path: "testdata",
RawPath: "testdata",
Devices: []FolderDeviceConfiguration{{DeviceID: device1}, {DeviceID: device4}},
ReadOnly: true,
RescanIntervalS: 600,
@@ -297,10 +299,10 @@ func TestVersioningConfig(t *testing.T) {
func TestIssue1262(t *testing.T) {
cfg, err := Load("testdata/issue-1262.xml", device4)
if err != nil {
t.Error(err)
t.Fatal(err)
}
actual := cfg.Folders()["test"].Path
actual := cfg.Folders()["test"].RawPath
expected := "e:"
if runtime.GOOS == "windows" {
expected = `e:\`
@@ -311,6 +313,51 @@ func TestIssue1262(t *testing.T) {
}
}
func TestWindowsPaths(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("Not useful on non-Windows")
return
}
folder := FolderConfiguration{
RawPath: `e:\`,
}
expected := `\\?\e:\`
actual := folder.Path()
if actual != expected {
t.Errorf("%q != %q", actual, expected)
}
folder.RawPath = `\\192.0.2.22\network\share`
expected = folder.RawPath
actual = folder.Path()
if actual != expected {
t.Errorf("%q != %q", actual, expected)
}
folder.RawPath = `relative\path`
expected = folder.RawPath
actual = folder.Path()
if actual != expected {
t.Errorf("%q != %q", actual, expected)
}
}
func TestFolderPath(t *testing.T) {
folder := FolderConfiguration{
RawPath: "~/tmp",
}
realPath := folder.Path()
if !filepath.IsAbs(realPath) {
t.Error(realPath, "should be absolute")
}
if strings.Contains(realPath, "~") {
t.Error(realPath, "should not contain ~")
}
}
func TestNewSaveLoad(t *testing.T) {
path := "testdata/temp.xml"
os.Remove(path)
@@ -391,8 +438,8 @@ func TestRequiresRestart(t *testing.T) {
newCfg = cfg
newCfg.Folders = append(newCfg.Folders, FolderConfiguration{
ID: "t1",
Path: "t1",
ID: "t1",
RawPath: "t1",
})
if !ChangeRequiresRestart(cfg, newCfg) {
t.Error("Adding a folder requires restart")
@@ -411,7 +458,7 @@ func TestRequiresRestart(t *testing.T) {
if ChangeRequiresRestart(cfg, newCfg) {
t.Error("No changes done yet")
}
newCfg.Folders[0].Path = "different"
newCfg.Folders[0].RawPath = "different"
if !ChangeRequiresRestart(cfg, newCfg) {
t.Error("Changing a folder requires restart")
}
-6
View File
@@ -159,12 +159,6 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
if w.folderMap == nil {
w.folderMap = make(map[string]FolderConfiguration, len(w.cfg.Folders))
for _, fld := range w.cfg.Folders {
path, err := osutil.ExpandTilde(fld.Path)
if err != nil {
l.Warnln("home:", err)
continue
}
fld.Path = path
w.folderMap[fld.ID] = fld
}
}
+3
View File
@@ -156,6 +156,9 @@ func (o *versionList) UnmarshalXDR(bs []byte) error {
func (o *versionList) DecodeXDRFrom(xr *xdr.Reader) error {
_versionsSize := int(xr.ReadUint32())
if _versionsSize < 0 {
return xdr.ElementSizeExceeded("versions", _versionsSize, 0)
}
o.versions = make([]fileVersion, _versionsSize)
for i := range o.versions {
(&o.versions[i]).DecodeXDRFrom(xr)
+6
View File
@@ -172,6 +172,9 @@ func (o *Announce) DecodeXDRFrom(xr *xdr.Reader) error {
o.Magic = xr.ReadUint32()
(&o.This).DecodeXDRFrom(xr)
_ExtraSize := int(xr.ReadUint32())
if _ExtraSize < 0 {
return xdr.ElementSizeExceeded("Extra", _ExtraSize, 16)
}
if _ExtraSize > 16 {
return xdr.ElementSizeExceeded("Extra", _ExtraSize, 16)
}
@@ -266,6 +269,9 @@ func (o *Device) UnmarshalXDR(bs []byte) error {
func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32)
_AddressesSize := int(xr.ReadUint32())
if _AddressesSize < 0 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
}
if _AddressesSize > 16 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 16)
}
+26 -25
View File
@@ -17,7 +17,6 @@ import (
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
@@ -118,18 +117,17 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
go m.progressEmitter.Serve()
}
var timeout = 20 * 60 // seconds
if t := os.Getenv("STDEADLOCKTIMEOUT"); len(t) > 0 {
it, err := strconv.Atoi(t)
if err == nil {
timeout = it
}
}
deadlockDetect(&m.fmut, time.Duration(timeout)*time.Second)
deadlockDetect(&m.pmut, time.Duration(timeout)*time.Second)
return m
}
// Starts deadlock detector on the models locks which causes panics in case
// the locks cannot be acquired in the given timeout period.
func (m *Model) StartDeadlockDetector(timeout time.Duration) {
l.Infof("Starting deadlock detector with %v timeout", timeout)
deadlockDetect(&m.fmut, timeout)
deadlockDetect(&m.pmut, timeout)
}
// StartRW starts read/write processing on the current model. When in
// read/write mode the model will attempt to keep in sync with the cluster by
// pulling needed files from peer devices.
@@ -144,7 +142,7 @@ func (m *Model) StartFolderRW(folder string) {
if ok {
panic("cannot start already running folder " + folder)
}
p := newRWFolder(m, cfg)
p := newRWFolder(m, m.shortID, cfg)
m.folderRunners[folder] = p
m.fmut.Unlock()
@@ -153,7 +151,7 @@ func (m *Model) StartFolderRW(folder string) {
if !ok {
l.Fatalf("Requested versioning type %q that does not exist", cfg.Versioning.Type)
}
p.versioner = factory(folder, cfg.Path, cfg.Versioning.Params)
p.versioner = factory(folder, cfg.Path(), cfg.Versioning.Params)
}
if cfg.LenientMtimes {
@@ -201,7 +199,7 @@ func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
}
// ConnectionStats returns a map with connection statistics for each connected device.
func (m *Model) ConnectionStats() map[string]ConnectionInfo {
func (m *Model) ConnectionStats() map[string]interface{} {
type remoteAddrer interface {
RemoteAddr() net.Addr
}
@@ -209,7 +207,8 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
m.pmut.RLock()
m.fmut.RLock()
var res = make(map[string]ConnectionInfo)
var res = make(map[string]interface{})
conns := make(map[string]ConnectionInfo, len(m.protoConn))
for device, conn := range m.protoConn {
ci := ConnectionInfo{
Statistics: conn.Statistics(),
@@ -219,9 +218,11 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
ci.Address = nc.RemoteAddr().String()
}
res[device.String()] = ci
conns[device.String()] = ci
}
res["connections"] = conns
m.fmut.RUnlock()
m.pmut.RUnlock()
@@ -727,7 +728,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
}
m.fmut.RLock()
fn := filepath.Join(m.folderCfgs[folder].Path, name)
fn := filepath.Join(m.folderCfgs[folder].Path(), name)
m.fmut.RUnlock()
var reader io.ReaderAt
@@ -810,7 +811,7 @@ func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
return lines, nil, fmt.Errorf("Folder %s does not exist", folder)
}
fd, err := os.Open(filepath.Join(cfg.Path, ".stignore"))
fd, err := os.Open(filepath.Join(cfg.Path(), ".stignore"))
if err != nil {
if os.IsNotExist(err) {
return lines, nil, nil
@@ -841,7 +842,7 @@ func (m *Model) SetIgnores(folder string, content []string) error {
return fmt.Errorf("Folder %s does not exist", folder)
}
fd, err := ioutil.TempFile(cfg.Path, ".syncthing.stignore-"+folder)
fd, err := ioutil.TempFile(cfg.Path(), ".syncthing.stignore-"+folder)
if err != nil {
l.Warnln("Saving .stignore:", err)
return err
@@ -862,7 +863,7 @@ func (m *Model) SetIgnores(folder string, content []string) error {
return err
}
file := filepath.Join(cfg.Path, ".stignore")
file := filepath.Join(cfg.Path(), ".stignore")
err = osutil.Rename(fd.Name(), file)
if err != nil {
l.Warnln("Saving .stignore:", err)
@@ -1073,7 +1074,7 @@ func (m *Model) AddFolder(cfg config.FolderConfiguration) {
}
ignores := ignore.New(m.cfg.Options().CacheIgnoredFiles)
_ = ignores.Load(filepath.Join(cfg.Path, ".stignore")) // Ignore error, there might not be an .stignore
_ = ignores.Load(filepath.Join(cfg.Path(), ".stignore")) // Ignore error, there might not be an .stignore
m.folderIgnores[cfg.ID] = ignores
m.addedFolder = true
@@ -1141,7 +1142,7 @@ func (m *Model) ScanFolderSubs(folder string, subs []string) error {
return errors.New("no such folder")
}
_ = ignores.Load(filepath.Join(folderCfg.Path, ".stignore")) // Ignore error, there might not be an .stignore
_ = ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")) // Ignore error, there might not be an .stignore
// Required to make sure that we start indexing at a directory we're already
// aware off.
@@ -1167,7 +1168,7 @@ nextSub:
subs = unifySubs
w := &scanner.Walker{
Dir: folderCfg.Path,
Dir: folderCfg.Path(),
Subs: subs,
Matcher: ignores,
BlockSize: protocol.BlockSize,
@@ -1265,7 +1266,7 @@ nextSub:
"size": f.Size(),
})
batch = append(batch, nf)
} else if _, err := os.Lstat(filepath.Join(folderCfg.Path, f.Name)); err != nil {
} else if _, err := os.Lstat(filepath.Join(folderCfg.Path(), f.Name)); err != nil {
// File has been deleted.
// We don't specifically verify that the error is
@@ -1530,7 +1531,7 @@ func (m *Model) CheckFolderHealth(id string) error {
return errors.New("Folder does not exist")
}
fi, err := os.Stat(folder.Path)
fi, err := os.Stat(folder.Path())
if m.CurrentLocalVersion(id) > 0 {
// Safety check. If the cached index contains files but the
// folder doesn't exist, we have a problem. We would assume
@@ -1544,7 +1545,7 @@ func (m *Model) CheckFolderHealth(id string) error {
} else if os.IsNotExist(err) {
// If we don't have any files in the index, and the directory
// doesn't exist, try creating it.
err = os.MkdirAll(folder.Path, 0700)
err = os.MkdirAll(folder.Path(), 0700)
if err == nil {
err = folder.CreateMarker()
}
+15 -15
View File
@@ -35,8 +35,8 @@ func init() {
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
defaultFolderConfig = config.FolderConfiguration{
ID: "default",
Path: "testdata",
ID: "default",
RawPath: "testdata",
Devices: []config.FolderDeviceConfiguration{
{
DeviceID: device1,
@@ -540,7 +540,7 @@ func TestIgnores(t *testing.T) {
t.Error("No error")
}
m.AddFolder(config.FolderConfiguration{ID: "fresh", Path: "XXX"})
m.AddFolder(config.FolderConfiguration{ID: "fresh", RawPath: "XXX"})
ignores, _, err = m.GetIgnores("fresh")
if err != nil {
t.Error(err)
@@ -596,7 +596,7 @@ func TestROScanRecovery(t *testing.T) {
fcfg := config.FolderConfiguration{
ID: "default",
Path: "testdata/rotestfolder",
RawPath: "testdata/rotestfolder",
RescanIntervalS: 1,
}
cfg := config.Wrap("/tmp/test", config.Configuration{
@@ -608,7 +608,7 @@ func TestROScanRecovery(t *testing.T) {
},
})
os.RemoveAll(fcfg.Path)
os.RemoveAll(fcfg.RawPath)
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
@@ -633,14 +633,14 @@ func TestROScanRecovery(t *testing.T) {
return
}
os.Mkdir(fcfg.Path, 0700)
os.Mkdir(fcfg.RawPath, 0700)
if err := waitFor("Folder marker missing"); err != nil {
t.Error(err)
return
}
fd, err := os.Create(filepath.Join(fcfg.Path, ".stfolder"))
fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
if err != nil {
t.Error(err)
return
@@ -652,14 +652,14 @@ func TestROScanRecovery(t *testing.T) {
return
}
os.Remove(filepath.Join(fcfg.Path, ".stfolder"))
os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
if err := waitFor("Folder marker missing"); err != nil {
t.Error(err)
return
}
os.Remove(fcfg.Path)
os.Remove(fcfg.RawPath)
if err := waitFor("Folder path missing"); err != nil {
t.Error(err)
@@ -676,7 +676,7 @@ func TestRWScanRecovery(t *testing.T) {
fcfg := config.FolderConfiguration{
ID: "default",
Path: "testdata/rwtestfolder",
RawPath: "testdata/rwtestfolder",
RescanIntervalS: 1,
}
cfg := config.Wrap("/tmp/test", config.Configuration{
@@ -688,7 +688,7 @@ func TestRWScanRecovery(t *testing.T) {
},
})
os.RemoveAll(fcfg.Path)
os.RemoveAll(fcfg.RawPath)
m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
@@ -713,14 +713,14 @@ func TestRWScanRecovery(t *testing.T) {
return
}
os.Mkdir(fcfg.Path, 0700)
os.Mkdir(fcfg.RawPath, 0700)
if err := waitFor("Folder marker missing"); err != nil {
t.Error(err)
return
}
fd, err := os.Create(filepath.Join(fcfg.Path, ".stfolder"))
fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
if err != nil {
t.Error(err)
return
@@ -732,14 +732,14 @@ func TestRWScanRecovery(t *testing.T) {
return
}
os.Remove(filepath.Join(fcfg.Path, ".stfolder"))
os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
if err := waitFor("Folder marker missing"); err != nil {
t.Error(err)
return
}
os.Remove(fcfg.Path)
os.Remove(fcfg.RawPath)
if err := waitFor("Folder path missing"); err != nil {
t.Error(err)
+36 -7
View File
@@ -68,13 +68,14 @@ type rwFolder struct {
lenientMtimes bool
copiers int
pullers int
shortID uint64
stop chan struct{}
queue *jobQueue
dbUpdates chan protocol.FileInfo
}
func newRWFolder(m *Model, cfg config.FolderConfiguration) *rwFolder {
func newRWFolder(m *Model, shortID uint64, cfg config.FolderConfiguration) *rwFolder {
return &rwFolder{
stateTracker: stateTracker{folder: cfg.ID},
@@ -82,12 +83,13 @@ func newRWFolder(m *Model, cfg config.FolderConfiguration) *rwFolder {
progressEmitter: m.progressEmitter,
folder: cfg.ID,
dir: cfg.Path,
dir: cfg.Path(),
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
ignorePerms: cfg.IgnorePerms,
lenientMtimes: cfg.LenientMtimes,
copiers: cfg.Copiers,
pullers: cfg.Pullers,
shortID: shortID,
stop: make(chan struct{}),
queue: newJobQueue(),
@@ -603,8 +605,11 @@ func (p *rwFolder) deleteFile(file protocol.FileInfo) {
realName := filepath.Join(p.dir, file.Name)
cur, ok := p.model.CurrentFolderFile(p.folder, file.Name)
if ok && cur.Version.Concurrent(file.Version) {
// There is a conflict here. Move the file to a conflict copy instead of deleting.
if ok && p.inConflict(cur.Version, file.Version) {
// There is a conflict here. Move the file to a conflict copy instead
// of deleting. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
file.Version = file.Version.Merge(cur.Version)
err = osutil.InWritableDir(moveForConflict, realName)
} else if p.versioner != nil {
err = osutil.InWritableDir(p.versioner.Archive, realName)
@@ -816,6 +821,12 @@ func (p *rwFolder) shortcutFile(file protocol.FileInfo) (err error) {
}
}
// This may have been a conflict. We should merge the version vectors so
// that our clock doesn't move backwards.
if cur, ok := p.model.CurrentFolderFile(p.folder, file.Name); ok {
file.Version = file.Version.Merge(cur.Version)
}
p.dbUpdates <- file
return
}
@@ -852,7 +863,7 @@ func (p *rwFolder) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pull
folderRoots := make(map[string]string)
p.model.fmut.RLock()
for folder, cfg := range p.model.folderCfgs {
folderRoots[folder] = cfg.Path
folderRoots[folder] = cfg.Path()
}
p.model.fmut.RUnlock()
@@ -1011,10 +1022,12 @@ func (p *rwFolder) performFinish(state *sharedPullerState) {
}
}
if state.version.Concurrent(state.file.Version) {
if p.inConflict(state.version, state.file.Version) {
// The new file has been changed in conflict with the existing one. We
// should file it away as a conflict instead of just removing or
// archiving.
// archiving. Also merge with the version vector we had, to indicate
// we have resolved the conflict.
state.file.Version = state.file.Version.Merge(state.version)
err = osutil.InWritableDir(moveForConflict, state.realName)
} else if p.versioner != nil {
// If we should use versioning, let the versioner archive the old
@@ -1144,6 +1157,22 @@ loop:
}
}
func (p *rwFolder) inConflict(current, replacement protocol.Vector) bool {
if current.Concurrent(replacement) {
// Obvious case
return true
}
if replacement.Counter(p.shortID) > current.Counter(p.shortID) {
// The replacement file contains a higher version for ourselves than
// what we have. This isn't supposed to be possible, since it's only
// we who can increment that counter. We take it as a sign that
// something is wrong (our index may have been corrupted or removed)
// and flag it as a conflict.
return true
}
return false
}
func invalidateFolder(cfg *config.Configuration, folderID string, err error) {
for i := range cfg.Folders {
folder := &cfg.Folders[i]
+12 -7
View File
@@ -27,21 +27,21 @@ import (
"strings"
)
// Returns the latest release, including prereleases or not depending on the argument
func LatestGithubRelease(version string) (Release, error) {
resp, err := http.Get("https://api.github.com/repos/syncthing/syncthing/releases?per_page=10")
// Returns the latest releases, including prereleases or not depending on the argument
func LatestGithubReleases(version string) ([]Release, error) {
resp, err := http.Get("https://api.github.com/repos/syncthing/syncthing/releases?per_page=30")
if err != nil {
return Release{}, err
return nil, err
}
if resp.StatusCode > 299 {
return Release{}, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
return nil, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
}
var rels []Release
json.NewDecoder(resp.Body).Decode(&rels)
resp.Body.Close()
return LatestRelease(version, rels)
return rels, nil
}
type SortByRelease []Release
@@ -56,7 +56,12 @@ func (s SortByRelease) Less(i, j int) bool {
return CompareVersions(s[i].Tag, s[j].Tag) > 0
}
func LatestRelease(version string, rels []Release) (Release, error) {
func LatestRelease(version string) (Release, error) {
rels, _ := LatestGithubReleases(version)
return SelectLatestRelease(version, rels)
}
func SelectLatestRelease(version string, rels []Release) (Release, error) {
if len(rels) == 0 {
return Release{}, ErrVersionUnknown
}
+11 -2
View File
@@ -4,6 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !noupgrade
package upgrade
import (
@@ -65,7 +67,7 @@ var upgrades = map[string]string{
"v0.11.0-beta0+40-g53cb66e-dirty": "v0.11.0-beta0",
}
func TestRelease(t *testing.T) {
func TestGithubRelease(t *testing.T) {
fd, err := os.Open("testdata/github-releases.json")
if err != nil {
t.Errorf("Missing github-release test data")
@@ -76,7 +78,7 @@ func TestRelease(t *testing.T) {
json.NewDecoder(fd).Decode(&rels)
for old, target := range upgrades {
upgrade, err := LatestRelease(old, rels)
upgrade, err := SelectLatestRelease(old, rels)
if err != nil {
t.Error("Error retrieving latest version", err)
}
@@ -85,3 +87,10 @@ func TestRelease(t *testing.T) {
}
}
}
func TestErrorRelease(t *testing.T) {
_, err := SelectLatestRelease("v0.11.0-beta", nil)
if err == nil {
t.Error("Should return an error when no release were available")
}
}
+1 -1
View File
@@ -16,6 +16,6 @@ func upgradeToURL(binary, url string) error {
return ErrUpgradeUnsupported
}
func LatestRelease(prerelease bool) (Release, error) {
func LatestRelease(version string) (Release, error) {
return Release{}, ErrUpgradeUnsupported
}
+290 -36
View File
@@ -9,6 +9,7 @@
package integration
import (
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -47,43 +48,8 @@ func TestConflict(t *testing.T) {
t.Fatal(err)
}
log.Println("Starting sender...")
sender := syncthingProcess{ // id1
instance: "1",
argv: []string{"-home", "h1"},
port: 8081,
apiKey: apiKey,
}
err = sender.start()
if err != nil {
t.Fatal(err)
}
sender, receiver := coSenderReceiver(t)
defer sender.stop()
// Wait for one scan to succeed, or up to 20 seconds... This is to let
// startup, UPnP etc complete and make sure the sender has the full index
// before they connect.
for i := 0; i < 20; i++ {
err := sender.rescan("default")
if err != nil {
time.Sleep(time.Second)
continue
}
break
}
log.Println("Starting receiver...")
receiver := syncthingProcess{ // id2
instance: "2",
argv: []string{"-home", "h2"},
port: 8082,
apiKey: apiKey,
}
err = receiver.start()
if err != nil {
sender.stop()
t.Fatal(err)
}
defer receiver.stop()
if err = coCompletion(sender, receiver); err != nil {
@@ -213,6 +179,294 @@ func TestConflict(t *testing.T) {
}
}
func TestInitialMergeConflicts(t *testing.T) {
log.Println("Cleaning...")
err := removeAll("s1", "s2", "h1/index*", "h2/index*")
if err != nil {
t.Fatal(err)
}
err = os.Mkdir("s1", 0755)
if err != nil {
t.Fatal(err)
}
err = os.Mkdir("s2", 0755)
if err != nil {
t.Fatal(err)
}
// File 1 is a conflict
err = ioutil.WriteFile("s1/file1", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile("s2/file1", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
// File 2 exists on s1 only
err = ioutil.WriteFile("s1/file2", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
// File 3 exists on s2 only
err = ioutil.WriteFile("s2/file3", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
// Let them sync
sender, receiver := coSenderReceiver(t)
defer sender.stop()
defer receiver.stop()
log.Println("Syncing...")
if err = coCompletion(sender, receiver); err != nil {
t.Fatal(err)
}
sender.stop()
receiver.stop()
log.Println("Verifying...")
// s1 should have three-four files (there's a conflict from s2 which may or may not have synced yet)
files, err := filepath.Glob("s1/file*")
if err != nil {
t.Fatal(err)
}
if len(files) < 3 || len(files) > 4 {
t.Errorf("Expected 3-4 files in s1 instead of %d", len(files))
}
// s2 should have four files (there's a conflict)
files, err = filepath.Glob("s2/file*")
if err != nil {
t.Fatal(err)
}
if len(files) != 4 {
t.Errorf("Expected 4 files in s2 instead of %d", len(files))
}
// file1 is in conflict, so there's two versions of that one
files, err = filepath.Glob("s2/file1*")
if err != nil {
t.Fatal(err)
}
if len(files) != 2 {
t.Errorf("Expected 2 'file1' files in s2 instead of %d", len(files))
}
}
func TestResetConflicts(t *testing.T) {
log.Println("Cleaning...")
err := removeAll("s1", "s2", "h1/index*", "h2/index*")
if err != nil {
t.Fatal(err)
}
err = os.Mkdir("s1", 0755)
if err != nil {
t.Fatal(err)
}
err = os.Mkdir("s2", 0755)
if err != nil {
t.Fatal(err)
}
// Three files on s1
err = ioutil.WriteFile("s1/file1", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile("s1/file2", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile("s2/file3", []byte("hello\n"), 0644)
if err != nil {
t.Fatal(err)
}
// Let them sync
sender, receiver := coSenderReceiver(t)
defer sender.stop()
defer receiver.stop()
log.Println("Syncing...")
if err = coCompletion(sender, receiver); err != nil {
t.Fatal(err)
}
log.Println("Verifying...")
// s1 should have three files
files, err := filepath.Glob("s1/file*")
if err != nil {
t.Fatal(err)
}
if len(files) != 3 {
t.Errorf("Expected 3 files in s1 instead of %d", len(files))
}
// s2 should have three
files, err = filepath.Glob("s2/file*")
if err != nil {
t.Fatal(err)
}
if len(files) != 3 {
t.Errorf("Expected 3 files in s2 instead of %d", len(files))
}
log.Println("Updating...")
// change s2/file2 a few times, so that it's version counter increases.
// This will make the file on the cluster look newer than what we have
// locally after we rest the index, unless we have a fix for that.
err = ioutil.WriteFile("s2/file2", []byte("hello1\n"), 0644)
if err != nil {
t.Fatal(err)
}
err = receiver.rescan("default")
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
err = ioutil.WriteFile("s2/file2", []byte("hello2\n"), 0644)
if err != nil {
t.Fatal(err)
}
err = receiver.rescan("default")
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
err = ioutil.WriteFile("s2/file2", []byte("hello3\n"), 0644)
if err != nil {
t.Fatal(err)
}
err = receiver.rescan("default")
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Second)
if err = coCompletion(sender, receiver); err != nil {
t.Fatal(err)
}
// Now nuke the index
log.Println("Resetting...")
receiver.stop()
removeAll("h2/index*")
// s1/file1 (remote) changes while receiver is down
err = ioutil.WriteFile("s1/file1", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
// s1 must know about it
err = sender.rescan("default")
if err != nil {
t.Fatal(err)
}
// s2/file2 (local) changes while receiver is down
err = ioutil.WriteFile("s2/file2", []byte("goodbye\n"), 0644)
if err != nil {
t.Fatal(err)
}
receiver.start()
log.Println("Syncing...")
if err = coCompletion(sender, receiver); err != nil {
t.Fatal(err)
}
// s2 should have five files (three plus two conflicts)
files, err = filepath.Glob("s2/file*")
if err != nil {
t.Fatal(err)
}
if len(files) != 5 {
t.Errorf("Expected 5 files in s2 instead of %d", len(files))
}
// file1 is in conflict, so there's two versions of that one
files, err = filepath.Glob("s2/file1*")
if err != nil {
t.Fatal(err)
}
if len(files) != 2 {
t.Errorf("Expected 2 'file1' files in s2 instead of %d", len(files))
}
// file2 is in conflict, so there's two versions of that one
files, err = filepath.Glob("s2/file2*")
if err != nil {
t.Fatal(err)
}
if len(files) != 2 {
t.Errorf("Expected 2 'file2' files in s2 instead of %d", len(files))
}
}
func coSenderReceiver(t *testing.T) (syncthingProcess, syncthingProcess) {
log.Println("Starting sender...")
sender := syncthingProcess{ // id1
instance: "1",
argv: []string{"-home", "h1"},
port: 8081,
apiKey: apiKey,
}
err := sender.start()
if err != nil {
t.Fatal(err)
}
log.Println("Starting receiver...")
receiver := syncthingProcess{ // id2
instance: "2",
argv: []string{"-home", "h2"},
port: 8082,
apiKey: apiKey,
}
err = receiver.start()
if err != nil {
sender.stop()
t.Fatal(err)
}
return sender, receiver
}
func coCompletion(p ...syncthingProcess) error {
mainLoop:
for {
+1 -1
View File
@@ -234,7 +234,7 @@ func TestPOSTWithoutCSRF(t *testing.T) {
// Should succeed with CSRF
req, err = http.NewRequest("POST", "http://127.0.0.1:8082/rest/error/clear", nil)
req, err = http.NewRequest("POST", "http://127.0.0.1:8082/rest/system/error/clear", nil)
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -148,7 +148,7 @@ func TestOverride(t *testing.T) {
log.Println("Hitting Override on master...")
resp, err := master.post("/rest/model/override?folder=default", nil)
resp, err := master.post("/rest/db/override?folder=default", nil)
if err != nil {
t.Fatal(err)
}
+13 -1
View File
@@ -118,11 +118,18 @@ func (p *syncthingProcess) stop() error {
raceConditionStart := []byte("WARNING: DATA RACE")
raceConditionSep := []byte("==================")
panicConditionStart := []byte("panic:")
panicConditionSep := []byte(p.id.String()[:5])
sc := bufio.NewScanner(fd)
race := false
_panic := false
for sc.Scan() {
line := sc.Bytes()
if race {
if race || _panic {
if bytes.Contains(line, panicConditionSep) {
_panic = false
continue
}
fmt.Printf("%s\n", line)
if bytes.Contains(line, raceConditionSep) {
race = false
@@ -134,6 +141,11 @@ func (p *syncthingProcess) stop() error {
if err == nil {
err = errors.New("Race condition detected")
}
} else if bytes.Contains(line, panicConditionStart) {
_panic = true
if err == nil {
err = errors.New("Panic detected")
}
}
}
return err