mirror of
https://github.com/syncthing/syncthing.git
synced 2026-02-26 03:29:42 -05:00
Compare commits
29 Commits
v0.9.0-bet
...
v0.9.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ff6cd9105 | ||
|
|
96c17d8292 | ||
|
|
bc6faaffc4 | ||
|
|
51e9839237 | ||
|
|
6115631746 | ||
|
|
ee005fbc8e | ||
|
|
e27d42935c | ||
|
|
9c99d65716 | ||
|
|
5b9469eed3 | ||
|
|
6805ac915b | ||
|
|
7148cf99f7 | ||
|
|
67a3fb8bf2 | ||
|
|
933b61f99f | ||
|
|
6c5c14f35f | ||
|
|
6a441d5013 | ||
|
|
6b46465c77 | ||
|
|
75388caeed | ||
|
|
2546930a1a | ||
|
|
135e29a3bb | ||
|
|
3b65a58f59 | ||
|
|
49cb931572 | ||
|
|
b7176d2204 | ||
|
|
5bf7d372f6 | ||
|
|
073775e461 | ||
|
|
fbf8f3dc68 | ||
|
|
e8c8cc550b | ||
|
|
87c3790fa8 | ||
|
|
0d9dcb2f4f | ||
|
|
6188185b22 |
8
Godeps/Godeps.json
generated
8
Godeps/Godeps.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ImportPath": "github.com/calmh/syncthing",
|
||||
"GoVersion": "devel +6bdbf9086c00 Thu Jul 17 17:02:46 2014 +1000",
|
||||
"GoVersion": "go1.3",
|
||||
"Packages": [
|
||||
"./cmd/..."
|
||||
],
|
||||
@@ -40,6 +40,10 @@
|
||||
"Comment": "null-15",
|
||||
"Rev": "12e4b4183793ac4b061921e7980845e750679fd0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/bkaradzic/go-lz4",
|
||||
"Rev": "77e2ba877bde9da31213bec75dbbe197fa507c21"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/groupcache/lru",
|
||||
"Rev": "8b25adc0f62632c810997cb38c21111a3f256bf4"
|
||||
@@ -50,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||
"Rev": "ba4481e4cb1d45f586e32be2ab663f173b08b207"
|
||||
"Rev": "c5955912e3287376475731c5bc59c79a5a799105"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vitrun/qart/coding",
|
||||
|
||||
1
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/.gitignore
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/lz4-example/lz4-example
|
||||
7
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/.travis.yml
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
24
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/LICENSE
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||
Copyright 2013 Damian Gryski. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
71
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/README.md
generated
vendored
Normal file
71
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/README.md
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
go-lz4
|
||||
======
|
||||
|
||||
go-lz4 is port of LZ4 lossless compression algorithm to Go. The original C code
|
||||
is located at:
|
||||
|
||||
https://code.google.com/p/lz4/
|
||||
|
||||
Status
|
||||
------
|
||||
[](http://travis-ci.org/bkaradzic/go-lz4)
|
||||
[](https://godoc.org/github.com/bkaradzic/go-lz4)
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
go get github.com/bkaradzic/go-lz4
|
||||
|
||||
import "github.com/bkaradzic/go-lz4"
|
||||
|
||||
The package name is `lz4`
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
* go-lz4 saves a uint32 with the original uncompressed length at the beginning
|
||||
of the encoded buffer. They may get in the way of interoperability with
|
||||
other implementations.
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
Damian Gryski ([@dgryski](https://github.com/dgryski))
|
||||
Dustin Sallings ([@dustin](https://github.com/dustin))
|
||||
|
||||
Contact
|
||||
-------
|
||||
|
||||
[@bkaradzic](https://twitter.com/bkaradzic)
|
||||
http://www.stuckingeometry.com
|
||||
|
||||
Project page
|
||||
https://github.com/bkaradzic/go-lz4
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||
Copyright 2013 Damian Gryski. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
74
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/fuzzer/main.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/fuzzer/main.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/bkaradzic/go-lz4"
|
||||
|
||||
// lz4's API matches snappy's, so we can easily see how it performs
|
||||
// lz4 "code.google.com/p/snappy-go/snappy"
|
||||
)
|
||||
|
||||
var input = `
|
||||
ADVENTURE I. A SCANDAL IN BOHEMIA
|
||||
|
||||
I.
|
||||
|
||||
To Sherlock Holmes she is always THE woman. I have seldom heard
|
||||
him mention her under any other name. In his eyes she eclipses
|
||||
and predominates the whole of her sex. It was not that he felt
|
||||
any emotion akin to love for Irene Adler. All emotions, and that
|
||||
one particularly, were abhorrent to his cold, precise but
|
||||
admirably balanced mind. He was, I take it, the most perfect
|
||||
reasoning and observing machine that the world has seen, but as a
|
||||
lover he would have placed himself in a false position. He never
|
||||
spoke of the softer passions, save with a gibe and a sneer. They
|
||||
were admirable things for the observer--excellent for drawing the
|
||||
veil from men's motives and actions. But for the trained reasoner
|
||||
to admit such intrusions into his own delicate and finely
|
||||
adjusted temperament was to introduce a distracting factor which
|
||||
might throw a doubt upon all his mental results. Grit in a
|
||||
sensitive instrument, or a crack in one of his own high-power
|
||||
lenses, would not be more disturbing than a strong emotion in a
|
||||
nature such as his. And yet there was but one woman to him, and
|
||||
that woman was the late Irene Adler, of dubious and questionable
|
||||
memory.
|
||||
|
||||
I had seen little of Holmes lately. My marriage had drifted us
|
||||
away from each other. My own complete happiness, and the
|
||||
home-centred interests which rise up around the man who first
|
||||
finds himself master of his own establishment, were sufficient to
|
||||
absorb all my attention, while Holmes, who loathed every form of
|
||||
society with his whole Bohemian soul, remained in our lodgings in
|
||||
Baker Street, buried among his old books, and alternating from
|
||||
week to week between cocaine and ambition, the drowsiness of the
|
||||
drug, and the fierce energy of his own keen nature. He was still,
|
||||
as ever, deeply attracted by the study of crime, and occupied his
|
||||
immense faculties and extraordinary powers of observation in
|
||||
following out those clues, and clearing up those mysteries which
|
||||
had been abandoned as hopeless by the official police. From time
|
||||
to time I heard some vague account of his doings: of his summons
|
||||
to Odessa in the case of the Trepoff murder, of his clearing up
|
||||
of the singular tragedy of the Atkinson brothers at Trincomalee,
|
||||
and finally of the mission which he had accomplished so
|
||||
delicately and successfully for the reigning family of Holland.
|
||||
Beyond these signs of his activity, however, which I merely
|
||||
shared with all the readers of the daily press, I knew little of
|
||||
my former friend and companion.
|
||||
`
|
||||
|
||||
func main() {
|
||||
|
||||
compressed, _ := lz4.Encode(nil, []byte(input))
|
||||
|
||||
modified := make([]byte, len(compressed))
|
||||
|
||||
for {
|
||||
copy(modified, compressed)
|
||||
for i := 0; i < 100; i++ {
|
||||
modified[rand.Intn(len(compressed)-4)+4] = byte(rand.Intn(256))
|
||||
}
|
||||
lz4.Decode(nil, modified)
|
||||
}
|
||||
|
||||
}
|
||||
86
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/lz4-example/main.go
generated
vendored
Normal file
86
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/lz4-example/main.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2011 Branimir Karadzic. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
|
||||
lz4 "github.com/bkaradzic/go-lz4"
|
||||
)
|
||||
|
||||
var (
|
||||
decompress = flag.Bool("d", false, "decompress")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var optCPUProfile = flag.String("cpuprofile", "", "profile")
|
||||
flag.Parse()
|
||||
|
||||
if *optCPUProfile != "" {
|
||||
f, err := os.Create(*optCPUProfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
var data []byte
|
||||
|
||||
if len(args) < 2 {
|
||||
fmt.Print("Usage: lz4 [-d] <input> <output>\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
input, err := os.OpenFile(args[0], os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open input file %s\n", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
if *decompress {
|
||||
data, _ = ioutil.ReadAll(input)
|
||||
data, _ = lz4.Decode(nil, data)
|
||||
} else {
|
||||
data, _ = ioutil.ReadAll(input)
|
||||
data, _ = lz4.Encode(nil, data)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(args[1], data, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open output file %s\n", args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
63
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/lz4_test.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/lz4_test.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package lz4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testfile, _ = ioutil.ReadFile("testdata/pg1661.txt")
|
||||
|
||||
func roundtrip(t *testing.T, input []byte) {
|
||||
|
||||
dst, err := Encode(nil, input)
|
||||
if err != nil {
|
||||
t.Errorf("got error during compression: %s", err)
|
||||
}
|
||||
|
||||
output, err := Decode(nil, dst)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("got error during decompress: %s", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(output, input) {
|
||||
t.Errorf("roundtrip failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
roundtrip(t, nil)
|
||||
}
|
||||
|
||||
func TestLengths(t *testing.T) {
|
||||
|
||||
for i := 0; i < 1024; i++ {
|
||||
roundtrip(t, testfile[:i])
|
||||
}
|
||||
|
||||
for i := 1024; i < 4096; i += 23 {
|
||||
roundtrip(t, testfile[:i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWords(t *testing.T) {
|
||||
roundtrip(t, testfile)
|
||||
}
|
||||
|
||||
func BenchmarkLZ4Encode(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Encode(nil, testfile)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLZ4Decode(b *testing.B) {
|
||||
|
||||
var compressed, _ = Encode(nil, testfile)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(nil, compressed)
|
||||
}
|
||||
}
|
||||
194
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/reader.go
generated
vendored
Normal file
194
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/reader.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package lz4
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCorrupt indicates the input was corrupt
|
||||
ErrCorrupt = errors.New("corrupt input")
|
||||
)
|
||||
|
||||
const (
|
||||
mlBits = 4
|
||||
mlMask = (1 << mlBits) - 1
|
||||
runBits = 8 - mlBits
|
||||
runMask = (1 << runBits) - 1
|
||||
)
|
||||
|
||||
type decoder struct {
|
||||
src []byte
|
||||
dst []byte
|
||||
spos uint32
|
||||
dpos uint32
|
||||
ref uint32
|
||||
}
|
||||
|
||||
func (d *decoder) readByte() (uint8, error) {
|
||||
if int(d.spos) == len(d.src) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
b := d.src[d.spos]
|
||||
d.spos++
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (d *decoder) getLen() (uint32, error) {
|
||||
|
||||
length := uint32(0)
|
||||
ln, err := d.readByte()
|
||||
if err != nil {
|
||||
return 0, ErrCorrupt
|
||||
}
|
||||
for ln == 255 {
|
||||
length += 255
|
||||
ln, err = d.readByte()
|
||||
if err != nil {
|
||||
return 0, ErrCorrupt
|
||||
}
|
||||
}
|
||||
length += uint32(ln)
|
||||
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (d *decoder) cp(length, decr uint32) {
|
||||
|
||||
if int(d.ref+length) < int(d.dpos) {
|
||||
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
|
||||
} else {
|
||||
for ii := uint32(0); ii < length; ii++ {
|
||||
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
|
||||
}
|
||||
}
|
||||
d.dpos += length
|
||||
d.ref += length - decr
|
||||
}
|
||||
|
||||
func (d *decoder) finish(err error) error {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode returns the decoded form of src. The returned slice may be a
|
||||
// subslice of dst if it was large enough to hold the entire decoded block.
|
||||
func Decode(dst, src []byte) ([]byte, error) {
|
||||
|
||||
if len(src) < 4 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
uncompressedLen := binary.LittleEndian.Uint32(src)
|
||||
|
||||
if uncompressedLen == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if uncompressedLen > MaxInputSize {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
if dst == nil || len(dst) < int(uncompressedLen) {
|
||||
dst = make([]byte, uncompressedLen)
|
||||
}
|
||||
|
||||
d := decoder{src: src, dst: dst[:uncompressedLen], spos: 4}
|
||||
|
||||
decr := []uint32{0, 3, 2, 3}
|
||||
|
||||
for {
|
||||
code, err := d.readByte()
|
||||
if err != nil {
|
||||
return d.dst, d.finish(err)
|
||||
}
|
||||
|
||||
length := uint32(code >> mlBits)
|
||||
if length == runMask {
|
||||
ln, err := d.getLen()
|
||||
if err != nil {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
length += ln
|
||||
}
|
||||
|
||||
if int(d.spos+length) > len(d.src) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
for ii := uint32(0); ii < length; ii++ {
|
||||
d.dst[d.dpos+ii] = d.src[d.spos+ii]
|
||||
}
|
||||
|
||||
d.spos += length
|
||||
d.dpos += length
|
||||
|
||||
if int(d.spos) == len(d.src) {
|
||||
return d.dst, nil
|
||||
}
|
||||
|
||||
if int(d.spos+2) >= len(d.src) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
|
||||
|
||||
if back > d.dpos {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
d.spos += 2
|
||||
d.ref = d.dpos - back
|
||||
|
||||
length = uint32(code & mlMask)
|
||||
if length == mlMask {
|
||||
ln, err := d.getLen()
|
||||
if err != nil {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
length += ln
|
||||
}
|
||||
|
||||
literal := d.dpos - d.ref
|
||||
if literal < 4 {
|
||||
d.cp(4, decr[literal])
|
||||
} else {
|
||||
length += 4
|
||||
}
|
||||
|
||||
if d.dpos+length > uncompressedLen {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
d.cp(length, 0)
|
||||
}
|
||||
}
|
||||
13052
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/testdata/pg1661.txt
generated
vendored
Normal file
13052
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/testdata/pg1661.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
188
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/writer.go
generated
vendored
Normal file
188
Godeps/_workspace/src/github.com/bkaradzic/go-lz4/writer.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package lz4
|
||||
|
||||
import "encoding/binary"
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
minMatch = 4
|
||||
hashLog = 17
|
||||
hashTableSize = 1 << hashLog
|
||||
hashShift = (minMatch * 8) - hashLog
|
||||
incompressible uint32 = 128
|
||||
uninitHash = 0x88888888
|
||||
|
||||
// MaxInputSize is the largest buffer than can be compressed in a single block
|
||||
MaxInputSize = 0x7E000000
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrTooLarge indicates the input buffer was too large
|
||||
ErrTooLarge = errors.New("input too large")
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
src []byte
|
||||
dst []byte
|
||||
hashTable []uint32
|
||||
pos uint32
|
||||
anchor uint32
|
||||
dpos uint32
|
||||
}
|
||||
|
||||
// CompressBound returns the maximum length of a lz4 block, given it's uncompressed length
|
||||
func CompressBound(isize int) int {
|
||||
if isize > MaxInputSize {
|
||||
return 0
|
||||
}
|
||||
return isize + ((isize) / 255) + 16 + 4
|
||||
}
|
||||
|
||||
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
|
||||
|
||||
ln := length
|
||||
|
||||
var code byte
|
||||
if ln > runMask-1 {
|
||||
code = runMask
|
||||
} else {
|
||||
code = byte(ln)
|
||||
}
|
||||
|
||||
if mlLen > mlMask-1 {
|
||||
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
|
||||
} else {
|
||||
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
|
||||
}
|
||||
e.dpos++
|
||||
|
||||
if code == runMask {
|
||||
ln -= runMask
|
||||
for ; ln > 254; ln -= 255 {
|
||||
e.dst[e.dpos] = 255
|
||||
e.dpos++
|
||||
}
|
||||
|
||||
e.dst[e.dpos] = byte(ln)
|
||||
e.dpos++
|
||||
}
|
||||
|
||||
for ii := uint32(0); ii < length; ii++ {
|
||||
e.dst[e.dpos+ii] = e.src[pos+ii]
|
||||
}
|
||||
|
||||
e.dpos += length
|
||||
}
|
||||
|
||||
// Encode returns the encoded form of src. The returned array may be a
|
||||
// sub-slice of dst if it was large enough to hold the entire output.
|
||||
func Encode(dst, src []byte) ([]byte, error) {
|
||||
|
||||
if len(src) >= MaxInputSize {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
if n := CompressBound(len(src)); len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
||||
e := encoder{src: src, dst: dst, hashTable: make([]uint32, hashTableSize)}
|
||||
|
||||
binary.LittleEndian.PutUint32(dst, uint32(len(src)))
|
||||
e.dpos = 4
|
||||
|
||||
var (
|
||||
step uint32 = 1
|
||||
limit = incompressible
|
||||
)
|
||||
|
||||
for {
|
||||
if int(e.pos)+4 >= len(e.src) {
|
||||
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
|
||||
return e.dst[:e.dpos], nil
|
||||
}
|
||||
|
||||
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
|
||||
|
||||
hash := (sequence * 2654435761) >> hashShift
|
||||
ref := e.hashTable[hash] + uninitHash
|
||||
e.hashTable[hash] = e.pos - uninitHash
|
||||
|
||||
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
|
||||
if e.pos-e.anchor > limit {
|
||||
limit <<= 1
|
||||
step += 1 + (step >> 2)
|
||||
}
|
||||
e.pos += step
|
||||
continue
|
||||
}
|
||||
|
||||
if step > 1 {
|
||||
e.hashTable[hash] = ref - uninitHash
|
||||
e.pos -= step - 1
|
||||
step = 1
|
||||
continue
|
||||
}
|
||||
limit = incompressible
|
||||
|
||||
ln := e.pos - e.anchor
|
||||
back := e.pos - ref
|
||||
|
||||
anchor := e.anchor
|
||||
|
||||
e.pos += minMatch
|
||||
ref += minMatch
|
||||
e.anchor = e.pos
|
||||
|
||||
for int(e.pos) < len(e.src) && e.src[e.pos] == e.src[ref] {
|
||||
e.pos++
|
||||
ref++
|
||||
}
|
||||
|
||||
mlLen := e.pos - e.anchor
|
||||
|
||||
e.writeLiterals(ln, mlLen, anchor)
|
||||
e.dst[e.dpos] = uint8(back)
|
||||
e.dst[e.dpos+1] = uint8(back >> 8)
|
||||
e.dpos += 2
|
||||
|
||||
if mlLen > mlMask-1 {
|
||||
mlLen -= mlMask
|
||||
for mlLen > 254 {
|
||||
mlLen -= 255
|
||||
|
||||
e.dst[e.dpos] = 255
|
||||
e.dpos++
|
||||
}
|
||||
|
||||
e.dst[e.dpos] = byte(mlLen)
|
||||
e.dpos++
|
||||
}
|
||||
|
||||
e.anchor = e.pos
|
||||
}
|
||||
}
|
||||
68
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
||||
// All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type unixFileLock struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (fl *unixFileLock) release() error {
|
||||
if err := setFileLock(fl.f, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return fl.f.Close()
|
||||
}
|
||||
|
||||
func newFileLock(path string) (fl fileLock, err error) {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = setFileLock(f, true)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
fl = &unixFileLock{f: f}
|
||||
return
|
||||
}
|
||||
|
||||
func setFileLock(f *os.File, lock bool) error {
|
||||
flock := syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
Whence: 1,
|
||||
}
|
||||
if lock {
|
||||
flock.Type = syscall.F_WRLCK
|
||||
}
|
||||
return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &flock)
|
||||
}
|
||||
|
||||
func rename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
|
||||
func syncDir(name string) error {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// +build darwin freebsd linux netbsd openbsd
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package storage
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
12
build.sh
12
build.sh
@@ -102,6 +102,12 @@ xdr() {
|
||||
done
|
||||
}
|
||||
|
||||
transifex() {
|
||||
pushd gui
|
||||
go run ../cmd/transifexdl/main.go
|
||||
popd
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
"")
|
||||
shift
|
||||
@@ -143,7 +149,7 @@ case "$1" in
|
||||
test || exit 1
|
||||
assets
|
||||
|
||||
for os in darwin-amd64 linux-386 linux-amd64 freebsd-amd64 windows-amd64 windows-386 ; do
|
||||
for os in darwin-amd64 linux-386 linux-amd64 freebsd-amd64 windows-amd64 windows-386 solaris-amd64 ; do
|
||||
export GOOS=${os%-*}
|
||||
export GOARCH=${os#*-}
|
||||
|
||||
@@ -208,6 +214,10 @@ case "$1" in
|
||||
xdr
|
||||
;;
|
||||
|
||||
transifex)
|
||||
transifex
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown build parameter $1"
|
||||
;;
|
||||
|
||||
@@ -47,7 +47,7 @@ var (
|
||||
static func(http.ResponseWriter, *http.Request, *log.Logger)
|
||||
apiKey string
|
||||
modt = time.Now().UTC().Format(http.TimeFormat)
|
||||
eventSub = events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000)
|
||||
eventSub *events.BufferedSubscription
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,6 +56,8 @@ const (
|
||||
|
||||
func init() {
|
||||
l.AddHandler(logger.LevelWarn, showGuiError)
|
||||
sub := events.Default.Subscribe(^events.EventType(events.ItemStarted | events.ItemCompleted))
|
||||
eventSub = events.NewBufferedSubscription(sub, 1000)
|
||||
}
|
||||
|
||||
func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) error {
|
||||
@@ -92,31 +94,36 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
|
||||
|
||||
// The GET handlers
|
||||
getRestMux := http.NewServeMux()
|
||||
getRestMux.HandleFunc("/rest/version", restGetVersion)
|
||||
getRestMux.HandleFunc("/rest/completion", withModel(m, restGetCompletion))
|
||||
getRestMux.HandleFunc("/rest/config", restGetConfig)
|
||||
getRestMux.HandleFunc("/rest/config/sync", restGetConfigInSync)
|
||||
getRestMux.HandleFunc("/rest/connections", withModel(m, restGetConnections))
|
||||
getRestMux.HandleFunc("/rest/discovery", restGetDiscovery)
|
||||
getRestMux.HandleFunc("/rest/errors", restGetErrors)
|
||||
getRestMux.HandleFunc("/rest/events", restGetEvents)
|
||||
getRestMux.HandleFunc("/rest/lang", restGetLang)
|
||||
getRestMux.HandleFunc("/rest/model", withModel(m, restGetModel))
|
||||
getRestMux.HandleFunc("/rest/model/version", withModel(m, restGetModelVersion))
|
||||
getRestMux.HandleFunc("/rest/need", withModel(m, restGetNeed))
|
||||
getRestMux.HandleFunc("/rest/connections", withModel(m, restGetConnections))
|
||||
getRestMux.HandleFunc("/rest/config", restGetConfig)
|
||||
getRestMux.HandleFunc("/rest/config/sync", restGetConfigInSync)
|
||||
getRestMux.HandleFunc("/rest/system", restGetSystem)
|
||||
getRestMux.HandleFunc("/rest/errors", restGetErrors)
|
||||
getRestMux.HandleFunc("/rest/discovery", restGetDiscovery)
|
||||
getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport))
|
||||
getRestMux.HandleFunc("/rest/events", restGetEvents)
|
||||
getRestMux.HandleFunc("/rest/upgrade", restGetUpgrade)
|
||||
getRestMux.HandleFunc("/rest/nodeid", restGetNodeID)
|
||||
getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport))
|
||||
getRestMux.HandleFunc("/rest/system", restGetSystem)
|
||||
getRestMux.HandleFunc("/rest/upgrade", restGetUpgrade)
|
||||
getRestMux.HandleFunc("/rest/version", restGetVersion)
|
||||
|
||||
// Debug endpoints, not for general use
|
||||
getRestMux.HandleFunc("/rest/debug/peerCompletion", withModel(m, restGetPeerCompletion))
|
||||
|
||||
// The POST handlers
|
||||
postRestMux := http.NewServeMux()
|
||||
postRestMux.HandleFunc("/rest/config", withModel(m, restPostConfig))
|
||||
postRestMux.HandleFunc("/rest/restart", restPostRestart)
|
||||
postRestMux.HandleFunc("/rest/reset", restPostReset)
|
||||
postRestMux.HandleFunc("/rest/shutdown", restPostShutdown)
|
||||
postRestMux.HandleFunc("/rest/discovery/hint", restPostDiscoveryHint)
|
||||
postRestMux.HandleFunc("/rest/error", restPostError)
|
||||
postRestMux.HandleFunc("/rest/error/clear", restClearErrors)
|
||||
postRestMux.HandleFunc("/rest/discovery/hint", restPostDiscoveryHint)
|
||||
postRestMux.HandleFunc("/rest/model/override", withModel(m, restPostOverride))
|
||||
postRestMux.HandleFunc("/rest/reset", restPostReset)
|
||||
postRestMux.HandleFunc("/rest/restart", restPostRestart)
|
||||
postRestMux.HandleFunc("/rest/shutdown", restPostShutdown)
|
||||
postRestMux.HandleFunc("/rest/upgrade", restPostUpgrade)
|
||||
|
||||
// A handler that splits requests between the two above and disables
|
||||
@@ -174,6 +181,25 @@ func restGetVersion(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(Version))
|
||||
}
|
||||
|
||||
func restGetCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var repo = qs.Get("repo")
|
||||
var nodeStr = qs.Get("node")
|
||||
|
||||
node, err := protocol.NodeIDFromString(nodeStr)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
res := map[string]float64{
|
||||
"completion": m.Completion(node, repo),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
func restGetModelVersion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
var qs = r.URL.Query()
|
||||
var repo = qs.Get("repo")
|
||||
@@ -287,6 +313,7 @@ func restPostConfig(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
nm := newCfg.NodeMap()
|
||||
for k := range om {
|
||||
if _, ok := nm[k]; !ok {
|
||||
// A node was removed and another added
|
||||
configInSync = false
|
||||
break
|
||||
}
|
||||
@@ -421,11 +448,18 @@ func restGetReport(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func restGetEvents(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
ts := qs.Get("since")
|
||||
since, _ := strconv.Atoi(ts)
|
||||
sinceStr := qs.Get("since")
|
||||
limitStr := qs.Get("limit")
|
||||
since, _ := strconv.Atoi(sinceStr)
|
||||
limit, _ := strconv.Atoi(limitStr)
|
||||
|
||||
evs := eventSub.Since(since, nil)
|
||||
if 0 < limit && limit < len(evs) {
|
||||
evs = evs[len(evs)-limit:]
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(eventSub.Since(since, nil))
|
||||
json.NewEncoder(w).Encode(evs)
|
||||
}
|
||||
|
||||
func restGetUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -459,6 +493,17 @@ func restGetNodeID(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func restGetLang(w http.ResponseWriter, r *http.Request) {
|
||||
lang := r.Header.Get("Accept-Language")
|
||||
var langs []string
|
||||
for _, l := range strings.Split(lang, ",") {
|
||||
if len(l) >= 2 {
|
||||
langs = append(langs, l[:2])
|
||||
}
|
||||
}
|
||||
json.NewEncoder(w).Encode(langs)
|
||||
}
|
||||
|
||||
func restPostUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
err := upgrade()
|
||||
if err != nil {
|
||||
@@ -483,6 +528,31 @@ func getQR(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(code.PNG())
|
||||
}
|
||||
|
||||
func restGetPeerCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
|
||||
tot := map[string]float64{}
|
||||
count := map[string]float64{}
|
||||
|
||||
for _, repo := range cfg.Repositories {
|
||||
for _, node := range repo.NodeIDs() {
|
||||
nodeStr := node.String()
|
||||
if m.ConnectedTo(node) {
|
||||
tot[nodeStr] += m.Completion(node, repo.ID)
|
||||
} else {
|
||||
tot[nodeStr] = 0
|
||||
}
|
||||
count[nodeStr]++
|
||||
}
|
||||
}
|
||||
|
||||
comp := map[string]int{}
|
||||
for node := range tot {
|
||||
comp[node] = int(tot[node] / count[node])
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(comp)
|
||||
}
|
||||
|
||||
func basicAuthMiddleware(username string, passhash string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if validAPIKey(r.Header.Get("X-API-Key")) {
|
||||
|
||||
@@ -107,6 +107,7 @@ The following enviroment variables are interpreted by syncthing:
|
||||
facility strings:
|
||||
- "beacon" (the beacon package)
|
||||
- "discover" (the discover package)
|
||||
- "events" (the events package)
|
||||
- "files" (the files package)
|
||||
- "net" (the main package; connections & network messages)
|
||||
- "model" (the model package)
|
||||
@@ -307,20 +308,29 @@ nextRepo:
|
||||
|
||||
repo.Directory = expandTilde(repo.Directory)
|
||||
|
||||
// Safety check. If the cached index contains files but the repository
|
||||
// doesn't exist, we have a problem. We would assume that all files
|
||||
// have been deleted which might not be the case, so abort instead.
|
||||
|
||||
id := fmt.Sprintf("%x", sha1.Sum([]byte(repo.Directory)))
|
||||
idxFile := filepath.Join(confDir, id+".idx.gz")
|
||||
if _, err := os.Stat(idxFile); err == nil {
|
||||
if fi, err := os.Stat(repo.Directory); err != nil || !fi.IsDir() {
|
||||
fi, err := os.Stat(repo.Directory)
|
||||
if m.LocalVersion(repo.ID) > 0 {
|
||||
// Safety check. If the cached index contains files but the
|
||||
// repository doesn't exist, we have a problem. We would assume
|
||||
// that all files have been deleted which might not be the case,
|
||||
// so mark it as invalid instead.
|
||||
if err != nil || !fi.IsDir() {
|
||||
cfg.Repositories[i].Invalid = "repo directory missing"
|
||||
continue nextRepo
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// If we don't have ny files in the index, and the directory does
|
||||
// exist, try creating it.
|
||||
err = os.MkdirAll(repo.Directory, 0700)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// If there was another error or we could not create the
|
||||
// directory, the repository is invalid.
|
||||
cfg.Repositories[i].Invalid = err.Error()
|
||||
continue nextRepo
|
||||
}
|
||||
|
||||
ensureDir(repo.Directory, -1)
|
||||
m.AddRepo(repo)
|
||||
}
|
||||
|
||||
@@ -644,7 +654,7 @@ next:
|
||||
wr = &limitedWriter{conn, rateBucket}
|
||||
}
|
||||
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
|
||||
protoConn := protocol.NewConnection(remoteID, conn, wr, m, name)
|
||||
protoConn := protocol.NewConnection(remoteID, conn, wr, m, name, nodeCfg.Compression)
|
||||
|
||||
l.Infof("Established secure connection to %s at %s", remoteID, name)
|
||||
if debugNet {
|
||||
|
||||
@@ -93,13 +93,24 @@ func currentRelease() (githubRelease, error) {
|
||||
json.NewDecoder(resp.Body).Decode(&rels)
|
||||
resp.Body.Close()
|
||||
|
||||
for _, rel := range rels {
|
||||
if !rel.Prerelease {
|
||||
return rel, nil
|
||||
if strings.Contains(Version, "-beta") {
|
||||
// We are a beta version. Use whatever we can find that is newer-or-equal than current.
|
||||
for _, rel := range rels {
|
||||
if compareVersions(rel.Tag, Version) >= 0 {
|
||||
return rel, nil
|
||||
}
|
||||
}
|
||||
// We found nothing. Return the latest release and let the next layer decide.
|
||||
return rels[0], nil
|
||||
} else {
|
||||
// We are a regular release. Only consider non-prerelease versions for upgrade.
|
||||
for _, rel := range rels {
|
||||
if !rel.Prerelease {
|
||||
return rel, nil
|
||||
}
|
||||
}
|
||||
return githubRelease{}, errors.New("no suitable release found")
|
||||
}
|
||||
|
||||
return githubRelease{}, errors.New("no suitable release found")
|
||||
}
|
||||
|
||||
func readTarGZ(url string, dir string) (string, error) {
|
||||
|
||||
91
cmd/transifexdl/main.go
Normal file
91
cmd/transifexdl/main.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type stat struct {
|
||||
Translated int `json:"translated_entities"`
|
||||
Untranslated int `json:"untranslated_entities"`
|
||||
}
|
||||
|
||||
type translation struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
if u, p := userPass(); u == "" || p == "" {
|
||||
log.Fatal("Need environment variables TRANSIFEX_USER and TRANSIFEX_PASS")
|
||||
}
|
||||
|
||||
resp := req("https://www.transifex.com/api/2/project/syncthing/resource/gui/stats")
|
||||
|
||||
var stats map[string]stat
|
||||
err := json.NewDecoder(resp.Body).Decode(&stats)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
var langs []string
|
||||
for code, stat := range stats {
|
||||
shortCode := code[:2]
|
||||
if pct := 100 * stat.Translated / (stat.Translated + stat.Untranslated); pct < 95 {
|
||||
log.Printf("Skipping language %q (too low completion ratio %d%%)", shortCode, pct)
|
||||
os.Remove("lang-" + shortCode + ".json")
|
||||
continue
|
||||
}
|
||||
|
||||
langs = append(langs, shortCode)
|
||||
if shortCode == "en" {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Updating language %q", shortCode)
|
||||
|
||||
resp := req("https://www.transifex.com/api/2/project/syncthing/resource/gui/translation/" + code)
|
||||
var t translation
|
||||
err := json.NewDecoder(resp.Body).Decode(&t)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
fd, err := os.Create("lang-" + shortCode + ".json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fd.WriteString(t.Content)
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
sort.Strings(langs)
|
||||
json.NewEncoder(os.Stdout).Encode(langs)
|
||||
}
|
||||
|
||||
func userPass() (string, string) {
|
||||
user := os.Getenv("TRANSIFEX_USER")
|
||||
pass := os.Getenv("TRANSIFEX_PASS")
|
||||
return user, pass
|
||||
}
|
||||
|
||||
func req(url string) *http.Response {
|
||||
user, pass := userPass()
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
req.SetBasicAuth(user, pass)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
@@ -93,9 +93,10 @@ func (r *RepositoryConfiguration) NodeIDs() []protocol.NodeID {
|
||||
}
|
||||
|
||||
type NodeConfiguration struct {
|
||||
NodeID protocol.NodeID `xml:"id,attr"`
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Addresses []string `xml:"address,omitempty"`
|
||||
NodeID protocol.NodeID `xml:"id,attr"`
|
||||
Name string `xml:"name,attr,omitempty"`
|
||||
Addresses []string `xml:"address,omitempty"`
|
||||
Compression bool `xml:"compression,attr"`
|
||||
}
|
||||
|
||||
type OptionsConfiguration struct {
|
||||
@@ -313,10 +314,14 @@ func Load(rd io.Reader, myID protocol.NodeID) (Configuration, error) {
|
||||
|
||||
// Ensure this node is present in all relevant places
|
||||
// Ensure that any loose nodes are not present in the wrong places
|
||||
// Ensure that there are no duplicate nodes
|
||||
cfg.Nodes = ensureNodePresent(cfg.Nodes, myID)
|
||||
sort.Sort(NodeConfigurationList(cfg.Nodes))
|
||||
for i := range cfg.Repositories {
|
||||
cfg.Repositories[i].Nodes = ensureNodePresent(cfg.Repositories[i].Nodes, myID)
|
||||
cfg.Repositories[i].Nodes = ensureExistingNodes(cfg.Repositories[i].Nodes, existingNodes)
|
||||
cfg.Repositories[i].Nodes = ensureNoDuplicates(cfg.Repositories[i].Nodes)
|
||||
sort.Sort(NodeConfigurationList(cfg.Repositories[i].Nodes))
|
||||
}
|
||||
|
||||
// An empty address list is equivalent to a single "dynamic" entry
|
||||
@@ -381,40 +386,50 @@ func (l NodeConfigurationList) Len() int {
|
||||
}
|
||||
|
||||
func ensureNodePresent(nodes []NodeConfiguration, myID protocol.NodeID) []NodeConfiguration {
|
||||
var myIDExists bool
|
||||
for _, node := range nodes {
|
||||
if node.NodeID.Equals(myID) {
|
||||
myIDExists = true
|
||||
break
|
||||
return nodes
|
||||
}
|
||||
}
|
||||
|
||||
if !myIDExists {
|
||||
name, _ := os.Hostname()
|
||||
nodes = append(nodes, NodeConfiguration{
|
||||
NodeID: myID,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Sort(NodeConfigurationList(nodes))
|
||||
name, _ := os.Hostname()
|
||||
nodes = append(nodes, NodeConfiguration{
|
||||
NodeID: myID,
|
||||
Name: name,
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func ensureExistingNodes(nodes []NodeConfiguration, existingNodes map[protocol.NodeID]bool) []NodeConfiguration {
|
||||
count := len(nodes)
|
||||
i := 0
|
||||
for _, node := range nodes {
|
||||
if _, ok := existingNodes[node.NodeID]; !ok {
|
||||
last := len(nodes) - 1
|
||||
nodes[i] = nodes[last]
|
||||
nodes = nodes[:last]
|
||||
} else {
|
||||
i++
|
||||
loop:
|
||||
for i < count {
|
||||
if _, ok := existingNodes[nodes[i].NodeID]; !ok {
|
||||
nodes[i] = nodes[count-1]
|
||||
count--
|
||||
continue loop
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Sort(NodeConfigurationList(nodes))
|
||||
|
||||
return nodes
|
||||
return nodes[0:count]
|
||||
}
|
||||
|
||||
func ensureNoDuplicates(nodes []NodeConfiguration) []NodeConfiguration {
|
||||
count := len(nodes)
|
||||
i := 0
|
||||
seenNodes := make(map[protocol.NodeID]bool)
|
||||
loop:
|
||||
for i < count {
|
||||
id := nodes[i].NodeID
|
||||
if _, ok := seenNodes[id]; ok {
|
||||
nodes[i] = nodes[count-1]
|
||||
count--
|
||||
continue loop
|
||||
}
|
||||
seenNodes[id] = true
|
||||
i++
|
||||
}
|
||||
return nodes[0:count]
|
||||
}
|
||||
|
||||
@@ -59,6 +59,12 @@ func TestNodeConfig(t *testing.T) {
|
||||
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
|
||||
<address>b</address>
|
||||
</node>
|
||||
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
|
||||
<address>a</address>
|
||||
</node>
|
||||
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ" name="node two">
|
||||
<address>b</address>
|
||||
</node>
|
||||
</repository>
|
||||
<options>
|
||||
<readOnly>true</readOnly>
|
||||
@@ -69,9 +75,12 @@ func TestNodeConfig(t *testing.T) {
|
||||
v2data := []byte(`
|
||||
<configuration version="2">
|
||||
<repository id="test" directory="~/Sync" ro="true">
|
||||
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
|
||||
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
|
||||
<node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>
|
||||
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
|
||||
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
|
||||
<node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>
|
||||
</repository>
|
||||
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
|
||||
<address>a</address>
|
||||
|
||||
17
events/debug.go
Normal file
17
events/debug.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/syncthing/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "events") || os.Getenv("STTRACE") == "all"
|
||||
dl = logger.DefaultLogger
|
||||
)
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package events provides event subscription and polling functionality.
|
||||
package events
|
||||
|
||||
@@ -93,6 +97,9 @@ func NewLogger() *Logger {
|
||||
|
||||
func (l *Logger) Log(t EventType, data interface{}) {
|
||||
l.mutex.Lock()
|
||||
if debug {
|
||||
dl.Debugln("log", l.nextId, t.String(), data)
|
||||
}
|
||||
e := Event{
|
||||
ID: l.nextId,
|
||||
Time: time.Now(),
|
||||
@@ -114,6 +121,9 @@ func (l *Logger) Log(t EventType, data interface{}) {
|
||||
|
||||
func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
l.mutex.Lock()
|
||||
if debug {
|
||||
dl.Debugln("subscribe", mask)
|
||||
}
|
||||
s := &Subscription{
|
||||
mask: mask,
|
||||
id: l.nextId,
|
||||
@@ -127,6 +137,9 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
|
||||
|
||||
func (l *Logger) Unsubscribe(s *Subscription) {
|
||||
l.mutex.Lock()
|
||||
if debug {
|
||||
dl.Debugln("unsubsribe")
|
||||
}
|
||||
delete(l.subs, s.id)
|
||||
close(s.events)
|
||||
l.mutex.Unlock()
|
||||
@@ -136,6 +149,10 @@ func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if debug {
|
||||
dl.Debugln("poll", timeout)
|
||||
}
|
||||
|
||||
to := time.After(timeout)
|
||||
select {
|
||||
case e, ok := <-s.events:
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
|
||||
// All rights reserved. Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package events_test
|
||||
|
||||
import (
|
||||
|
||||
@@ -632,9 +632,6 @@ func ldbWithNeed(db *leveldb.DB, repo, node []byte, fn fileIterator) {
|
||||
|
||||
if need || !have {
|
||||
name := globalKeyName(dbi.Key())
|
||||
if debug {
|
||||
l.Debugf("need repo=%q node=%v name=%q need=%v have=%v haveV=%d globalV=%d", repo, protocol.NodeIDFromBytes(node), name, need, have, haveVersion, vl.versions[0].version)
|
||||
}
|
||||
fk := nodeKey(repo, vl.versions[0].node, name)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err != nil {
|
||||
@@ -652,6 +649,10 @@ func ldbWithNeed(db *leveldb.DB, repo, node []byte, fn fileIterator) {
|
||||
continue
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("need repo=%q node=%v name=%q need=%v have=%v haveV=%d globalV=%d", repo, protocol.NodeIDFromBytes(node), name, need, have, haveVersion, vl.versions[0].version)
|
||||
}
|
||||
|
||||
if cont := fn(gf); !cont {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (s *Set) Update(node protocol.NodeID, fs []protocol.FileInfo) {
|
||||
|
||||
func (s *Set) WithNeed(node protocol.NodeID, fn fileIterator) {
|
||||
if debug {
|
||||
l.Debugf("%s Need(%v)", s.repo, node)
|
||||
l.Debugf("%s WithNeed(%v)", s.repo, node)
|
||||
}
|
||||
ldbWithNeed(s.db, []byte(s.repo), node[:], fn)
|
||||
}
|
||||
|
||||
422
gui/app.js
422
gui/app.js
@@ -9,6 +9,7 @@
|
||||
|
||||
var syncthing = angular.module('syncthing', ['pascalprecht.translate']);
|
||||
var urlbase = 'rest';
|
||||
var validLangs = ["de","en","es","fr","pt","sv"];
|
||||
|
||||
syncthing.config(function ($httpProvider, $translateProvider) {
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
|
||||
@@ -18,7 +19,51 @@ syncthing.config(function ($httpProvider, $translateProvider) {
|
||||
prefix: 'lang-',
|
||||
suffix: '.json'
|
||||
});
|
||||
$translateProvider.preferredLanguage('en');
|
||||
});
|
||||
|
||||
syncthing.controller('EventCtrl', function ($scope, $http) {
|
||||
$scope.lastEvent = null;
|
||||
var online = false;
|
||||
var lastID = 0;
|
||||
|
||||
var successFn = function (data) {
|
||||
if (!online) {
|
||||
$scope.$emit('UIOnline');
|
||||
online = true;
|
||||
}
|
||||
|
||||
if (lastID > 0) {
|
||||
data.forEach(function (event) {
|
||||
console.log("event", event.id, event.type, event.data);
|
||||
$scope.$emit(event.type, event);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.lastEvent = data[data.length - 1];
|
||||
lastID = $scope.lastEvent.id;
|
||||
|
||||
setTimeout(function () {
|
||||
$http.get(urlbase + '/events?since=' + lastID)
|
||||
.success(successFn)
|
||||
.error(errorFn);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
var errorFn = function (data) {
|
||||
if (online) {
|
||||
$scope.$emit('UIOffline');
|
||||
online = false;
|
||||
}
|
||||
setTimeout(function () {
|
||||
$http.get(urlbase + '/events?limit=1')
|
||||
.success(successFn)
|
||||
.error(errorFn);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
$http.get(urlbase + '/events?limit=1')
|
||||
.success(successFn)
|
||||
.error(errorFn);
|
||||
});
|
||||
|
||||
syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $location) {
|
||||
@@ -26,20 +71,32 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
var getOK = true;
|
||||
var restarting = false;
|
||||
|
||||
$scope.connections = {};
|
||||
$scope.completion = {};
|
||||
$scope.config = {};
|
||||
$scope.configInSync = true;
|
||||
$scope.connections = {};
|
||||
$scope.errors = [];
|
||||
$scope.model = {};
|
||||
$scope.myID = '';
|
||||
$scope.nodes = [];
|
||||
$scope.configInSync = true;
|
||||
$scope.protocolChanged = false;
|
||||
$scope.errors = [];
|
||||
$scope.seenError = '';
|
||||
$scope.model = {};
|
||||
$scope.repos = {};
|
||||
$scope.reportData = {};
|
||||
$scope.reportPreview = false;
|
||||
$scope.repos = {};
|
||||
$scope.seenError = '';
|
||||
$scope.upgradeInfo = {};
|
||||
|
||||
$http.get(urlbase+"/lang").success(function (langs) {
|
||||
var lang;
|
||||
for (var i = 0; i < langs.length; i++) {
|
||||
lang = langs[i];
|
||||
if (validLangs.indexOf(lang) >= 0) {
|
||||
$translate.use(lang);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$scope.$on("$locationChangeSuccess", function () {
|
||||
var lang = $location.search().lang;
|
||||
if (lang) {
|
||||
@@ -60,53 +117,136 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
'touch': 'asterisk',
|
||||
}
|
||||
|
||||
function getSucceeded() {
|
||||
if (!getOK) {
|
||||
$scope.init();
|
||||
$('#networkError').modal('hide');
|
||||
getOK = true;
|
||||
}
|
||||
if (restarting) {
|
||||
$scope.init();
|
||||
$('#restarting').modal('hide');
|
||||
$('#shutdown').modal('hide');
|
||||
restarting = false;
|
||||
}
|
||||
}
|
||||
$scope.$on('UIOnline', function (event, arg) {
|
||||
console.log('UIOnline');
|
||||
$scope.init();
|
||||
restarting = false;
|
||||
$('#networkError').modal('hide');
|
||||
$('#restarting').modal('hide');
|
||||
$('#shutdown').modal('hide');
|
||||
});
|
||||
|
||||
function getFailed() {
|
||||
if (restarting) {
|
||||
return;
|
||||
}
|
||||
if (getOK) {
|
||||
$scope.$on('UIOffline', function (event, arg) {
|
||||
console.log('UIOffline');
|
||||
if (!restarting) {
|
||||
$('#networkError').modal({backdrop: 'static', keyboard: false});
|
||||
getOK = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('StateChanged', function (event, arg) {
|
||||
var data = arg.data;
|
||||
if ($scope.model[data.repo]) {
|
||||
$scope.model[data.repo].state = data.to;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('LocalIndexUpdated', function (event, arg) {
|
||||
var data = arg.data;
|
||||
refreshRepo(data.repo);
|
||||
|
||||
// Update completion status for all nodes that we share this repo with.
|
||||
$scope.repos[data.repo].Nodes.forEach(function (nodeCfg) {
|
||||
refreshCompletion(nodeCfg.NodeID, data.repo);
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$on('RemoteIndexUpdated', function (event, arg) {
|
||||
var data = arg.data;
|
||||
refreshRepo(data.repo);
|
||||
refreshCompletion(data.node, data.repo);
|
||||
});
|
||||
|
||||
$scope.$on('NodeDisconnected', function (event, arg) {
|
||||
delete $scope.connections[arg.data.id];
|
||||
});
|
||||
|
||||
$scope.$on('NodeConnected', function (event, arg) {
|
||||
if (!$scope.connections[arg.data.id]) {
|
||||
$scope.connections[arg.data.id] = {
|
||||
inbps: 0,
|
||||
outbps: 0,
|
||||
InBytesTotal: 0,
|
||||
OutBytesTotal: 0,
|
||||
Address: arg.data.addr,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('ConfigLoaded', function (event) {
|
||||
if ($scope.config.Options.URAccepted == 0) {
|
||||
// If usage reporting has been neither accepted nor declined,
|
||||
// we want to ask the user to make a choice. But we don't want
|
||||
// to bug them during initial setup, so we set a cookie with
|
||||
// the time of the first visit. When that cookie is present
|
||||
// and the time is more than four hours ago, we ask the
|
||||
// question.
|
||||
|
||||
var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1");
|
||||
if (!firstVisit) {
|
||||
document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30*24*3600;
|
||||
} else {
|
||||
if (+firstVisit < Date.now() - 4*3600*1000){
|
||||
$('#ur').modal({backdrop: 'static', keyboard: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var debouncedFuncs = {};
|
||||
|
||||
function refreshRepo(repo) {
|
||||
var key = "refreshRepo" + repo;
|
||||
if (!debouncedFuncs[key]) {
|
||||
debouncedFuncs[key] = debounce(function () {
|
||||
$http.get(urlbase + '/model?repo=' + encodeURIComponent(repo)).success(function (data) {
|
||||
$scope.model[repo] = data;
|
||||
console.log("refreshRepo", repo, data);
|
||||
});
|
||||
}, 1000, true);
|
||||
}
|
||||
debouncedFuncs[key]();
|
||||
}
|
||||
|
||||
$scope.refresh = function () {
|
||||
function refreshSystem() {
|
||||
$http.get(urlbase + '/system').success(function (data) {
|
||||
getSucceeded();
|
||||
$scope.myID = data.myID;
|
||||
$scope.system = data;
|
||||
}).error(function () {
|
||||
getFailed();
|
||||
console.log("refreshSystem", data);
|
||||
});
|
||||
Object.keys($scope.repos).forEach(function (id) {
|
||||
if (typeof $scope.model[id] === 'undefined') {
|
||||
// Never fetched before
|
||||
$http.get(urlbase + '/model?repo=' + encodeURIComponent(id)).success(function (data) {
|
||||
$scope.model[id] = data;
|
||||
});
|
||||
} else {
|
||||
$http.get(urlbase + '/model/version?repo=' + encodeURIComponent(id)).success(function (data) {
|
||||
if (data.version > $scope.model[id].version) {
|
||||
$http.get(urlbase + '/model?repo=' + encodeURIComponent(id)).success(function (data) {
|
||||
$scope.model[id] = data;
|
||||
});
|
||||
}
|
||||
|
||||
function refreshCompletion(node, repo) {
|
||||
if (node === $scope.myID) {
|
||||
return
|
||||
}
|
||||
|
||||
var key = "refreshCompletion" + node + repo;
|
||||
if (!debouncedFuncs[key]) {
|
||||
debouncedFuncs[key] = debounce(function () {
|
||||
$http.get(urlbase + '/completion?node=' + node + '&repo=' + encodeURIComponent(repo)).success(function (data) {
|
||||
if (!$scope.completion[node]) {
|
||||
$scope.completion[node] = {};
|
||||
}
|
||||
$scope.completion[node][repo] = data.completion;
|
||||
|
||||
var tot = 0, cnt = 0;
|
||||
for (var cmp in $scope.completion[node]) {
|
||||
if (cmp === "_total") {
|
||||
continue;
|
||||
}
|
||||
tot += $scope.completion[node][cmp];
|
||||
cnt += 1;
|
||||
}
|
||||
$scope.completion[node]._total = tot / cnt;
|
||||
|
||||
console.log("refreshCompletion", node, repo, $scope.completion[node]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 1000, true);
|
||||
}
|
||||
debouncedFuncs[key]();
|
||||
}
|
||||
|
||||
function refreshConnectionStats() {
|
||||
$http.get(urlbase + '/connections').success(function (data) {
|
||||
var now = Date.now(),
|
||||
td = (now - prevDate) / 1000,
|
||||
@@ -126,29 +266,83 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
}
|
||||
}
|
||||
$scope.connections = data;
|
||||
console.log("refreshConnections", data);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshErrors() {
|
||||
$http.get(urlbase + '/errors').success(function (data) {
|
||||
$scope.errors = data;
|
||||
console.log("refreshErrors", data);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshConfig() {
|
||||
$http.get(urlbase + '/config').success(function (data) {
|
||||
var hasConfig = !isEmptyObject($scope.config);
|
||||
|
||||
$scope.config = data;
|
||||
$scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(', ');
|
||||
|
||||
$scope.nodes = $scope.config.Nodes;
|
||||
$scope.nodes.sort(nodeCompare);
|
||||
|
||||
$scope.repos = repoMap($scope.config.Repositories);
|
||||
Object.keys($scope.repos).forEach(function (repo) {
|
||||
refreshRepo(repo);
|
||||
$scope.repos[repo].Nodes.forEach(function (nodeCfg) {
|
||||
refreshCompletion(nodeCfg.NodeID, repo);
|
||||
});
|
||||
});
|
||||
|
||||
if (!hasConfig) {
|
||||
$scope.$emit('ConfigLoaded');
|
||||
}
|
||||
|
||||
console.log("refreshConfig", data);
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.init = function() {
|
||||
refreshSystem();
|
||||
refreshConfig();
|
||||
refreshConnectionStats();
|
||||
|
||||
$http.get(urlbase + '/version').success(function (data) {
|
||||
$scope.version = data;
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/report').success(function (data) {
|
||||
$scope.reportData = data;
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/upgrade').success(function (data) {
|
||||
$scope.upgradeInfo = data;
|
||||
}).error(function () {
|
||||
$scope.upgradeInfo = {};
|
||||
});
|
||||
};
|
||||
|
||||
$scope.refresh = function () {
|
||||
refreshSystem();
|
||||
refreshConnectionStats();
|
||||
refreshErrors();
|
||||
};
|
||||
|
||||
$scope.repoStatus = function (repo) {
|
||||
if (typeof $scope.model[repo] === 'undefined') {
|
||||
return 'Unknown';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
if ($scope.model[repo].invalid !== '') {
|
||||
return 'Stopped';
|
||||
return 'stopped';
|
||||
}
|
||||
|
||||
var state = '' + $scope.model[repo].state;
|
||||
state = state[0].toUpperCase() + state.substr(1);
|
||||
|
||||
if (state == "Syncing" || state == "Idle") {
|
||||
state += " (" + $scope.syncPercentage(repo) + "%)";
|
||||
}
|
||||
|
||||
return state;
|
||||
return '' + $scope.model[repo].state;
|
||||
};
|
||||
|
||||
$scope.repoClass = function (repo) {
|
||||
@@ -182,23 +376,9 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
return Math.floor(pct);
|
||||
};
|
||||
|
||||
$scope.nodeStatus = function (nodeCfg) {
|
||||
var conn = $scope.connections[nodeCfg.NodeID];
|
||||
if (conn) {
|
||||
if (conn.Completion === 100) {
|
||||
return 'Up to Date';
|
||||
} else {
|
||||
return 'Syncing (' + conn.Completion + '%)';
|
||||
}
|
||||
}
|
||||
|
||||
return 'Disconnected';
|
||||
};
|
||||
|
||||
$scope.nodeIcon = function (nodeCfg) {
|
||||
var conn = $scope.connections[nodeCfg.NodeID];
|
||||
if (conn) {
|
||||
if (conn.Completion === 100) {
|
||||
if ($scope.connections[nodeCfg.NodeID]) {
|
||||
if ($scope.completion[nodeCfg.NodeID] && $scope.completion[nodeCfg.NodeID]._total === 100) {
|
||||
return 'ok';
|
||||
} else {
|
||||
return 'refresh';
|
||||
@@ -209,9 +389,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
};
|
||||
|
||||
$scope.nodeClass = function (nodeCfg) {
|
||||
var conn = $scope.connections[nodeCfg.NodeID];
|
||||
if (conn) {
|
||||
if (conn.Completion === 100) {
|
||||
if ($scope.connections[nodeCfg.NodeID]) {
|
||||
if ($scope.completion[nodeCfg.NodeID] && $scope.completion[nodeCfg.NodeID]._total === 100) {
|
||||
return 'success';
|
||||
} else {
|
||||
return 'primary';
|
||||
@@ -381,7 +560,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
};
|
||||
|
||||
$scope.addNode = function () {
|
||||
$scope.currentNode = {AddressesStr: 'dynamic'};
|
||||
$scope.currentNode = {AddressesStr: 'dynamic', Compression: true};
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingSelf = false;
|
||||
$scope.nodeEditor.$setPristine();
|
||||
@@ -561,60 +740,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
cfg.APIKey = randomString(30, 32);
|
||||
};
|
||||
|
||||
$scope.init = function() {
|
||||
$http.get(urlbase + '/version').success(function (data) {
|
||||
$scope.version = data;
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/system').success(function (data) {
|
||||
$scope.system = data;
|
||||
$scope.myID = data.myID;
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/config').success(function (data) {
|
||||
$scope.config = data;
|
||||
$scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(', ');
|
||||
|
||||
$scope.nodes = $scope.config.Nodes;
|
||||
$scope.nodes.sort(nodeCompare);
|
||||
|
||||
$scope.repos = repoMap($scope.config.Repositories);
|
||||
|
||||
$scope.refresh();
|
||||
|
||||
if ($scope.config.Options.URAccepted == 0) {
|
||||
// If usage reporting has been neither accepted nor declined,
|
||||
// we want to ask the user to make a choice. But we don't want
|
||||
// to bug them during initial setup, so we set a cookie with
|
||||
// the time of the first visit. When that cookie is present
|
||||
// and the time is more than four hours ago, we ask the
|
||||
// question.
|
||||
|
||||
var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1");
|
||||
if (!firstVisit) {
|
||||
document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30*24*3600;
|
||||
} else {
|
||||
if (+firstVisit < Date.now() - 4*3600*1000){
|
||||
$('#ur').modal({backdrop: 'static', keyboard: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/report').success(function (data) {
|
||||
$scope.reportData = data;
|
||||
});
|
||||
|
||||
$http.get(urlbase + '/upgrade').success(function (data) {
|
||||
$scope.upgradeInfo = data;
|
||||
}).error(function () {
|
||||
$scope.upgradeInfo = {};
|
||||
});
|
||||
};
|
||||
|
||||
$scope.acceptUR = function () {
|
||||
$scope.config.Options.URAccepted = 1000; // Larger than the largest existing report version
|
||||
@@ -726,6 +852,48 @@ function randomString(len, bits)
|
||||
return outStr.toLowerCase();
|
||||
}
|
||||
|
||||
function isEmptyObject(obj) {
|
||||
var name;
|
||||
for (name in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function debounce(func, wait) {
|
||||
var timeout, args, context, timestamp, result, again;
|
||||
|
||||
var later = function() {
|
||||
var last = Date.now() - timestamp;
|
||||
if (last < wait) {
|
||||
timeout = setTimeout(later, wait - last);
|
||||
} else {
|
||||
timeout = null;
|
||||
if (again) {
|
||||
result = func.apply(context, args);
|
||||
context = args = null;
|
||||
again = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return function() {
|
||||
context = this;
|
||||
args = arguments;
|
||||
timestamp = Date.now();
|
||||
var callNow = !timeout;
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(later, wait);
|
||||
result = func.apply(context, args);
|
||||
context = args = null;
|
||||
} else {
|
||||
again = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
syncthing.filter('natural', function () {
|
||||
return function (input, valid) {
|
||||
return input.toFixed(decimals(input, valid));
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div ng-controller="EventCtrl"></div>
|
||||
|
||||
<!-- Top bar -->
|
||||
|
||||
@@ -153,7 +154,19 @@
|
||||
<h3 class="panel-title">
|
||||
<a data-toggle="collapse" data-parent="#repositories" href="#repo-{{$index}}">
|
||||
<span class="glyphicon glyphicon-hdd"></span> {{repo.Directory | shortPath}}
|
||||
<span class="pull-right hidden-xs">{{repoStatus(repo.ID)}}</span>
|
||||
<span class="pull-right hidden-xs" ng-switch="repoStatus(repo.ID)">
|
||||
<span translate ng-switch-when="unknown">Unknown</span>
|
||||
<span translate ng-switch-when="stopped">Stopped</span>
|
||||
<span translate ng-switch-when="scanning">Scanning</span>
|
||||
<span ng-switch-when="syncing">
|
||||
<span translate>Syncing</span>
|
||||
({{syncPercentage(repo.ID)}}%)
|
||||
</span>
|
||||
<span ng-switch-when="idle">
|
||||
<span translate>Idle</span>
|
||||
({{syncPercentage(repo.ID)}}%)
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -174,10 +187,6 @@
|
||||
<th><span class="glyphicon glyphicon-warning-sign"></span> <span translate>Error</span></th>
|
||||
<td class="text-right">{{model[repo.ID].invalid}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-comment"></span> <span translate>Synchronization</span></th>
|
||||
<td class="text-right">{{repoStatus(repo.ID)}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-globe"></span> <span translate>Global Repository</span></th>
|
||||
<td class="text-right">{{model[repo.ID].globalFiles | alwaysNumber}} <span translate>items</span>, {{model[repo.ID].globalBytes | binary}}B</td>
|
||||
@@ -280,7 +289,15 @@
|
||||
<a data-toggle="collapse" data-parent="#nodes" href="#node-{{$index}}">
|
||||
<span class="glyphicon glyphicon-retweet"></span>
|
||||
{{nodeName(nodeCfg)}}
|
||||
<span class="pull-right hidden-xs">{{nodeStatus(nodeCfg)}}</span>
|
||||
<span class="pull-right hidden-xs">
|
||||
<span ng-if="connections[nodeCfg.NodeID] && completion[nodeCfg.NodeID]._total == 100">
|
||||
<span translate>Up to Date</span> (100%)
|
||||
</span>
|
||||
<span ng-if="connections[nodeCfg.NodeID] && completion[nodeCfg.NodeID]._total < 100">
|
||||
<span translate>Syncing</span> ({{completion[nodeCfg.NodeID]._total | number:0}}%)
|
||||
</span>
|
||||
<span translate ng-if="!connections[nodeCfg.NodeID]">Disconnected</span>
|
||||
</span>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -295,7 +312,12 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-comment"></span> <span translate>Synchronization</span></th>
|
||||
<td class="text-right">{{nodeStatus(nodeCfg)}}</td>
|
||||
<td class="text-right">{{completion[nodeCfg.NodeID]._total | alwaysNumber | number:0}}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-compressed"></span> <span translate>Use Compression</span></th>
|
||||
<td translate ng-if="nodeCfg.Compression" class="text-right">Yes</td>
|
||||
<td translate ng-if="!nodeCfg.Compression" class="text-right">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="glyphicon glyphicon-cloud-download"></span> <span translate>Download Rate</span></th>
|
||||
@@ -412,6 +434,13 @@
|
||||
<input placeholder="dynamic" ng-disabled="currentNode.NodeID == myID" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
|
||||
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="currentNode.Compression"> <span translate>Use Compression</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -686,6 +715,7 @@
|
||||
<li>Aaron Bieber</li>
|
||||
<li>Andrew Dunham</li>
|
||||
<li>Arthur Axel fREW Schmidt</li>
|
||||
<li>Audrius Butkevicius</li>
|
||||
<li>Ben Sidhom</li>
|
||||
<li>Brandon Philips</li>
|
||||
</ul>
|
||||
|
||||
112
gui/lang-de.json
Normal file
112
gui/lang-de.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"API Key": "API-Schlüssel",
|
||||
"About": "Über",
|
||||
"Add Node": "Knoten hinzufügen",
|
||||
"Add Repository": "Verzeichnis hinzufügen",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adressen",
|
||||
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsstatistiken erlauben?",
|
||||
"Announce Server": "Ankündigungs-Server",
|
||||
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
|
||||
"Bugs": "Fehler",
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Close": "Schließen",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg und folgende Unterstützer:",
|
||||
"Delete": "Löschen",
|
||||
"Disconnected": "Verbindung getrennt",
|
||||
"Documentation": "Dokumentation",
|
||||
"Download Rate": "Downloadgeschwindigkeit",
|
||||
"Edit": "Bearbeiten",
|
||||
"Edit Node": "Knoten bearbeiten",
|
||||
"Edit Repository": "Verzeichnis ändern",
|
||||
"Enable UPnP": "Aktiviere UPnP",
|
||||
"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.",
|
||||
"Error": "Fehler",
|
||||
"File Versioning": "Dateiversionierung",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen verwenden.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Dateien werden beim Löschen oder Ersetzen als datierte Versionen in einen .stversions -Ordner verschoben.",
|
||||
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Knoten geschützt, auf diesem Knoten durchgeführte Veränderungen werden aber auf den Rest des Netzwerks übertragen.",
|
||||
"Folder": "Ordner",
|
||||
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
|
||||
"GUI Authentication User": "Nutzername für Zugang zur Benutzeroberfläche.",
|
||||
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
|
||||
"Generate": "Generiere",
|
||||
"Global Discovery": "Globale Auffindung",
|
||||
"Global Repository": "Globales Verzeichnis",
|
||||
"Idle": "Untätig",
|
||||
"Ignore Permissions": "Berechtigungen ignorieren",
|
||||
"Keep Versions": "Versionen erhalten",
|
||||
"Latest Release": "Letzte Veröffentlichung",
|
||||
"Local Discovery": "Lokale Auffindung",
|
||||
"Local Discovery Port": "Lokaler Aufindungsport",
|
||||
"Local Repository": "Lokales Verzeichnis",
|
||||
"Master Repo": "Veränderungen zugelassen",
|
||||
"Max File Change Rate (KiB/s)": "Maximale Datenänderungsrate (KiB/s)",
|
||||
"Max Outstanding Requests": "Max. ausstehende Anfragen",
|
||||
"No": "Nein",
|
||||
"Node ID": "Knoten-ID",
|
||||
"Node Name": "Knoten-Name",
|
||||
"Notice": "Benachrichtigung",
|
||||
"OK": "Ok",
|
||||
"Offline": "Offline",
|
||||
"Online": "Online",
|
||||
"Out Of Sync": "Nicht synchronisiert",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ausgehendes Datenratelimit (KiB/s)",
|
||||
"Override Changes": "Änderungen überschreiben",
|
||||
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Pfad zum Verzeichnis auf dem lokalen Rechner. Wird erzeugt, wenn es nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
|
||||
"Please wait": "Bitte warten",
|
||||
"Preview Usage Report": "Vorschau des Nutzungsberichts",
|
||||
"RAM Utilization": "Verwendeter Arbeitsspeicher",
|
||||
"Reconnect Interval (s)": "Wiederverbindungsintervall (s)",
|
||||
"Repository ID": "Verzeichnis-ID",
|
||||
"Repository Master": "Keine Veränderungen zulassen",
|
||||
"Repository Path": "Pfad zum Verzeichnis",
|
||||
"Rescan Interval (s)": "Suchintervall (s)",
|
||||
"Restart": "Neustart",
|
||||
"Restart Needed": "Neustart notwendig",
|
||||
"Save": "Speichern",
|
||||
"Scanning": "Überprüfe",
|
||||
"Select the nodes to share this repository with.": "Wähle die Knoten aus, mit denen du dieses Verzeichnis teilen willst.",
|
||||
"Settings": "Einstellungen",
|
||||
"Share With Nodes": "Teile mit diesen Knoten",
|
||||
"Shared With": "Geteilt mit",
|
||||
"Short identifier for the repository. Must be the same on all cluster nodes.": "Kurze ID für das Verzeichnis. Muss auf allen Verbunds-Knoten gleich sein.",
|
||||
"Show ID": "ID anzeigen",
|
||||
"Shown instead of Node ID in the cluster status.": "Wird anstatt der Knoten-ID im Verbunds-Status angezeigt.",
|
||||
"Shutdown": "Herunterfahren",
|
||||
"Source Code": "Quellcode",
|
||||
"Start Browser": "Starte Browser",
|
||||
"Stopped": "Gestoppt",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Adresse(n) für das Synchronisierungsprotokoll",
|
||||
"Synchronization": "Synchronisierung",
|
||||
"Syncing": "Synchronisiere",
|
||||
"Syncthing has been shut down.": "Syncthing wurde heruntergefahren.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing enthält die folgende Software oder Teile davon:",
|
||||
"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 Ihrer Internetverbindung. Versuche erneut...",
|
||||
"The aggregated statistics are publicly available at {{url}}": "Die aggregierten 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 encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Benutzungsbericht wird täglich gesendet. Er wird benutzt um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sobald der Bericht in Zukunft weitere Daten erfasst, wird dir dieses Fenster erneut angezeigt.",
|
||||
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "Die eingegebene Knoten-ID scheint nicht gültig zu sein. Sie sollte eine 52 Stellen lange Zeichenkette aus Buchstaben und Zahlen sein. Leerzeichen und Striche sind optional (werden ignoriert).",
|
||||
"The node ID cannot be blank.": "Die Knoten-ID darf nicht leer sein.",
|
||||
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "Die hier einzutragende Knoten-ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Knoten gefunden werden. Leerzeichen und Striche sind optional (werden ignoriert).",
|
||||
"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 sein und darf nicht leer sein.",
|
||||
"The repository ID cannot be blank.": "Die Verzeichnis-ID darf nicht leer sein.",
|
||||
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis-ID muss eine kurze Kennung (64 Zeichen oder weniger) sein. Sie kann aus Buchstaben, Zahlen und den Punkt- (.), Strich- (-), und Unterstrich- (_) Zeichen bestehen.",
|
||||
"The repository ID must be unique.": "Die Verzeichnis-ID muss eindeutig sein.",
|
||||
"The repository path cannot be blank.": "Der Verzeichnis-Pfad kann nicht leer sein",
|
||||
"Unknown": "Unbekannt",
|
||||
"Up to Date": "Aktuell",
|
||||
"Upgrade To {%version%}": "Upgrade auf {{version}}",
|
||||
"Upload Rate": "Uploadgeschwindigkeit",
|
||||
"Usage": "Benutzung",
|
||||
"Use Compression": "Benutze Komprimierung",
|
||||
"Use HTTPS for GUI": "Benutze HTTPS für Benutzeroberfläche",
|
||||
"Version": "Version",
|
||||
"When adding a new node, keep in mind that this node must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Knotens, dass dieser Knoten auch auf der Gegenseite hinzugefügt werden muss.",
|
||||
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Beim Hinzufügen eines neuen Verzeichnisses, beachte dass die Verzeichnis-ID dazu verwendet wird, Verzeichnisse zwischen Knoten zu verbinden. Die ID muss also auf allen Knoten gleich sein, Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
|
||||
"items": "Einträge"
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"Close": "Close",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
|
||||
"Delete": "Delete",
|
||||
"Disconnected": "Disconnected",
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Download Rate",
|
||||
"Edit": "Edit",
|
||||
@@ -32,6 +33,7 @@
|
||||
"Generate": "Generate",
|
||||
"Global Discovery": "Global Discovery",
|
||||
"Global Repository": "Global Repository",
|
||||
"Idle": "Idle",
|
||||
"Ignore Permissions": "Ignore Permissions",
|
||||
"Keep Versions": "Keep Versions",
|
||||
"Latest Release": "Latest Release",
|
||||
@@ -63,6 +65,7 @@
|
||||
"Restart": "Restart",
|
||||
"Restart Needed": "Restart Needed",
|
||||
"Save": "Save",
|
||||
"Scanning": "Scanning",
|
||||
"Select the nodes to share this repository with.": "Select the nodes to share this repository with.",
|
||||
"Settings": "Settings",
|
||||
"Share With Nodes": "Share With Nodes",
|
||||
@@ -73,9 +76,11 @@
|
||||
"Shutdown": "Shutdown",
|
||||
"Source Code": "Source Code",
|
||||
"Start Browser": "Start Browser",
|
||||
"Stopped": "Stopped",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
|
||||
"Synchronization": "Synchronization",
|
||||
"Syncing": "Syncing",
|
||||
"Syncthing has been shut down.": "Syncthing has been shut down.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
|
||||
@@ -91,9 +96,12 @@
|
||||
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.",
|
||||
"The repository ID must be unique.": "The repository ID must be unique.",
|
||||
"The repository path cannot be blank.": "The repository path cannot be blank.",
|
||||
"Unknown": "Unknown",
|
||||
"Up to Date": "Up to Date",
|
||||
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
||||
"Upload Rate": "Upload Rate",
|
||||
"Usage": "Usage",
|
||||
"Use Compression": "Use Compression",
|
||||
"Use HTTPS for GUI": "Use HTTPS for GUI",
|
||||
"Version": "Version",
|
||||
"When adding a new node, keep in mind that this node must be added on the other side too.": "When adding a new node, keep in mind that this node must be added on the other side too.",
|
||||
@@ -101,4 +109,4 @@
|
||||
"Yes": "Yes",
|
||||
"You must keep at least one version.": "You must keep at least one version.",
|
||||
"items": "items"
|
||||
}
|
||||
}
|
||||
|
||||
112
gui/lang-es.json
Normal file
112
gui/lang-es.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"API Key": "Clave API",
|
||||
"About": "Acerca de",
|
||||
"Add Node": "Añadir nodo",
|
||||
"Add Repository": "Añadir repositorio",
|
||||
"Address": "Dirección",
|
||||
"Addresses": "Direcciones",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir reporte anónimo de uso?",
|
||||
"Announce Server": "Anunciar servidor",
|
||||
"Anonymous Usage Reporting": "Reporte anónimo de uso",
|
||||
"Bugs": "Errores",
|
||||
"CPU Utilization": "Uso de la CPU",
|
||||
"Close": "Cerrar",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Derechos de autor © 2014 Jakob Borg y los siguientes colaboradores:",
|
||||
"Delete": "Suprimir",
|
||||
"Disconnected": "Desconectado",
|
||||
"Documentation": "Documentación",
|
||||
"Download Rate": "Tasa de descarga",
|
||||
"Edit": "Editar",
|
||||
"Edit Node": "Editar nodo",
|
||||
"Edit Repository": "Editar repositorio",
|
||||
"Enable UPnP": "Permitir UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Ingrese las direcciones \"ip:port\" separadas por coma o \"dynamic\" para descubrir automáticamente las direcciones.",
|
||||
"Error": "Error",
|
||||
"File Versioning": "Control de versiones",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Los permisos de archivo son ignorados al buscar cambios. Usar en sistemas de archivos FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Los archivos son movidos al directorio .stversions y renombrados a versiones marcadas por fecha cuando son reemplazados o eliminados por Syncthing,",
|
||||
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Los archivos están protegidos de cambios en otros nodos, pero los cambios realizados en este nodo serán enviados al resto de los nodos.",
|
||||
"Folder": "Carpeta",
|
||||
"GUI Authentication Password": "Contraseña de autenticación de la GUI",
|
||||
"GUI Authentication User": "Usuario de la GUI",
|
||||
"GUI Listen Addresses": "Direcciones de escucha para la GUI.",
|
||||
"Generate": "Generar",
|
||||
"Global Discovery": "Búsqueda en internet",
|
||||
"Global Repository": "Repositorio global",
|
||||
"Idle": "Inactivo",
|
||||
"Ignore Permissions": "Ignorar permisos",
|
||||
"Keep Versions": "Conservar versiones",
|
||||
"Latest Release": "Última versión",
|
||||
"Local Discovery": "Búsqueda en red local",
|
||||
"Local Discovery Port": "Puerto de búsqueda de red local",
|
||||
"Local Repository": "Repositorio local.",
|
||||
"Master Repo": "Repositorio maestro",
|
||||
"Max File Change Rate (KiB/s)": "Tasa máxima de cambios (KiB/s)",
|
||||
"Max Outstanding Requests": "Cantidad máxima de peticiones pendientes",
|
||||
"No": "No",
|
||||
"Node ID": "Nodo ID",
|
||||
"Node Name": "Nodo nombre",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Offline": "Fuera de linea",
|
||||
"Online": "En linea",
|
||||
"Out Of Sync": "Fuera de sincronización",
|
||||
"Outgoing Rate Limit (KiB/s)": "Tasa máxima de envío (KiB/s)",
|
||||
"Override Changes": "Reemplazar los cambios",
|
||||
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta del repositorio en el equipo local. La carpeta sera creada si no existe. El carácter tilde (~) puede ser utilizado como atajo de ",
|
||||
"Please wait": "Aguarde por favor",
|
||||
"Preview Usage Report": "Ver reporte de uso",
|
||||
"RAM Utilization": "Utilización de RAM",
|
||||
"Reconnect Interval (s)": "Intervalo de reconexión (s)",
|
||||
"Repository ID": "ID de repositorio",
|
||||
"Repository Master": "Repositorio maestro",
|
||||
"Repository Path": "Ruta del repositorio",
|
||||
"Rescan Interval (s)": "Intervalo de reescaneo (s)",
|
||||
"Restart": "Reiniciar",
|
||||
"Restart Needed": "Es necesario reiniciar",
|
||||
"Save": "Guardar",
|
||||
"Scanning": "Actualización",
|
||||
"Select the nodes to share this repository with.": "Seleccione los nodos con los cuales compartir el repositorio.",
|
||||
"Settings": "Configuración",
|
||||
"Share With Nodes": "Compartir con los nodos",
|
||||
"Shared With": "Compartido con",
|
||||
"Short identifier for the repository. Must be the same on all cluster nodes.": "Identificador corto para el repositorio. Debe ser el mismo en todos los nodos del clúster.",
|
||||
"Show ID": "Mostrar ID",
|
||||
"Shown instead of Node ID in the cluster status.": "Mostrar en lugar de ID de nodo en estado de cluster.",
|
||||
"Shutdown": "Apagar",
|
||||
"Source Code": "Código fuente",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
"Stopped": "Parado",
|
||||
"Support / Forum": "Soporte / Foro",
|
||||
"Sync Protocol Listen Addresses": "Dirección de escucha del protocolo de sincronización",
|
||||
"Synchronization": "Sincronización",
|
||||
"Syncing": "Sincronización",
|
||||
"Syncthing has been shut down.": "La sincronización esta apagada",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing incluye los siguientes softwares o partes de ellos:",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing parece estar apagado, o hay un problema con su conexión de Internet. Reintentando...",
|
||||
"The aggregated statistics are publicly available at {{url}}": "Las estadísticas acumuladas están públicamente disponibles en {{url}}",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuración ha sido guardada pero no activada.\nSyncthing debe reiniciarse para activar la nueva configuración.",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "El reporte de uso se envía encriptado diariamente. Se utiliza para hacer un seguimiento de plataformas comunes, tamaño de repositorios y versión de aplicaciones. Si el conjunto de datos cambia sera notificado mediante este dialogo nuevamente.",
|
||||
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "El ID de nodo ingresado no es valido. Debe ser una cadena de al menos 52 caracteres consistente en letras y números, con espacios y guiones opcionales.",
|
||||
"The node ID cannot be blank.": "El ID de nodo no puede estar vacío.",
|
||||
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "El ID de nodo a ingresar aquí puede verse en la opción de menú \"Edición > Mostrar ID\" del otro nodo. Espacios y guiones son opcionales (ignorados).",
|
||||
"The number of old versions to keep, per file.": "El numero de versiones anteriores a conservar, por archivo.",
|
||||
"The number of versions must be a number and cannot be blank.": "El número de versiones debe ser un número y no puede estar vacío.",
|
||||
"The repository ID cannot be blank.": "El ID de repositorio no puede estar vacio.",
|
||||
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "El ID de repositorio debe ser una cadena corta (64 caracteres o menos) consistente solamente en letras, números, punto (.), guion (-) y guion bajo (_).",
|
||||
"The repository ID must be unique.": "El ID de repositorio debe ser único.",
|
||||
"The repository path cannot be blank.": "La ruta del repositorio no puede estar vacía.",
|
||||
"Unknown": "Desconocido",
|
||||
"Up to Date": "Actualizado",
|
||||
"Upgrade To {%version%}": "Actualizar a {{version}}",
|
||||
"Upload Rate": "Tasa de subida",
|
||||
"Usage": "Utilización",
|
||||
"Use Compression": "Use Compression",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para la GUI",
|
||||
"Version": "Versión",
|
||||
"When adding a new node, keep in mind that this node must be added on the other side too.": "Al agregar un nuevo nodo, recuerde que este nodo debe ser agregado en el otro lado también.",
|
||||
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Al agregar un nuevo repositorio, tenga en mente que el ID de repositorio se utiliza para ligar los repositorios entre nodos. Distingue mayúsculas y minúsculas y debe ser exactamente igual en todos los nodos.",
|
||||
"Yes": "Si",
|
||||
"You must keep at least one version.": "Debe mantener al menos una versión",
|
||||
"items": "items"
|
||||
}
|
||||
112
gui/lang-fr.json
Normal file
112
gui/lang-fr.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"API Key": "Clé API",
|
||||
"About": "À propos",
|
||||
"Add Node": "Ajouter un nœud",
|
||||
"Add Repository": "Ajouter un répertoire",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adresses",
|
||||
"Allow Anonymous Usage Reporting?": "Autoriser le rapport anonyme de statistiques d'utilisation?",
|
||||
"Announce Server": "Serveur d'annonce",
|
||||
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Utilisation du CPU",
|
||||
"Close": "Fermer",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg et les contributeurs suivants:",
|
||||
"Delete": "Supprimer",
|
||||
"Disconnected": "Déconnecté",
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Débit Download",
|
||||
"Edit": "Éditer",
|
||||
"Edit Node": "Éditer le nœud",
|
||||
"Edit Repository": "Éditer le répertoire",
|
||||
"Enable UPnP": "Activer l'UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Entrer les adresses \"ip:port\" séparées par une virgule ou \"dynamic\" afin d'activer la recherche automatique de l'adresse.",
|
||||
"Error": "Erreur",
|
||||
"File Versioning": "Versions de fichier",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Les permissions de fichier sont ignorées lors de la recherche de changements. À utiliser sur les systèmes de fichiers en FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Les fichiers sont datés et déplacés dans le dossier .stversions lors de leurs remplacements ou suppressions par syncthing.",
|
||||
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Les fichiers sont protégés des changements réalisés sur les autres nœuds, mais les changements réalisés sur ce nœud seront transférés au reste du cluster.",
|
||||
"Folder": "Dossier",
|
||||
"GUI Authentication Password": "Mot de passe d'authentification GUI",
|
||||
"GUI Authentication User": "Utilistateur autorisé GUI",
|
||||
"GUI Listen Addresses": "Adresse du GUI",
|
||||
"Generate": "Générer",
|
||||
"Global Discovery": "Recherche globale",
|
||||
"Global Repository": "Répertoire global",
|
||||
"Idle": "Au repos",
|
||||
"Ignore Permissions": "Ignorer les permissions",
|
||||
"Keep Versions": "Conserver les versions",
|
||||
"Latest Release": "Dernière version",
|
||||
"Local Discovery": "Recherche locale",
|
||||
"Local Discovery Port": "Port de recherche locale",
|
||||
"Local Repository": "Dossier local",
|
||||
"Master Repo": "Dossier maître",
|
||||
"Max File Change Rate (KiB/s)": "Débit maximum de changement de fichier (KiB/s)",
|
||||
"Max Outstanding Requests": "Nombre maximum de demandes conccurentes de blocs de fichier",
|
||||
"No": "Non",
|
||||
"Node ID": "ID du nœud",
|
||||
"Node Name": "Nom du nœud",
|
||||
"Notice": "Notification",
|
||||
"OK": "OK",
|
||||
"Offline": "Offline",
|
||||
"Online": "Online",
|
||||
"Out Of Sync": "Non synchronisé",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
|
||||
"Override Changes": "Écraser les changements",
|
||||
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin du répertoire sur l'ordinateur local. Il sera créé si il n'existe pas. Le caractère tilde (~) peut être utilisé comme raccourci vers",
|
||||
"Please wait": "Merci de patienter",
|
||||
"Preview Usage Report": "Aperçu du rapport de statistiques d'utilisation",
|
||||
"RAM Utilization": "Utilisation de la RAM",
|
||||
"Reconnect Interval (s)": "Intervalle de reconnexion (s)",
|
||||
"Repository ID": "ID du répertoire",
|
||||
"Repository Master": "Répertoire maître",
|
||||
"Repository Path": "Chemin du répertoire",
|
||||
"Rescan Interval (s)": "Intervalle de rescan (s)",
|
||||
"Restart": "Redémarrer",
|
||||
"Restart Needed": "Redémarrage nécessaire",
|
||||
"Save": "Sauver",
|
||||
"Scanning": "Scanning",
|
||||
"Select the nodes to share this repository with.": "Sélectionner les nœuds qui partageront ce répertoire.",
|
||||
"Settings": "Configuration",
|
||||
"Share With Nodes": "Partager avec les nœuds",
|
||||
"Shared With": "Partagé avec",
|
||||
"Short identifier for the repository. Must be the same on all cluster nodes.": "Identifiant court pour le répertoire. Il doit être le même sur l'ensemble des nœuds du cluster.",
|
||||
"Show ID": "Montrer l'ID",
|
||||
"Shown instead of Node ID in the cluster status.": "Affiché à la place de l'ID du nœud au sein du cluster.",
|
||||
"Shutdown": "Éteindre",
|
||||
"Source Code": "Code source",
|
||||
"Start Browser": "Démarrer le navigateur web",
|
||||
"Stopped": "Arrêté",
|
||||
"Support / Forum": "Aide / Forum",
|
||||
"Sync Protocol Listen Addresses": "Adresse du protocole de synchronisation",
|
||||
"Synchronization": "Synchronisation",
|
||||
"Syncing": "En cours de synchronisation",
|
||||
"Syncthing has been shut down.": "Syncthing a été éteint.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inclut les logiciels, ou portion de ceux-ci, suivants:",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être éteint, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
|
||||
"The aggregated statistics are publicly available at {{url}}": "Les statistiques agrégées sont disponibles publiquement à l'adresse {{url}}",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuration a été sauvée mais pas activée. Syncthing doit redémarrer afin d'activer la nouvelle configuration.",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Le rapport d'utilisation chiffré est envoyé quotidiennement. Il sert à répertorier les plateformes utilisées, la taille des répertoires et les versions de l'application. Si le jeu de données rapportées devait être changé, il vous sera demandé de le valider de nouveau via ce dialogue.",
|
||||
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "L'ID du nœud ne semble pas être valide. Il devrait ressembler à une chaine de 52 caractères comprenant lettres et chiffres, avec des espaces et des traits d'union optionnels.",
|
||||
"The node ID cannot be blank.": "L'ID du nœud ne peut être vide.",
|
||||
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "L'ID du nœud à insérer peut être trouvé à travers le menu \"Éditer > Montrer l'ID\" des autres nœuds. Les espaces et les traits d'union sont optionnels (ils seront ignorés).",
|
||||
"The number of old versions to keep, per file.": "Le nombre d'anciennes versions à garder, par fichier.",
|
||||
"The number of versions must be a number and cannot be blank.": "Le nombre de version doit être un nombre et ne peut pas être vide.",
|
||||
"The repository ID cannot be blank.": "L'ID du répertoire ne peut être vide.",
|
||||
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "L'ID du répertoire doit un identifiant court (64 caractères ou moins) comprenant des lettres, nombres, points (.), trait d'union (-) et tiret bas (_).",
|
||||
"The repository ID must be unique.": "L'ID du répertoire doit être unique.",
|
||||
"The repository path cannot be blank.": "Le chemin du répertoire ne peut pas être vide.",
|
||||
"Unknown": "Inconnu",
|
||||
"Up to Date": "Synchronistation à jour",
|
||||
"Upgrade To {%version%}": "Upgrader vers {{version}}",
|
||||
"Upload Rate": "Débit Upload",
|
||||
"Usage": "Utilisation",
|
||||
"Use Compression": "Utiliser la compression",
|
||||
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
|
||||
"Version": "Version",
|
||||
"When adding a new node, keep in mind that this node must be added on the other side too.": "Lorsqu'un nœud est ajouté, gardez à l'esprit que ce nœud doit aussi être ajouté de l'autre coté.",
|
||||
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Lorsqu'un nouveau répertoire est ajouté, gardez à l'esprit que l'ID du répertoire est utilisé pour lier les répertoires à travers les nœuds. Ils sont sensibles à la case et doivent être semblables à travers tous les nœuds.",
|
||||
"Yes": "Oui",
|
||||
"You must keep at least one version.": "Vous devez garder au minimum une version.",
|
||||
"items": "éléments"
|
||||
}
|
||||
112
gui/lang-pt.json
Normal file
112
gui/lang-pt.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"API Key": "Chave API",
|
||||
"About": "Acerca de",
|
||||
"Add Node": "Adicionar Nó",
|
||||
"Add Repository": "Adicionar Repositório",
|
||||
"Address": "Endereço",
|
||||
"Addresses": "Endereços",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir Envio de Relatórios Anónimos?",
|
||||
"Announce Server": "Servidor de anúncios",
|
||||
"Anonymous Usage Reporting": "Envio de Relatórios Anónimos",
|
||||
"Bugs": "Erros",
|
||||
"CPU Utilization": "Utilização da CPU",
|
||||
"Close": "Fechar",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Direitos Reservados © 2014 Jakob Borg e os seguintes Contribuidores:",
|
||||
"Delete": "Apagar",
|
||||
"Disconnected": "Desconectado",
|
||||
"Documentation": "Documentação",
|
||||
"Download Rate": "Taxa de transferência",
|
||||
"Edit": "Editar",
|
||||
"Edit Node": "Editar Nó",
|
||||
"Edit Repository": "Editar Repositório",
|
||||
"Enable UPnP": "Ativar UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza endereços \"ip:porto\" separados por virgulas ou \"dynamic\" para o descobrimento automático do endereço.",
|
||||
"Error": "Erro",
|
||||
"File Versioning": "Gestão de versões",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "As permissões do ficheiro são ignoradas na pesquisa por mudanças. Utilize nos sistemas de ficheiros FAT.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Os ficheiros são movidos para versões carimbadas com o tempo numa pasta .stversions quando substituídos ou apagados por syncthing.",
|
||||
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Os ficheiros são protegidos de mudanças feitas em outros nós, mas alterações feitas neste nó serão enviadas para o resto do agrupamento.",
|
||||
"Folder": "Pasta",
|
||||
"GUI Authentication Password": "Senha de Autenticação GUI",
|
||||
"GUI Authentication User": "Utilizador de autenticação GUI",
|
||||
"GUI Listen Addresses": "Endereço de escuta GUI",
|
||||
"Generate": "Gerar",
|
||||
"Global Discovery": "Descoberta Global",
|
||||
"Global Repository": "Repositório Global",
|
||||
"Idle": "Em espera",
|
||||
"Ignore Permissions": "Ignorar Permissões",
|
||||
"Keep Versions": "Manter Versões",
|
||||
"Latest Release": "Última versão",
|
||||
"Local Discovery": "Descoberta Local",
|
||||
"Local Discovery Port": "Porto de Descoberta Local",
|
||||
"Local Repository": "Repositório local",
|
||||
"Master Repo": "Repositório Mestre",
|
||||
"Max File Change Rate (KiB/s)": "Taxa máxima de troca de ficheiros (KiB/s)",
|
||||
"Max Outstanding Requests": "Número máximo de pedidos pendentes",
|
||||
"No": "Não",
|
||||
"Node ID": "ID do Nó",
|
||||
"Node Name": "Nome do Nó",
|
||||
"Notice": "Nota",
|
||||
"OK": "OK",
|
||||
"Offline": "Desconectado",
|
||||
"Online": "Conectado",
|
||||
"Out Of Sync": "Não sincronizado",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite da taxa de envio (KiB/s)",
|
||||
"Override Changes": "Sobrepor Mudanças",
|
||||
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Caminho para o repositório no computador local. Será criado se não existir. O carácter (~) pode ser utilizado como atalho para",
|
||||
"Please wait": "Aguarde",
|
||||
"Preview Usage Report": "Visualização de Relatório",
|
||||
"RAM Utilization": "Utilização da RAM",
|
||||
"Reconnect Interval (s)": "Intervalo de reestabelecimento de ligação (s)",
|
||||
"Repository ID": "ID do Repositório",
|
||||
"Repository Master": "Repositório Mestre",
|
||||
"Repository Path": "Caminho do Repositório",
|
||||
"Rescan Interval (s)": "Intervalo de monitorização (s)",
|
||||
"Restart": "Reiniciar",
|
||||
"Restart Needed": "É preciso reiniciar",
|
||||
"Save": "Gravar",
|
||||
"Scanning": "Varrendo",
|
||||
"Select the nodes to share this repository with.": "Selecione os nós com quais partilhar este repositório.",
|
||||
"Settings": "Configurações",
|
||||
"Share With Nodes": "Partilhar com Nós",
|
||||
"Shared With": "Partilhado Com",
|
||||
"Short identifier for the repository. Must be the same on all cluster nodes.": "Identificador curto para o repositório. Tem que ser igual em todos os nós do agrupamento.",
|
||||
"Show ID": "Mostrar ID",
|
||||
"Shown instead of Node ID in the cluster status.": "Mostrado ao invés do ID do Nó no estado do agrupamento.",
|
||||
"Shutdown": "Desligar",
|
||||
"Source Code": "Código Fonte",
|
||||
"Start Browser": "Iniciar Navegador",
|
||||
"Stopped": "Parado",
|
||||
"Support / Forum": "Suporte / Fórum",
|
||||
"Sync Protocol Listen Addresses": "Endereços de escuta do protocolo de sincronização",
|
||||
"Synchronization": "Sincronização",
|
||||
"Syncing": "Sincronizando",
|
||||
"Syncthing has been shut down.": "Syncthing foi desligado.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inclui as seguintes aplicacoes ou partes delas:",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing parece estar em baixo, ou existe um problema com a sua ligação Internet. A tentar novamente...",
|
||||
"The aggregated statistics are publicly available at {{url}}": "As estatísticas agrupadas estão disponíveis publicamente em {{url}}",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi gravada mas não activada. Syncthing tem que reiniciar para activar a nova configuração.",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório de utilização cifrado é enviado diariamente. É utilizado para seguir plataformas comuns, tamanhos de repositórios e versões da aplicação. Se o conjunto de dados do relatório for alterado, será notificado novamente através desta janela.",
|
||||
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "O ID do nó indicado não parece ser válido. Deveria conter uma palavra de 52 caracteres constituída por letras e números, com espaços e traços opcionais. ",
|
||||
"The node ID cannot be blank.": "O ID do nó não pode ser vazio.",
|
||||
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "O ID do nó a introduzir pode ser encontrado no diálogo \"Editar > Mostrar ID\" no outro nó. Espaços e traços são opcionais (ignorados).",
|
||||
"The number of old versions to keep, per file.": "O número de versões antigas a manter, por ficheiro.",
|
||||
"The number of versions must be a number and cannot be blank.": "O número de versões tem que ser um número e não pode ser vazio.",
|
||||
"The repository ID cannot be blank.": "O ID do repositório não pode ser vazio.",
|
||||
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "O ID do repositório tem que ser um identificador curto (64 carateres ou menos) e consiste em letras, números e os carateres ponto (.), traço (-) e (_).",
|
||||
"The repository ID must be unique.": "O ID do repositório tem que ser único.",
|
||||
"The repository path cannot be blank.": "O caminho do repositório não pode ser vazio.",
|
||||
"Unknown": "Desconhecido",
|
||||
"Up to Date": "Actualizado",
|
||||
"Upgrade To {%version%}": "Atualizar para {{version}}",
|
||||
"Upload Rate": "Taxa de envio",
|
||||
"Usage": "Utilização",
|
||||
"Use Compression": "Usar Compressão",
|
||||
"Use HTTPS for GUI": "Utilizar HTTPS para GUI",
|
||||
"Version": "Versão",
|
||||
"When adding a new node, keep in mind that this node must be added on the other side too.": "Quando adicionar um novo nó, lembre-se que este nó tem que ser adicionado do outro lado também.",
|
||||
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Quando adicionar um novo repositório, lembre-se que o ID do Repositório é utilizado para juntar os repositórios entre nós. São sensíveis às maiúsculas e minúsculas e tem que corresponder exactamente entre todos os nós.",
|
||||
"Yes": "Sim",
|
||||
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
|
||||
"items": "itens"
|
||||
}
|
||||
112
gui/lang-sv.json
Normal file
112
gui/lang-sv.json
Normal file
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"API Key": "API-nyckel",
|
||||
"About": "Om",
|
||||
"Add Node": "Lägg till nod",
|
||||
"Add Repository": "Lägg till lagring",
|
||||
"Address": "Adress",
|
||||
"Addresses": "Adresser",
|
||||
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
|
||||
"Announce Server": "Uppslagningsserver",
|
||||
"Anonymous Usage Reporting": "Anonym användarstatistik",
|
||||
"Bugs": "Buggar",
|
||||
"CPU Utilization": "CPU-utnyttjande",
|
||||
"Close": "Stäng",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg och de följande medarbetarna:",
|
||||
"Delete": "Radera",
|
||||
"Disconnected": "Ej ansluten",
|
||||
"Documentation": "Dokumentation",
|
||||
"Download Rate": "Nerladdningshastighet",
|
||||
"Edit": "Redigera",
|
||||
"Edit Node": "Redigera nod",
|
||||
"Edit Repository": "Redigera lagring",
|
||||
"Enable UPnP": "Använd UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade \"ip:port\"-adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
|
||||
"Error": "Fel",
|
||||
"File Versioning": "Versionshantering",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Filers rättighetsbitar tas inte hänsyn till vid synkronisering. Använd på FAT-filsystem.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Filer flyttas till datumstämplade versioner i en .stversions-katalog när de blir uppdaterade eller raderade av syncthing.",
|
||||
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra noder, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
|
||||
"Folder": "Katalog",
|
||||
"GUI Authentication Password": "GUI-lösenord",
|
||||
"GUI Authentication User": "GUI-användare",
|
||||
"GUI Listen Addresses": "GUI-address",
|
||||
"Generate": "Skapa",
|
||||
"Global Discovery": "Global uppslagning",
|
||||
"Global Repository": "Global lagring",
|
||||
"Idle": "Vilande",
|
||||
"Ignore Permissions": "Ignorera filrättigheter",
|
||||
"Keep Versions": "Behåll versioner",
|
||||
"Latest Release": "Senaste version",
|
||||
"Local Discovery": "Lokal uppslagning",
|
||||
"Local Discovery Port": "Lokal uppslagningsport",
|
||||
"Local Repository": "Lokal lagring",
|
||||
"Master Repo": "Huvudlagring",
|
||||
"Max File Change Rate (KiB/s)": "Högsta ändringshastighet (KiB/s)",
|
||||
"Max Outstanding Requests": "Paralellitet",
|
||||
"No": "Nej",
|
||||
"Node ID": "Nod-ID",
|
||||
"Node Name": "Nodnamn",
|
||||
"Notice": "OBS",
|
||||
"OK": "OK",
|
||||
"Offline": "Ej tillgänglig",
|
||||
"Online": "Tillgänglig",
|
||||
"Out Of Sync": "Ur synk",
|
||||
"Outgoing Rate Limit (KiB/s)": "Utgående hastighetsbegränsning (KiB/s)",
|
||||
"Override Changes": "Skriv över ändringar",
|
||||
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sökväg till katalogen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
|
||||
"Please wait": "Var god vänta",
|
||||
"Preview Usage Report": "Förhandsgranska statistik",
|
||||
"RAM Utilization": "Minnesutnyttjande",
|
||||
"Reconnect Interval (s)": "Anslutningsintervall (s)",
|
||||
"Repository ID": "Lagrings-ID",
|
||||
"Repository Master": "Huvudlagring",
|
||||
"Repository Path": "Lagringskatalog",
|
||||
"Rescan Interval (s)": "Förnyelseintervall (s)",
|
||||
"Restart": "Starta om",
|
||||
"Restart Needed": "Omstart behövs",
|
||||
"Save": "Spara",
|
||||
"Scanning": "Uppdaterar",
|
||||
"Select the nodes to share this repository with.": "Välj vilka noder förvaringen ska delas med.",
|
||||
"Settings": "Inställningar",
|
||||
"Share With Nodes": "Dela med noder",
|
||||
"Shared With": "Delat med",
|
||||
"Short identifier for the repository. Must be the same on all cluster nodes.": "Kort identifieringssträng för förvaringen. Måste vara samma på alla noder i klustern.",
|
||||
"Show ID": "Visa ID",
|
||||
"Shown instead of Node ID in the cluster status.": "Visas i stället för nod-ID",
|
||||
"Shutdown": "Stäng av",
|
||||
"Source Code": "Kälkod",
|
||||
"Start Browser": "Starta browser",
|
||||
"Stopped": "Stoppad",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Address för inkommande anslutningar",
|
||||
"Synchronization": "Synkronisation",
|
||||
"Syncing": "Synkroniserar",
|
||||
"Syncthing has been shut down.": "Syncthing har stängts ner.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller de följande mjukvarupaketen eller delar därav:",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller så är där ett problem med din Internetanslutning. Försöker igen...",
|
||||
"The aggregated statistics are publicly available at {{url}}": "Aggregerad statistik finns publikt tillgänglig på {{url}}",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, lagringsstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
|
||||
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade nod-ID:t verkar inte korrekt. Det ska vara en 52 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
|
||||
"The node ID cannot be blank.": "Nod-ID:t kan inte vara blankt.",
|
||||
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "Nod-ID:t som behövs här kan du hitta i \"Redigera > Visa ID\"-dialogen på den andra noden. Mellanrum och bindestreck är valfria (ignoreras).",
|
||||
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas, per fil.",
|
||||
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas blankt.",
|
||||
"The repository ID cannot be blank.": "Lagrings-ID kan inte vara blankt.",
|
||||
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "Lagrings-ID måste vara en kort identifierar (64 tecken eller mindre), bestående av endast bokstäver, siffror, punkt (.), bindestreck (-) och understräck (_).",
|
||||
"The repository ID must be unique.": "Lagrings-ID måste vara unikt.",
|
||||
"The repository path cannot be blank.": "Lagrings-ID kan inte lämnas blankt.",
|
||||
"Unknown": "Okänt",
|
||||
"Up to Date": "Helt uppdaterad",
|
||||
"Upgrade To {%version%}": "Uppgradera till {{version}}",
|
||||
"Upload Rate": "Uppladdningshastighet",
|
||||
"Usage": "Användande",
|
||||
"Use Compression": "Använd komprimering",
|
||||
"Use HTTPS for GUI": "Använd HTTPS för GUI",
|
||||
"Version": "Version",
|
||||
"When adding a new node, keep in mind that this node must be added on the other side too.": "När du lägger till en ny nod, kom ihåg att den här noden måste läggas till på den andra noden också.",
|
||||
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "När du lägger till ny lagring, tänk på att lagrings-ID knyter ihop lagringen mellan olika noder. De måste vara exakt desamma mellan noder, och stora eller små bokstäver har betydelse.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
|
||||
"items": "poster"
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<localAnnounceEnabled>true</localAnnounceEnabled>
|
||||
<localAnnouncePort>21025</localAnnouncePort>
|
||||
<parallelRequests>16</parallelRequests>
|
||||
<maxSendKbps>1000</maxSendKbps>
|
||||
<maxSendKbps>500</maxSendKbps>
|
||||
<rescanIntervalS>10</rescanIntervalS>
|
||||
<reconnectionIntervalS>5</reconnectionIntervalS>
|
||||
<maxChangeKbps>10000</maxChangeKbps>
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
mr "math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -26,29 +27,73 @@ func name() string {
|
||||
func main() {
|
||||
var files int
|
||||
var maxexp int
|
||||
var srcname string
|
||||
|
||||
flag.IntVar(&files, "files", 1000, "Number of files")
|
||||
flag.IntVar(&maxexp, "maxexp", 20, "Maximum file size (max = 2^n + 128*1024 B)")
|
||||
flag.StringVar(&srcname, "src", "/usr/share/dict/words", "Source material")
|
||||
flag.Parse()
|
||||
|
||||
fd, err := os.Open(srcname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < files; i++ {
|
||||
n := name()
|
||||
p0 := filepath.Join(string(n[0]), n[0:2])
|
||||
os.MkdirAll(p0, 0755)
|
||||
err = os.MkdirAll(p0, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s := 1 << uint(mr.Intn(maxexp))
|
||||
a := 128 * 1024
|
||||
if a > s {
|
||||
a = s
|
||||
}
|
||||
s += mr.Intn(a)
|
||||
b := make([]byte, s)
|
||||
rand.Reader.Read(b)
|
||||
p1 := filepath.Join(p0, n)
|
||||
ioutil.WriteFile(p1, b, 0644)
|
||||
|
||||
os.Chmod(p1, os.FileMode(mr.Intn(0777)|0400))
|
||||
src := io.LimitReader(&inifiteReader{fd}, int64(s))
|
||||
|
||||
p1 := filepath.Join(p0, n)
|
||||
dst, err := os.Create(p1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = dst.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.Chmod(p1, os.FileMode(mr.Intn(0777)|0400))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
t := time.Now().Add(-time.Duration(mr.Intn(30*86400)) * time.Second)
|
||||
os.Chtimes(p1, t, t)
|
||||
err = os.Chtimes(p1, t, t)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type inifiteReader struct {
|
||||
rd io.ReadSeeker
|
||||
}
|
||||
|
||||
func (i *inifiteReader) Read(bs []byte) (int, error) {
|
||||
n, err := i.rd.Read(bs)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
i.rd.Seek(0, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
<configuration version="2">
|
||||
<repository id="default" directory="s1" ro="false" ignorePerms="false">
|
||||
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA"></node>
|
||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></node>
|
||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
||||
<node id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></node>
|
||||
<node id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></node>
|
||||
<node id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"></node>
|
||||
<versioning></versioning>
|
||||
</repository>
|
||||
<repository id="s12" directory="s12-1" ro="false" ignorePerms="false">
|
||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA"></node>
|
||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ"></node>
|
||||
<node id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></node>
|
||||
<node id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></node>
|
||||
<versioning></versioning>
|
||||
</repository>
|
||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA" name="s1">
|
||||
<node id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="true">
|
||||
<address>127.0.0.1:22001</address>
|
||||
</node>
|
||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ" name="s2">
|
||||
<node id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU" name="s2">
|
||||
<address>127.0.0.1:22002</address>
|
||||
</node>
|
||||
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA" name="s3">
|
||||
<node id="373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU" name="s3">
|
||||
<address>127.0.0.1:22003</address>
|
||||
</node>
|
||||
<node id="EJHMPAQOGCVORISB4IS3SYYVJXTKJGLTU66DIQPGJ5D2GXGQ3OWQ" name="s4">
|
||||
<node id="EJHMPAQ-OGCVORE-ISB4IS3-SYYVJXF-TKJGLTU-66DIQPF-GJ5D2GX-GQ3OWQK" name="s4">
|
||||
<address>127.0.0.1:22004</address>
|
||||
</node>
|
||||
<gui enabled="true" tls="false">
|
||||
<address>127.0.0.1:8081</address>
|
||||
<apikey>abc123</apikey>
|
||||
<user>testuser</user>
|
||||
<password>testpass</password>
|
||||
<password>$2a$10$7tKL5uvLDGn5s2VLPM2yWOK/II45az0mTel8hxAUJDRQN1Tk2QYwu</password>
|
||||
<apikey>abc123</apikey>
|
||||
</gui>
|
||||
<options>
|
||||
<listenAddress>127.0.0.1:22001</listenAddress>
|
||||
<globalAnnounceServer>announce.syncthing.net:22025</globalAnnounceServer>
|
||||
<globalAnnounceServer>announce.syncthing.net:22026</globalAnnounceServer>
|
||||
<globalAnnounceEnabled>false</globalAnnounceEnabled>
|
||||
<localAnnounceEnabled>true</localAnnounceEnabled>
|
||||
<localAnnouncePort>21025</localAnnouncePort>
|
||||
@@ -41,5 +41,6 @@
|
||||
<maxChangeKbps>10000</maxChangeKbps>
|
||||
<startBrowser>false</startBrowser>
|
||||
<upnpEnabled>true</upnpEnabled>
|
||||
<urAccepted>-1</urAccepted>
|
||||
</options>
|
||||
</configuration>
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
<versioning></versioning>
|
||||
<syncorder></syncorder>
|
||||
</repository>
|
||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA" name="s1">
|
||||
<node id="I6KAH7666SLLL5PFXSOAUFJCDZYAOMLEKCP2GB3BV5RQST3PSROA" name="s1" compression="true">
|
||||
<address>127.0.0.1:22001</address>
|
||||
</node>
|
||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ" name="s2">
|
||||
<node id="JMFJCXBGZDE4BOCJE3VF65GYZNAIVJRET3J6HMRAUQIGJOFKNHMQ" name="s2" compression="true">
|
||||
<address>127.0.0.1:22002</address>
|
||||
</node>
|
||||
<node id="373HSRPQLPNLIJYKZVQFP4PKZ6R2ZE6K3YD442UJHBGBQGWWXAHA" name="s3">
|
||||
|
||||
@@ -31,9 +31,9 @@ stop() {
|
||||
testConvergence() {
|
||||
while true ; do
|
||||
sleep 5
|
||||
s1comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8082/rest/connections" | ./json "$id1/Completion")
|
||||
s2comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8083/rest/connections" | ./json "$id2/Completion")
|
||||
s3comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/connections" | ./json "$id3/Completion")
|
||||
s1comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8082/rest/debug/peerCompletion" | ./json "$id1")
|
||||
s2comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8083/rest/debug/peerCompletion" | ./json "$id2")
|
||||
s3comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/debug/peerCompletion" | ./json "$id3")
|
||||
s1comp=${s1comp:-0}
|
||||
s2comp=${s2comp:-0}
|
||||
s3comp=${s3comp:-0}
|
||||
|
||||
@@ -46,8 +46,8 @@ setup() {
|
||||
testConvergence() {
|
||||
while true ; do
|
||||
sleep 5
|
||||
s1comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8082/rest/connections" | ./json "$id1/Completion")
|
||||
s2comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/connections" | ./json "$id2/Completion")
|
||||
s1comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8082/rest/debug/peerCompletion" | ./json "$id1")
|
||||
s2comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/debug/peerCompletion" | ./json "$id2")
|
||||
s1comp=${s1comp:-0}
|
||||
s2comp=${s2comp:-0}
|
||||
tot=$(($s1comp + $s2comp))
|
||||
|
||||
@@ -40,9 +40,9 @@ clean() {
|
||||
testConvergence() {
|
||||
while true ; do
|
||||
sleep 5
|
||||
s1comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8082/rest/connections" | ./json "$id1/Completion")
|
||||
s2comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8083/rest/connections" | ./json "$id2/Completion")
|
||||
s3comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/connections" | ./json "$id3/Completion")
|
||||
s1comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8082/rest/debug/peerCompletion" | ./json "$id1")
|
||||
s2comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8083/rest/debug/peerCompletion" | ./json "$id2")
|
||||
s3comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/debug/peerCompletion" | ./json "$id3")
|
||||
s1comp=${s1comp:-0}
|
||||
s2comp=${s2comp:-0}
|
||||
s3comp=${s3comp:-0}
|
||||
|
||||
@@ -43,7 +43,7 @@ testConvergence() {
|
||||
|
||||
while true ; do
|
||||
sleep 5
|
||||
comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/connections" | ./json "$id2/Completion")
|
||||
comp=$(curl -HX-API-Key:abc123 -s "http://localhost:8081/rest/debug/peerCompletion" | ./json "$id2")
|
||||
comp=${comp:-0}
|
||||
echo $comp / 100
|
||||
|
||||
|
||||
@@ -157,7 +157,6 @@ type ConnectionInfo struct {
|
||||
protocol.Statistics
|
||||
Address string
|
||||
ClientVersion string
|
||||
Completion int
|
||||
}
|
||||
|
||||
// ConnectionStats returns a map with connection statistics for each connected node.
|
||||
@@ -179,43 +178,6 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
|
||||
ci.Address = nc.RemoteAddr().String()
|
||||
}
|
||||
|
||||
var tot int64
|
||||
var have int64
|
||||
|
||||
for _, repo := range m.nodeRepos[node] {
|
||||
m.repoFiles[repo].WithGlobal(func(f protocol.FileInfo) bool {
|
||||
if !protocol.IsDeleted(f.Flags) {
|
||||
var size int64
|
||||
if protocol.IsDirectory(f.Flags) {
|
||||
size = zeroEntrySize
|
||||
} else {
|
||||
size = f.Size()
|
||||
}
|
||||
tot += size
|
||||
have += size
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
m.repoFiles[repo].WithNeed(node, func(f protocol.FileInfo) bool {
|
||||
if !protocol.IsDeleted(f.Flags) {
|
||||
var size int64
|
||||
if protocol.IsDirectory(f.Flags) {
|
||||
size = zeroEntrySize
|
||||
} else {
|
||||
size = f.Size()
|
||||
}
|
||||
have -= size
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
ci.Completion = 100
|
||||
if tot != 0 {
|
||||
ci.Completion = int(100 * have / tot)
|
||||
}
|
||||
|
||||
res[node.String()] = ci
|
||||
}
|
||||
|
||||
@@ -234,6 +196,39 @@ func (m *Model) ConnectionStats() map[string]ConnectionInfo {
|
||||
return res
|
||||
}
|
||||
|
||||
// Returns the completion status, in percent, for the given node and repo.
|
||||
func (m *Model) Completion(node protocol.NodeID, repo string) float64 {
|
||||
var tot int64
|
||||
m.repoFiles[repo].WithGlobal(func(f protocol.FileInfo) bool {
|
||||
if !protocol.IsDeleted(f.Flags) {
|
||||
var size int64
|
||||
if protocol.IsDirectory(f.Flags) {
|
||||
size = zeroEntrySize
|
||||
} else {
|
||||
size = f.Size()
|
||||
}
|
||||
tot += size
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
var need int64
|
||||
m.repoFiles[repo].WithNeed(node, func(f protocol.FileInfo) bool {
|
||||
if !protocol.IsDeleted(f.Flags) {
|
||||
var size int64
|
||||
if protocol.IsDirectory(f.Flags) {
|
||||
size = zeroEntrySize
|
||||
} else {
|
||||
size = f.Size()
|
||||
}
|
||||
need += size
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return 100 * (1 - float64(need)/float64(tot))
|
||||
}
|
||||
|
||||
func sizeOf(fs []protocol.FileInfo) (files, deleted int, bytes int64) {
|
||||
for _, f := range fs {
|
||||
fs, de, by := sizeOfFile(f)
|
||||
@@ -586,9 +581,9 @@ func sendIndexes(conn protocol.Connection, repo string, fs *files.Set) {
|
||||
for err == nil {
|
||||
if !initial {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
if fs.LocalVersion(protocol.LocalNodeID) <= minLocalVer {
|
||||
continue
|
||||
if fs.LocalVersion(protocol.LocalNodeID) <= minLocalVer {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
batch := make([]protocol.FileInfo, 0, indexBatchSize)
|
||||
@@ -769,6 +764,13 @@ func (m *Model) ScanRepo(repo string) error {
|
||||
batchSize := 100
|
||||
batch := make([]protocol.FileInfo, 0, 00)
|
||||
for f := range fchan {
|
||||
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
|
||||
"repo": repo,
|
||||
"name": f.Name,
|
||||
"modified": time.Unix(f.Modified, 0),
|
||||
"flags": fmt.Sprintf("0%o", f.Flags),
|
||||
"size": f.Size(),
|
||||
})
|
||||
if len(batch) == batchSize {
|
||||
fs.Update(protocol.LocalNodeID, batch)
|
||||
batch = batch[:0]
|
||||
@@ -792,6 +794,13 @@ func (m *Model) ScanRepo(repo string) error {
|
||||
f.Flags |= protocol.FlagDeleted
|
||||
f.Version = lamport.Default.Tick(f.Version)
|
||||
f.LocalVersion = 0
|
||||
events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
|
||||
"repo": repo,
|
||||
"name": f.Name,
|
||||
"modified": time.Unix(f.Modified, 0),
|
||||
"flags": fmt.Sprintf("0%o", f.Flags),
|
||||
"size": f.Size(),
|
||||
})
|
||||
batch = append(batch, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,13 +64,15 @@ Messages
|
||||
--------
|
||||
|
||||
Every message starts with one 32 bit word indicating the message
|
||||
version, type and ID. The header is in network byte order, i.e. big
|
||||
endian.
|
||||
version, type and ID, followed by the length of the message. The header
|
||||
is in network byte order, i.e. big endian.
|
||||
|
||||
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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Ver | Message ID | Type | Reserved |
|
||||
| Ver | Message ID | Type | Reserved |C|
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
For BEP v1 the Version field is set to zero. Future versions with
|
||||
@@ -92,10 +94,28 @@ The Type field indicates the type of data following the message header
|
||||
and is one of the integers defined below. A message of an unknown type
|
||||
is a protocol error and MUST result in the connection being terminated.
|
||||
|
||||
All data following the message header MUST be in XDR (RFC 1014)
|
||||
encoding. All fields shorter than 32 bits and all variable length data
|
||||
MUST be padded to a multiple of 32 bits. The actual data types in use by
|
||||
BEP, in XDR naming convention, are the following:
|
||||
The Compression bit "C" indicates the compression used for the message.
|
||||
|
||||
For C=1:
|
||||
|
||||
* The Length field contains the length, in bytes, of the
|
||||
compressed message data.
|
||||
|
||||
* The message data is compressed using the LZ4 format and algorithm
|
||||
described in https://code.google.com/p/lz4/.
|
||||
|
||||
For C=0:
|
||||
|
||||
* The Length field contains the length, in bytes, of the
|
||||
uncompressed message data.
|
||||
|
||||
* The message is not compressed.
|
||||
|
||||
All data within the the message (post decompression, if compression is
|
||||
in use) MUST be in XDR (RFC 1014) encoding. All fields shorter than 32
|
||||
bits and all variable length data MUST be padded to a multiple of 32
|
||||
bits. The actual data types in use by BEP, in XDR naming convention, are
|
||||
the following:
|
||||
|
||||
- (unsigned) int -- (unsigned) 32 bit integer
|
||||
- (unsigned) hyper -- (unsigned) 64 bit integer
|
||||
@@ -561,6 +581,33 @@ firewalls and NAT gateways. The Ping message has no contents.
|
||||
The Pong message is sent in response to a Ping. The Pong message has no
|
||||
contents, but copies the Message ID from the Ping.
|
||||
|
||||
### Close (Type = 7)
|
||||
|
||||
The Close message MAY be sent to indicate that the connection will be
|
||||
torn down due to an error condition. A Close message MUST NOT be
|
||||
followed by further messages.
|
||||
|
||||
#### Graphical Representation
|
||||
|
||||
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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Reason |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Reason (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
#### Fields
|
||||
|
||||
The Reason field contains a human description of the error condition,
|
||||
suitable for consumption by a human.
|
||||
|
||||
struct CloseMessage {
|
||||
string Reason<1024>;
|
||||
}
|
||||
|
||||
Sharing Modes
|
||||
-------------
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ package protocol
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type countingReader struct {
|
||||
io.Reader
|
||||
tot uint64
|
||||
tot uint64 // bytes
|
||||
last int64 // unix nanos
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -23,6 +25,7 @@ func (c *countingReader) Read(bs []byte) (int, error) {
|
||||
n, err := c.Reader.Read(bs)
|
||||
atomic.AddUint64(&c.tot, uint64(n))
|
||||
atomic.AddUint64(&totalIncoming, uint64(n))
|
||||
atomic.StoreInt64(&c.last, time.Now().UnixNano())
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -30,15 +33,21 @@ func (c *countingReader) Tot() uint64 {
|
||||
return atomic.LoadUint64(&c.tot)
|
||||
}
|
||||
|
||||
func (c *countingReader) Last() time.Time {
|
||||
return time.Unix(0, atomic.LoadInt64(&c.last))
|
||||
}
|
||||
|
||||
type countingWriter struct {
|
||||
io.Writer
|
||||
tot uint64
|
||||
tot uint64 // bytes
|
||||
last int64 // unix nanos
|
||||
}
|
||||
|
||||
func (c *countingWriter) Write(bs []byte) (int, error) {
|
||||
n, err := c.Writer.Write(bs)
|
||||
atomic.AddUint64(&c.tot, uint64(n))
|
||||
atomic.AddUint64(&totalOutgoing, uint64(n))
|
||||
atomic.StoreInt64(&c.last, time.Now().UnixNano())
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -46,6 +55,10 @@ func (c *countingWriter) Tot() uint64 {
|
||||
return atomic.LoadUint64(&c.tot)
|
||||
}
|
||||
|
||||
func (c *countingWriter) Last() time.Time {
|
||||
return time.Unix(0, atomic.LoadInt64(&c.last))
|
||||
}
|
||||
|
||||
func TotalInOut() (uint64, uint64) {
|
||||
return atomic.LoadUint64(&totalIncoming), atomic.LoadUint64(&totalOutgoing)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ package protocol
|
||||
import "github.com/calmh/syncthing/xdr"
|
||||
|
||||
type header struct {
|
||||
version int
|
||||
msgID int
|
||||
msgType int
|
||||
version int
|
||||
msgID int
|
||||
msgType int
|
||||
compression bool
|
||||
}
|
||||
|
||||
func (h header) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
@@ -24,15 +25,21 @@ func (h *header) decodeXDR(xr *xdr.Reader) error {
|
||||
}
|
||||
|
||||
func encodeHeader(h header) uint32 {
|
||||
var isComp uint32
|
||||
if h.compression {
|
||||
isComp = 1 << 0 // the zeroth bit is the compression bit
|
||||
}
|
||||
return uint32(h.version&0xf)<<28 +
|
||||
uint32(h.msgID&0xfff)<<16 +
|
||||
uint32(h.msgType&0xff)<<8
|
||||
uint32(h.msgType&0xff)<<8 +
|
||||
isComp
|
||||
}
|
||||
|
||||
func decodeHeader(u uint32) header {
|
||||
return header{
|
||||
version: int(u>>28) & 0xf,
|
||||
msgID: int(u>>16) & 0xfff,
|
||||
msgType: int(u>>8) & 0xff,
|
||||
version: int(u>>28) & 0xf,
|
||||
msgID: int(u>>16) & 0xfff,
|
||||
msgType: int(u>>8) & 0xff,
|
||||
compression: u&1 == 1,
|
||||
}
|
||||
}
|
||||
|
||||
119
protocol/lz4stream.go
Normal file
119
protocol/lz4stream.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
lz4 "github.com/bkaradzic/go-lz4"
|
||||
)
|
||||
|
||||
const lz4Magic = 0x5e63b278
|
||||
|
||||
type lz4Writer struct {
|
||||
wr io.Writer
|
||||
mut sync.Mutex
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newLZ4Writer(w io.Writer) *lz4Writer {
|
||||
return &lz4Writer{wr: w}
|
||||
}
|
||||
|
||||
func (w *lz4Writer) Write(bs []byte) (int, error) {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
|
||||
var err error
|
||||
w.buf, err = lz4.Encode(w.buf[:cap(w.buf)], bs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var hdr [8]byte
|
||||
binary.BigEndian.PutUint32(hdr[0:], lz4Magic)
|
||||
binary.BigEndian.PutUint32(hdr[4:], uint32(len(w.buf)))
|
||||
_, err = w.wr.Write(hdr[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = w.wr.Write(w.buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("lz4 write; %d / %d bytes", len(bs), 8+len(w.buf))
|
||||
}
|
||||
return len(bs), nil
|
||||
}
|
||||
|
||||
type lz4Reader struct {
|
||||
rd io.Reader
|
||||
mut sync.Mutex
|
||||
buf []byte
|
||||
ebuf []byte
|
||||
obuf *bytes.Buffer
|
||||
ibytes uint64
|
||||
obytes uint64
|
||||
}
|
||||
|
||||
func newLZ4Reader(r io.Reader) *lz4Reader {
|
||||
return &lz4Reader{rd: r}
|
||||
}
|
||||
|
||||
func (r *lz4Reader) Read(bs []byte) (int, error) {
|
||||
r.mut.Lock()
|
||||
defer r.mut.Unlock()
|
||||
|
||||
if r.obuf == nil {
|
||||
r.obuf = bytes.NewBuffer(nil)
|
||||
}
|
||||
|
||||
if r.obuf.Len() == 0 {
|
||||
if err := r.moreBits(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err := r.obuf.Read(bs)
|
||||
if debug {
|
||||
l.Debugf("lz4 read; %d bytes", n)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *lz4Reader) moreBits() error {
|
||||
var hdr [8]byte
|
||||
_, err := io.ReadFull(r.rd, hdr[:])
|
||||
if binary.BigEndian.Uint32(hdr[0:]) != lz4Magic {
|
||||
return errors.New("bad magic")
|
||||
}
|
||||
|
||||
ln := int(binary.BigEndian.Uint32(hdr[4:]))
|
||||
if len(r.buf) < ln {
|
||||
r.buf = make([]byte, int(ln))
|
||||
} else {
|
||||
r.buf = r.buf[:ln]
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(r.rd, r.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.ebuf, err = lz4.Decode(r.ebuf[:cap(r.ebuf)], r.buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("lz4 moreBits: %d / %d bytes", ln+8, len(r.ebuf))
|
||||
}
|
||||
|
||||
_, err = r.obuf.Write(r.ebuf)
|
||||
return err
|
||||
}
|
||||
60
protocol/lz4stream_test.go
Normal file
60
protocol/lz4stream_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var toWrite = [][]byte{
|
||||
[]byte("this is a short byte string that should pass through somewhat compressed this is a short byte string that should pass through somewhat compressed this is a short byte string that should pass through somewhat compressed this is a short byte string that should pass through somewhat compressed this is a short byte string that should pass through somewhat compressed this is a short byte string that should pass through somewhat compressed"),
|
||||
[]byte("this is another short byte string that should pass through uncompressed"),
|
||||
[]byte{0, 1, 2, 3, 4, 5},
|
||||
}
|
||||
|
||||
func TestLZ4Stream(t *testing.T) {
|
||||
tb := make([]byte, 128*1024)
|
||||
rand.Reader.Read(tb)
|
||||
toWrite = append(toWrite, tb)
|
||||
tb = make([]byte, 512*1024)
|
||||
rand.Reader.Read(tb)
|
||||
toWrite = append(toWrite, tb)
|
||||
toWrite = append(toWrite, toWrite[0])
|
||||
toWrite = append(toWrite, toWrite[1])
|
||||
|
||||
rd, wr := io.Pipe()
|
||||
lz4r := newLZ4Reader(rd)
|
||||
lz4w := newLZ4Writer(wr)
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 5; i++ {
|
||||
for _, bs := range toWrite {
|
||||
n, err := lz4w.Write(bs)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if n != len(bs) {
|
||||
t.Errorf("weird write length; %d != %d", n, len(bs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, 512*1024)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
for _, bs := range toWrite {
|
||||
n, err := lz4r.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(bs) {
|
||||
t.Errorf("Unexpected len %d != %d", n, len(bs))
|
||||
}
|
||||
if bytes.Compare(bs, buf[:n]) != 0 {
|
||||
t.Error("Unexpected data")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ package protocol
|
||||
import "fmt"
|
||||
|
||||
type IndexMessage struct {
|
||||
Repository string // max:64
|
||||
Files []FileInfo // max:10000000
|
||||
Repository string // max:64
|
||||
Files []FileInfo
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
@@ -17,7 +17,7 @@ type FileInfo struct {
|
||||
Modified int64
|
||||
Version uint64
|
||||
LocalVersion uint64
|
||||
Blocks []BlockInfo // max:1000000
|
||||
Blocks []BlockInfo
|
||||
}
|
||||
|
||||
func (f FileInfo) String() string {
|
||||
@@ -49,6 +49,10 @@ type RequestMessage struct {
|
||||
Size uint32
|
||||
}
|
||||
|
||||
type ResponseMessage struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type ClusterConfigMessage struct {
|
||||
ClientName string // max:64
|
||||
ClientVersion string // max:64
|
||||
@@ -71,3 +75,9 @@ type Option struct {
|
||||
Key string // max:64
|
||||
Value string // max:1024
|
||||
}
|
||||
|
||||
type CloseMessage struct {
|
||||
Reason string // max:1024
|
||||
}
|
||||
|
||||
type EmptyMessage struct{}
|
||||
|
||||
@@ -38,7 +38,7 @@ IndexMessage Structure:
|
||||
|
||||
struct IndexMessage {
|
||||
string Repository<64>;
|
||||
FileInfo Files<10000000>;
|
||||
FileInfo Files<>;
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -64,9 +64,6 @@ func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteString(o.Repository)
|
||||
if len(o.Files) > 10000000 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteUint32(uint32(len(o.Files)))
|
||||
for i := range o.Files {
|
||||
o.Files[i].encodeXDR(xw)
|
||||
@@ -88,9 +85,6 @@ func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
|
||||
func (o *IndexMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Repository = xr.ReadStringMax(64)
|
||||
_FilesSize := int(xr.ReadUint32())
|
||||
if _FilesSize > 10000000 {
|
||||
return xdr.ErrElementSizeExceeded
|
||||
}
|
||||
o.Files = make([]FileInfo, _FilesSize)
|
||||
for i := range o.Files {
|
||||
(&o.Files[i]).decodeXDR(xr)
|
||||
@@ -139,7 +133,7 @@ struct FileInfo {
|
||||
hyper Modified;
|
||||
unsigned hyper Version;
|
||||
unsigned hyper LocalVersion;
|
||||
BlockInfo Blocks<1000000>;
|
||||
BlockInfo Blocks<>;
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -169,9 +163,6 @@ func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteUint64(uint64(o.Modified))
|
||||
xw.WriteUint64(o.Version)
|
||||
xw.WriteUint64(o.LocalVersion)
|
||||
if len(o.Blocks) > 1000000 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteUint32(uint32(len(o.Blocks)))
|
||||
for i := range o.Blocks {
|
||||
o.Blocks[i].encodeXDR(xw)
|
||||
@@ -197,9 +188,6 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Version = xr.ReadUint64()
|
||||
o.LocalVersion = xr.ReadUint64()
|
||||
_BlocksSize := int(xr.ReadUint32())
|
||||
if _BlocksSize > 1000000 {
|
||||
return xdr.ErrElementSizeExceeded
|
||||
}
|
||||
o.Blocks = make([]BlockInfo, _BlocksSize)
|
||||
for i := range o.Blocks {
|
||||
(&o.Blocks[i]).decodeXDR(xr)
|
||||
@@ -360,6 +348,64 @@ func (o *RequestMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
|
||||
/*
|
||||
|
||||
ResponseMessage 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Data |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Data (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct ResponseMessage {
|
||||
opaque Data<>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o ResponseMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o ResponseMessage) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o ResponseMessage) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o ResponseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteBytes(o.Data)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *ResponseMessage) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *ResponseMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Data = xr.ReadBytes()
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
ClusterConfigMessage Structure:
|
||||
|
||||
0 1 2 3
|
||||
@@ -703,3 +749,113 @@ func (o *Option) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Value = xr.ReadStringMax(1024)
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
CloseMessage 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Reason |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Reason (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct CloseMessage {
|
||||
string Reason<1024>;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o CloseMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o CloseMessage) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o CloseMessage) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o CloseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
if len(o.Reason) > 1024 {
|
||||
return xw.Tot(), xdr.ErrElementSizeExceeded
|
||||
}
|
||||
xw.WriteString(o.Reason)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *CloseMessage) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *CloseMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Reason = xr.ReadStringMax(1024)
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
EmptyMessage 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
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct EmptyMessage {
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o EmptyMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o EmptyMessage) MarshalXDR() []byte {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o EmptyMessage) AppendXDR(bs []byte) []byte {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
o.encodeXDR(xw)
|
||||
return []byte(aw)
|
||||
}
|
||||
|
||||
func (o EmptyMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *EmptyMessage) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *EmptyMessage) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *EmptyMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
@@ -6,16 +6,20 @@ package protocol
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/calmh/syncthing/xdr"
|
||||
lz4 "github.com/bkaradzic/go-lz4"
|
||||
)
|
||||
|
||||
const BlockSize = 128 * 1024
|
||||
const (
|
||||
BlockSize = 128 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
messageTypeClusterConfig = 0
|
||||
@@ -25,6 +29,7 @@ const (
|
||||
messageTypePing = 4
|
||||
messageTypePong = 5
|
||||
messageTypeIndexUpdate = 6
|
||||
messageTypeClose = 7
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -81,21 +86,24 @@ type rawConnection struct {
|
||||
state int
|
||||
|
||||
cr *countingReader
|
||||
xr *xdr.Reader
|
||||
|
||||
cw *countingWriter
|
||||
wb *bufio.Writer
|
||||
xw *xdr.Writer
|
||||
|
||||
awaiting []chan asyncResult
|
||||
awaiting [4096]chan asyncResult
|
||||
awaitingMut sync.Mutex
|
||||
|
||||
idxMut sync.Mutex // ensures serialization of Index calls
|
||||
|
||||
nextID chan int
|
||||
outbox chan []encodable
|
||||
outbox chan hdrMsg
|
||||
closed chan struct{}
|
||||
once sync.Once
|
||||
|
||||
compressionThreshold int // compress messages larger than this many bytes
|
||||
|
||||
rdbuf0 []byte // used & reused by readMessage
|
||||
rdbuf1 []byte // used & reused by readMessage
|
||||
}
|
||||
|
||||
type asyncResult struct {
|
||||
@@ -103,32 +111,39 @@ type asyncResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type hdrMsg struct {
|
||||
hdr header
|
||||
msg encodable
|
||||
}
|
||||
|
||||
type encodable interface {
|
||||
AppendXDR([]byte) []byte
|
||||
}
|
||||
|
||||
const (
|
||||
pingTimeout = 30 * time.Second
|
||||
pingIdleTime = 60 * time.Second
|
||||
)
|
||||
|
||||
func NewConnection(nodeID NodeID, reader io.Reader, writer io.Writer, receiver Model, name string) Connection {
|
||||
func NewConnection(nodeID NodeID, reader io.Reader, writer io.Writer, receiver Model, name string, compress bool) Connection {
|
||||
cr := &countingReader{Reader: reader}
|
||||
cw := &countingWriter{Writer: writer}
|
||||
|
||||
rb := bufio.NewReader(cr)
|
||||
wb := bufio.NewWriterSize(cw, 65536)
|
||||
|
||||
compThres := 1<<31 - 1 // compression disabled
|
||||
if compress {
|
||||
compThres = 128 // compress messages that are 128 bytes long or larger
|
||||
}
|
||||
c := rawConnection{
|
||||
id: nodeID,
|
||||
name: name,
|
||||
receiver: nativeModel{receiver},
|
||||
state: stateInitial,
|
||||
cr: cr,
|
||||
xr: xdr.NewReader(rb),
|
||||
cw: cw,
|
||||
wb: wb,
|
||||
xw: xdr.NewWriter(wb),
|
||||
awaiting: make([]chan asyncResult, 0x1000),
|
||||
outbox: make(chan []encodable),
|
||||
nextID: make(chan int),
|
||||
closed: make(chan struct{}),
|
||||
id: nodeID,
|
||||
name: name,
|
||||
receiver: nativeModel{receiver},
|
||||
state: stateInitial,
|
||||
cr: cr,
|
||||
cw: cw,
|
||||
outbox: make(chan hdrMsg),
|
||||
nextID: make(chan int),
|
||||
closed: make(chan struct{}),
|
||||
compressionThreshold: compThres,
|
||||
}
|
||||
|
||||
go c.readerLoop()
|
||||
@@ -155,7 +170,7 @@ func (c *rawConnection) Index(repo string, idx []FileInfo) error {
|
||||
default:
|
||||
}
|
||||
c.idxMut.Lock()
|
||||
c.send(header{0, -1, messageTypeIndex}, IndexMessage{repo, idx})
|
||||
c.send(-1, messageTypeIndex, IndexMessage{repo, idx})
|
||||
c.idxMut.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -168,7 +183,7 @@ func (c *rawConnection) IndexUpdate(repo string, idx []FileInfo) error {
|
||||
default:
|
||||
}
|
||||
c.idxMut.Lock()
|
||||
c.send(header{0, -1, messageTypeIndexUpdate}, IndexMessage{repo, idx})
|
||||
c.send(-1, messageTypeIndexUpdate, IndexMessage{repo, idx})
|
||||
c.idxMut.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -190,8 +205,7 @@ func (c *rawConnection) Request(repo string, name string, offset int64, size int
|
||||
c.awaiting[id] = rc
|
||||
c.awaitingMut.Unlock()
|
||||
|
||||
ok := c.send(header{0, id, messageTypeRequest},
|
||||
RequestMessage{repo, name, uint64(offset), uint32(size)})
|
||||
ok := c.send(id, messageTypeRequest, RequestMessage{repo, name, uint64(offset), uint32(size)})
|
||||
if !ok {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
@@ -205,7 +219,7 @@ func (c *rawConnection) Request(repo string, name string, offset int64, size int
|
||||
|
||||
// ClusterConfig send the cluster configuration message to the peer and returns any error
|
||||
func (c *rawConnection) ClusterConfig(config ClusterConfigMessage) {
|
||||
c.send(header{0, -1, messageTypeClusterConfig}, config)
|
||||
c.send(-1, messageTypeClusterConfig, config)
|
||||
}
|
||||
|
||||
func (c *rawConnection) ping() bool {
|
||||
@@ -221,7 +235,7 @@ func (c *rawConnection) ping() bool {
|
||||
c.awaiting[id] = rc
|
||||
c.awaitingMut.Unlock()
|
||||
|
||||
ok := c.send(header{0, id, messageTypePing})
|
||||
ok := c.send(id, messageTypePing, nil)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -242,169 +256,207 @@ func (c *rawConnection) readerLoop() (err error) {
|
||||
default:
|
||||
}
|
||||
|
||||
var hdr header
|
||||
hdr.decodeXDR(c.xr)
|
||||
if err := c.xr.Error(); err != nil {
|
||||
hdr, msg, err := c.readMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hdr.version != 0 {
|
||||
return fmt.Errorf("protocol error: %s: unknown message version %#x", c.id, hdr.version)
|
||||
}
|
||||
|
||||
switch hdr.msgType {
|
||||
case messageTypeIndex:
|
||||
if c.state < stateCCRcvd {
|
||||
return fmt.Errorf("protocol error: index message in state %d", c.state)
|
||||
}
|
||||
if err := c.handleIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.handleIndex(msg.(IndexMessage))
|
||||
c.state = stateIdxRcvd
|
||||
|
||||
case messageTypeIndexUpdate:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: index update message in state %d", c.state)
|
||||
}
|
||||
if err := c.handleIndexUpdate(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.handleIndexUpdate(msg.(IndexMessage))
|
||||
|
||||
case messageTypeRequest:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: request message in state %d", c.state)
|
||||
}
|
||||
if err := c.handleRequest(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
// Requests are handled asynchronously
|
||||
go c.handleRequest(hdr.msgID, msg.(RequestMessage))
|
||||
|
||||
case messageTypeResponse:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: response message in state %d", c.state)
|
||||
}
|
||||
if err := c.handleResponse(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
c.handleResponse(hdr.msgID, msg.(ResponseMessage))
|
||||
|
||||
case messageTypePing:
|
||||
c.send(header{0, hdr.msgID, messageTypePong})
|
||||
c.send(hdr.msgID, messageTypePong, EmptyMessage{})
|
||||
|
||||
case messageTypePong:
|
||||
c.handlePong(hdr)
|
||||
c.handlePong(hdr.msgID)
|
||||
|
||||
case messageTypeClusterConfig:
|
||||
if c.state != stateInitial {
|
||||
return fmt.Errorf("protocol error: cluster config message in state %d", c.state)
|
||||
}
|
||||
if err := c.handleClusterConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
go c.receiver.ClusterConfig(c.id, msg.(ClusterConfigMessage))
|
||||
c.state = stateCCRcvd
|
||||
|
||||
case messageTypeClose:
|
||||
return errors.New(msg.(CloseMessage).Reason)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleIndex() error {
|
||||
var im IndexMessage
|
||||
im.decodeXDR(c.xr)
|
||||
if err := c.xr.Error(); err != nil {
|
||||
return err
|
||||
func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
||||
if cap(c.rdbuf0) < 8 {
|
||||
c.rdbuf0 = make([]byte, 8)
|
||||
} else {
|
||||
if debug {
|
||||
l.Debugf("Index(%v, %v, %d files)", c.id, im.Repository, len(im.Files))
|
||||
}
|
||||
c.receiver.Index(c.id, im.Repository, im.Files)
|
||||
c.rdbuf0 = c.rdbuf0[:8]
|
||||
}
|
||||
_, err = io.ReadFull(c.cr, c.rdbuf0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleIndexUpdate() error {
|
||||
var im IndexMessage
|
||||
im.decodeXDR(c.xr)
|
||||
if err := c.xr.Error(); err != nil {
|
||||
return err
|
||||
hdr = decodeHeader(binary.BigEndian.Uint32(c.rdbuf0[0:4]))
|
||||
msglen := int(binary.BigEndian.Uint32(c.rdbuf0[4:8]))
|
||||
|
||||
if debug {
|
||||
l.Debugf("read header %v (msglen=%d)", hdr, msglen)
|
||||
}
|
||||
|
||||
if cap(c.rdbuf0) < msglen {
|
||||
c.rdbuf0 = make([]byte, msglen)
|
||||
} else {
|
||||
if debug {
|
||||
l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Repository, len(im.Files))
|
||||
c.rdbuf0 = c.rdbuf0[:msglen]
|
||||
}
|
||||
_, err = io.ReadFull(c.cr, c.rdbuf0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("read %d bytes", len(c.rdbuf0))
|
||||
}
|
||||
|
||||
msgBuf := c.rdbuf0
|
||||
if hdr.compression {
|
||||
c.rdbuf1 = c.rdbuf1[:cap(c.rdbuf1)]
|
||||
c.rdbuf1, err = lz4.Decode(c.rdbuf1, c.rdbuf0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
msgBuf = c.rdbuf1
|
||||
if debug {
|
||||
l.Debugf("decompressed to %d bytes", len(msgBuf))
|
||||
}
|
||||
c.receiver.IndexUpdate(c.id, im.Repository, im.Files)
|
||||
}
|
||||
return nil
|
||||
|
||||
if debug {
|
||||
if len(msgBuf) > 1024 {
|
||||
l.Debugf("message data:\n%s", hex.Dump(msgBuf[:1024]))
|
||||
} else {
|
||||
l.Debugf("message data:\n%s", hex.Dump(msgBuf))
|
||||
}
|
||||
}
|
||||
|
||||
switch hdr.msgType {
|
||||
case messageTypeIndex, messageTypeIndexUpdate:
|
||||
var idx IndexMessage
|
||||
err = idx.UnmarshalXDR(msgBuf)
|
||||
msg = idx
|
||||
|
||||
case messageTypeRequest:
|
||||
var req RequestMessage
|
||||
err = req.UnmarshalXDR(msgBuf)
|
||||
msg = req
|
||||
|
||||
case messageTypeResponse:
|
||||
var resp ResponseMessage
|
||||
err = resp.UnmarshalXDR(msgBuf)
|
||||
msg = resp
|
||||
|
||||
case messageTypePing, messageTypePong:
|
||||
msg = EmptyMessage{}
|
||||
|
||||
case messageTypeClusterConfig:
|
||||
var cc ClusterConfigMessage
|
||||
err = cc.UnmarshalXDR(msgBuf)
|
||||
msg = cc
|
||||
|
||||
case messageTypeClose:
|
||||
var cm CloseMessage
|
||||
err = cm.UnmarshalXDR(msgBuf)
|
||||
msg = cm
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleRequest(hdr header) error {
|
||||
var req RequestMessage
|
||||
req.decodeXDR(c.xr)
|
||||
if err := c.xr.Error(); err != nil {
|
||||
return err
|
||||
func (c *rawConnection) handleIndex(im IndexMessage) {
|
||||
if debug {
|
||||
l.Debugf("Index(%v, %v, %d files)", c.id, im.Repository, len(im.Files))
|
||||
}
|
||||
go c.processRequest(hdr.msgID, req)
|
||||
return nil
|
||||
c.receiver.Index(c.id, im.Repository, im.Files)
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleResponse(hdr header) error {
|
||||
data := c.xr.ReadBytesMax(256 * 1024) // Sufficiently larger than max expected block size
|
||||
|
||||
if err := c.xr.Error(); err != nil {
|
||||
return err
|
||||
func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
|
||||
if debug {
|
||||
l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Repository, len(im.Files))
|
||||
}
|
||||
c.receiver.IndexUpdate(c.id, im.Repository, im.Files)
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
|
||||
data, _ := c.receiver.Request(c.id, req.Repository, req.Name, int64(req.Offset), int(req.Size))
|
||||
|
||||
c.send(msgID, messageTypeResponse, ResponseMessage{data})
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
|
||||
c.awaitingMut.Lock()
|
||||
if rc := c.awaiting[hdr.msgID]; rc != nil {
|
||||
c.awaiting[hdr.msgID] = nil
|
||||
rc <- asyncResult{data, nil}
|
||||
if rc := c.awaiting[msgID]; rc != nil {
|
||||
c.awaiting[msgID] = nil
|
||||
rc <- asyncResult{resp.Data, nil}
|
||||
close(rc)
|
||||
}
|
||||
c.awaitingMut.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rawConnection) handlePong(hdr header) {
|
||||
func (c *rawConnection) handlePong(msgID int) {
|
||||
c.awaitingMut.Lock()
|
||||
if rc := c.awaiting[hdr.msgID]; rc != nil {
|
||||
c.awaiting[hdr.msgID] = nil
|
||||
if rc := c.awaiting[msgID]; rc != nil {
|
||||
c.awaiting[msgID] = nil
|
||||
rc <- asyncResult{}
|
||||
close(rc)
|
||||
}
|
||||
c.awaitingMut.Unlock()
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleClusterConfig() error {
|
||||
var cm ClusterConfigMessage
|
||||
cm.decodeXDR(c.xr)
|
||||
if err := c.xr.Error(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
go c.receiver.ClusterConfig(c.id, cm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type encodable interface {
|
||||
encodeXDR(*xdr.Writer) (int, error)
|
||||
}
|
||||
type encodableBytes []byte
|
||||
|
||||
func (e encodableBytes) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
return xw.WriteBytes(e)
|
||||
}
|
||||
|
||||
func (c *rawConnection) send(h header, es ...encodable) bool {
|
||||
if h.msgID < 0 {
|
||||
func (c *rawConnection) send(msgID int, msgType int, msg encodable) bool {
|
||||
if msgID < 0 {
|
||||
select {
|
||||
case id := <-c.nextID:
|
||||
h.msgID = id
|
||||
msgID = id
|
||||
case <-c.closed:
|
||||
return false
|
||||
}
|
||||
}
|
||||
msg := append([]encodable{h}, es...)
|
||||
|
||||
hdr := header{
|
||||
version: 0,
|
||||
msgID: msgID,
|
||||
msgType: msgType,
|
||||
}
|
||||
|
||||
select {
|
||||
case c.outbox <- msg:
|
||||
case c.outbox <- hdrMsg{hdr, msg}:
|
||||
return true
|
||||
case <-c.closed:
|
||||
return false
|
||||
@@ -412,13 +464,71 @@ func (c *rawConnection) send(h header, es ...encodable) bool {
|
||||
}
|
||||
|
||||
func (c *rawConnection) writerLoop() {
|
||||
var msgBuf = make([]byte, 8) // buffer for wire format message, kept and reused
|
||||
var uncBuf []byte // buffer for uncompressed message, kept and reused
|
||||
for {
|
||||
var tempBuf []byte
|
||||
var err error
|
||||
|
||||
select {
|
||||
case es := <-c.outbox:
|
||||
for _, e := range es {
|
||||
e.encodeXDR(c.xw)
|
||||
case hm := <-c.outbox:
|
||||
if hm.msg != nil {
|
||||
// Uncompressed message in uncBuf
|
||||
uncBuf = hm.msg.AppendXDR(uncBuf[:0])
|
||||
|
||||
if len(uncBuf) >= c.compressionThreshold {
|
||||
// Use compression for large messages
|
||||
hm.hdr.compression = true
|
||||
|
||||
// Make sure we have enough space for the compressed message plus header in msgBug
|
||||
msgBuf = msgBuf[:cap(msgBuf)]
|
||||
if maxLen := lz4.CompressBound(len(uncBuf)) + 8; maxLen > len(msgBuf) {
|
||||
msgBuf = make([]byte, maxLen)
|
||||
}
|
||||
|
||||
// Compressed is written to msgBuf, we keep tb for the length only
|
||||
tempBuf, err = lz4.Encode(msgBuf[8:], uncBuf)
|
||||
binary.BigEndian.PutUint32(msgBuf[4:8], uint32(len(tempBuf)))
|
||||
msgBuf = msgBuf[0 : len(tempBuf)+8]
|
||||
|
||||
if debug {
|
||||
l.Debugf("write compressed message; %v (len=%d)", hm.hdr, len(tempBuf))
|
||||
}
|
||||
} else {
|
||||
// No point in compressing very short messages
|
||||
hm.hdr.compression = false
|
||||
|
||||
msgBuf = msgBuf[:cap(msgBuf)]
|
||||
if l := len(uncBuf) + 8; l > len(msgBuf) {
|
||||
msgBuf = make([]byte, l)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(msgBuf[4:8], uint32(len(uncBuf)))
|
||||
msgBuf = msgBuf[0 : len(uncBuf)+8]
|
||||
copy(msgBuf[8:], uncBuf)
|
||||
|
||||
if debug {
|
||||
l.Debugf("write uncompressed message; %v (len=%d)", hm.hdr, len(uncBuf))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if debug {
|
||||
l.Debugf("write empty message; %v", hm.hdr)
|
||||
}
|
||||
binary.BigEndian.PutUint32(msgBuf[4:8], 0)
|
||||
msgBuf = msgBuf[:8]
|
||||
}
|
||||
if err := c.flush(); err != nil {
|
||||
|
||||
binary.BigEndian.PutUint32(msgBuf[0:4], encodeHeader(hm.hdr))
|
||||
|
||||
if err == nil {
|
||||
var n int
|
||||
n, err = c.cw.Write(msgBuf)
|
||||
if debug {
|
||||
l.Debugf("wrote %d bytes on the wire", n)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
c.close(err)
|
||||
return
|
||||
}
|
||||
@@ -428,20 +538,6 @@ func (c *rawConnection) writerLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
type flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func (c *rawConnection) flush() error {
|
||||
if err := c.xw.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.wb.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rawConnection) close(err error) {
|
||||
c.once.Do(func() {
|
||||
close(c.closed)
|
||||
@@ -477,13 +573,13 @@ func (c *rawConnection) pingerLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker:
|
||||
if d := time.Since(c.xr.LastRead()); d < pingIdleTime {
|
||||
if d := time.Since(c.cr.Last()); d < pingIdleTime {
|
||||
if debug {
|
||||
l.Debugln(c.id, "ping skipped after rd", d)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if d := time.Since(c.xw.LastWrite()); d < pingIdleTime {
|
||||
if d := time.Since(c.cw.Last()); d < pingIdleTime {
|
||||
if debug {
|
||||
l.Debugln(c.id, "ping skipped after wr", d)
|
||||
}
|
||||
@@ -515,12 +611,6 @@ func (c *rawConnection) pingerLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rawConnection) processRequest(msgID int, req RequestMessage) {
|
||||
data, _ := c.receiver.Request(c.id, req.Repository, req.Name, int64(req.Offset), int(req.Size))
|
||||
|
||||
c.send(header{0, msgID, messageTypeResponse}, encodableBytes(data))
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
At time.Time
|
||||
InBytesTotal uint64
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/calmh/syncthing/xdr"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -21,7 +23,7 @@ func TestHeaderFunctions(t *testing.T) {
|
||||
ver = int(uint(ver) % 16)
|
||||
id = int(uint(id) % 4096)
|
||||
typ = int(uint(typ) % 256)
|
||||
h0 := header{ver, id, typ}
|
||||
h0 := header{version: ver, msgID: id, msgType: typ}
|
||||
h1 := decodeHeader(encodeHeader(h0))
|
||||
return h0 == h1
|
||||
}
|
||||
@@ -35,21 +37,21 @@ func TestHeaderLayout(t *testing.T) {
|
||||
|
||||
// Version are the first four bits
|
||||
e = 0xf0000000
|
||||
a = encodeHeader(header{0xf, 0, 0})
|
||||
a = encodeHeader(header{version: 0xf})
|
||||
if a != e {
|
||||
t.Errorf("Header layout incorrect; %08x != %08x", a, e)
|
||||
}
|
||||
|
||||
// Message ID are the following 12 bits
|
||||
e = 0x0fff0000
|
||||
a = encodeHeader(header{0, 0xfff, 0})
|
||||
a = encodeHeader(header{msgID: 0xfff})
|
||||
if a != e {
|
||||
t.Errorf("Header layout incorrect; %08x != %08x", a, e)
|
||||
}
|
||||
|
||||
// Type are the last 8 bits before reserved
|
||||
e = 0x0000ff00
|
||||
a = encodeHeader(header{0, 0, 0xff})
|
||||
a = encodeHeader(header{msgType: 0xff})
|
||||
if a != e {
|
||||
t.Errorf("Header layout incorrect; %08x != %08x", a, e)
|
||||
}
|
||||
@@ -59,8 +61,8 @@ func TestPing(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, nil, "name").(wireFormatConnection).next.(*rawConnection)
|
||||
c1 := NewConnection(c1ID, br, aw, nil, "name").(wireFormatConnection).next.(*rawConnection)
|
||||
c0 := NewConnection(c0ID, ar, bw, nil, "name", true).(wireFormatConnection).next.(*rawConnection)
|
||||
c1 := NewConnection(c1ID, br, aw, nil, "name", true).(wireFormatConnection).next.(*rawConnection)
|
||||
|
||||
if ok := c0.ping(); !ok {
|
||||
t.Error("c0 ping failed")
|
||||
@@ -73,8 +75,8 @@ func TestPing(t *testing.T) {
|
||||
func TestPingErr(t *testing.T) {
|
||||
e := errors.New("something broke")
|
||||
|
||||
for i := 0; i < 12; i++ {
|
||||
for j := 0; j < 12; j++ {
|
||||
for i := 0; i < 16; i++ {
|
||||
for j := 0; j < 16; j++ {
|
||||
m0 := newTestModel()
|
||||
m1 := newTestModel()
|
||||
|
||||
@@ -83,13 +85,13 @@ func TestPingErr(t *testing.T) {
|
||||
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
||||
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||
|
||||
c0 := NewConnection(c0ID, ar, ebw, m0, "name").(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, eaw, m1, "name")
|
||||
c0 := NewConnection(c0ID, ar, ebw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, eaw, m1, "name", true)
|
||||
|
||||
res := c0.ping()
|
||||
if (i < 4 || j < 4) && res {
|
||||
if (i < 8 || j < 8) && res {
|
||||
t.Errorf("Unexpected ping success; i=%d, j=%d", i, j)
|
||||
} else if (i >= 8 && j >= 8) && !res {
|
||||
} else if (i >= 12 && j >= 12) && !res {
|
||||
t.Errorf("Unexpected ping fail; i=%d, j=%d", i, j)
|
||||
}
|
||||
}
|
||||
@@ -159,15 +161,16 @@ func TestVersionErr(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name").(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name")
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name", true)
|
||||
|
||||
c0.xw.WriteUint32(encodeHeader(header{
|
||||
w := xdr.NewWriter(c0.cw)
|
||||
w.WriteUint32(encodeHeader(header{
|
||||
version: 2,
|
||||
msgID: 0,
|
||||
msgType: 0,
|
||||
}))
|
||||
c0.flush()
|
||||
w.WriteUint32(0)
|
||||
|
||||
if !m1.isClosed() {
|
||||
t.Error("Connection should close due to unknown version")
|
||||
@@ -181,15 +184,16 @@ func TestTypeErr(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name").(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name")
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name", true)
|
||||
|
||||
c0.xw.WriteUint32(encodeHeader(header{
|
||||
w := xdr.NewWriter(c0.cw)
|
||||
w.WriteUint32(encodeHeader(header{
|
||||
version: 0,
|
||||
msgID: 0,
|
||||
msgType: 42,
|
||||
}))
|
||||
c0.flush()
|
||||
w.WriteUint32(0)
|
||||
|
||||
if !m1.isClosed() {
|
||||
t.Error("Connection should close due to unknown message type")
|
||||
@@ -203,8 +207,8 @@ func TestClose(t *testing.T) {
|
||||
ar, aw := io.Pipe()
|
||||
br, bw := io.Pipe()
|
||||
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name").(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name")
|
||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
||||
NewConnection(c1ID, br, aw, m1, "name", true)
|
||||
|
||||
c0.close(nil)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
|
||||
const StandardBlockSize = 128 * 1024
|
||||
|
||||
var sha256OfNothing = []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}
|
||||
|
||||
// Blocks returns the blockwise hash of the reader.
|
||||
func Blocks(r io.Reader, blocksize int) ([]protocol.BlockInfo, error) {
|
||||
var blocks []protocol.BlockInfo
|
||||
@@ -44,7 +46,7 @@ func Blocks(r io.Reader, blocksize int) ([]protocol.BlockInfo, error) {
|
||||
blocks = append(blocks, protocol.BlockInfo{
|
||||
Offset: 0,
|
||||
Size: 0,
|
||||
Hash: []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55},
|
||||
Hash: sha256OfNothing,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -167,9 +167,6 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo, ign map[string][
|
||||
cf := w.CurrentFiler.CurrentFile(rn)
|
||||
permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
|
||||
if !protocol.IsDeleted(cf.Flags) && protocol.IsDirectory(cf.Flags) && permUnchanged {
|
||||
if debug {
|
||||
l.Debugln("unchanged:", cf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -198,9 +195,6 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo, ign map[string][
|
||||
cf := w.CurrentFiler.CurrentFile(rn)
|
||||
permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
|
||||
if !protocol.IsDeleted(cf.Flags) && cf.Modified == info.ModTime().Unix() && permUnchanged {
|
||||
if debug {
|
||||
l.Debugln("unchanged:", cf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,18 +7,15 @@ package xdr
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrElementSizeExceeded = errors.New("element size exceeded")
|
||||
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
tot int
|
||||
err error
|
||||
b [8]byte
|
||||
sb []byte
|
||||
last time.Time
|
||||
r io.Reader
|
||||
err error
|
||||
b [8]byte
|
||||
sb []byte
|
||||
}
|
||||
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
@@ -63,8 +60,6 @@ func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
|
||||
if r.err != nil {
|
||||
return nil
|
||||
}
|
||||
r.last = time.Now()
|
||||
s := r.tot
|
||||
|
||||
l := int(r.ReadUint32())
|
||||
if r.err != nil {
|
||||
@@ -85,17 +80,16 @@ func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
|
||||
n, r.err = io.ReadFull(r.r, dst)
|
||||
if r.err != nil {
|
||||
if debug {
|
||||
dl.Debugf("@0x%x: rd bytes (%d): %v", s, len(dst), r.err)
|
||||
dl.Debugf("rd bytes (%d): %v", len(dst), r.err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
r.tot += n
|
||||
|
||||
if debug {
|
||||
if n > maxDebugBytes {
|
||||
dl.Debugf("@0x%x: rd bytes (%d): %x...", s, len(dst), dst[:maxDebugBytes])
|
||||
dl.Debugf("rd bytes (%d): %x...", len(dst), dst[:maxDebugBytes])
|
||||
} else {
|
||||
dl.Debugf("@0x%x: rd bytes (%d): %x", s, len(dst), dst)
|
||||
dl.Debugf("rd bytes (%d): %x", len(dst), dst)
|
||||
}
|
||||
}
|
||||
return dst[:l]
|
||||
@@ -113,15 +107,11 @@ func (r *Reader) ReadUint32() uint32 {
|
||||
if r.err != nil {
|
||||
return 0
|
||||
}
|
||||
r.last = time.Now()
|
||||
s := r.tot
|
||||
|
||||
var n int
|
||||
n, r.err = io.ReadFull(r.r, r.b[:4])
|
||||
r.tot += n
|
||||
_, r.err = io.ReadFull(r.r, r.b[:4])
|
||||
if r.err != nil {
|
||||
if debug {
|
||||
dl.Debugf("@0x%x: rd uint32: %v", r.tot, r.err)
|
||||
dl.Debugf("rd uint32: %v", r.err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -129,7 +119,7 @@ func (r *Reader) ReadUint32() uint32 {
|
||||
v := uint32(r.b[3]) | uint32(r.b[2])<<8 | uint32(r.b[1])<<16 | uint32(r.b[0])<<24
|
||||
|
||||
if debug {
|
||||
dl.Debugf("@0x%x: rd uint32=%d (0x%08x)", s, v, v)
|
||||
dl.Debugf("rd uint32=%d (0x%08x)", v, v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
@@ -138,15 +128,11 @@ func (r *Reader) ReadUint64() uint64 {
|
||||
if r.err != nil {
|
||||
return 0
|
||||
}
|
||||
r.last = time.Now()
|
||||
s := r.tot
|
||||
|
||||
var n int
|
||||
n, r.err = io.ReadFull(r.r, r.b[:8])
|
||||
r.tot += n
|
||||
_, r.err = io.ReadFull(r.r, r.b[:8])
|
||||
if r.err != nil {
|
||||
if debug {
|
||||
dl.Debugf("@0x%x: rd uint64: %v", r.tot, r.err)
|
||||
dl.Debugf("rd uint64: %v", r.err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -155,19 +141,23 @@ func (r *Reader) ReadUint64() uint64 {
|
||||
uint64(r.b[3])<<32 | uint64(r.b[2])<<40 | uint64(r.b[1])<<48 | uint64(r.b[0])<<56
|
||||
|
||||
if debug {
|
||||
dl.Debugf("@0x%x: rd uint64=%d (0x%016x)", s, v, v)
|
||||
dl.Debugf("rd uint64=%d (0x%016x)", v, v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (r *Reader) Tot() int {
|
||||
return r.tot
|
||||
type XDRError struct {
|
||||
op string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e XDRError) Error() string {
|
||||
return "xdr " + e.op + ": " + e.err.Error()
|
||||
}
|
||||
|
||||
func (r *Reader) Error() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *Reader) LastRead() time.Time {
|
||||
return r.last
|
||||
if r.err == nil {
|
||||
return nil
|
||||
}
|
||||
return XDRError{"read", r.err}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
|
||||
package xdr
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
import "io"
|
||||
|
||||
func pad(l int) int {
|
||||
d := l % 4
|
||||
@@ -20,11 +17,10 @@ func pad(l int) int {
|
||||
var padBytes = []byte{0, 0, 0}
|
||||
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
tot int
|
||||
err error
|
||||
b [8]byte
|
||||
last time.Time
|
||||
w io.Writer
|
||||
tot int
|
||||
err error
|
||||
b [8]byte
|
||||
}
|
||||
|
||||
type AppendWriter []byte
|
||||
@@ -49,7 +45,6 @@ func (w *Writer) WriteBytes(bs []byte) (int, error) {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
w.last = time.Now()
|
||||
w.WriteUint32(uint32(len(bs)))
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
@@ -93,7 +88,6 @@ func (w *Writer) WriteUint32(v uint32) (int, error) {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
w.last = time.Now()
|
||||
if debug {
|
||||
dl.Debugf("wr uint32=%d", v)
|
||||
}
|
||||
@@ -114,7 +108,6 @@ func (w *Writer) WriteUint64(v uint64) (int, error) {
|
||||
return 0, w.err
|
||||
}
|
||||
|
||||
w.last = time.Now()
|
||||
if debug {
|
||||
dl.Debugf("wr uint64=%d", v)
|
||||
}
|
||||
@@ -139,9 +132,8 @@ func (w *Writer) Tot() int {
|
||||
}
|
||||
|
||||
func (w *Writer) Error() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func (w *Writer) LastWrite() time.Time {
|
||||
return w.last
|
||||
if w.err == nil {
|
||||
return nil
|
||||
}
|
||||
return XDRError{"write", w.err}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user