Compare commits

..

18 Commits

Author SHA1 Message Date
Jakob Borg
960b40fa89 Translation update 2015-03-22 10:34:45 +01:00
Stefan Tatschner
afad329e99 systemd: Set -logflags to 0, provide -no-browser flag
Syncthing should not try to start a browser when invoked by systemd.
Furthermore we do not need any timestamps in the journal as systemd
already handles this for us.
2015-03-22 10:26:53 +01:00
Jakob Borg
4025284fba Update integration test configs to v10 2015-03-22 10:26:53 +01:00
Jakob Borg
a595e814dd Set defaults correctly for autoNormalize
The default:"foo" struct tags aren't actually used for folder configs.
2015-03-22 10:26:51 +01:00
Alexander Graf
963d8121d9 use Lstat instead of Stat to prevent errors with symlinks 2015-03-22 08:48:37 +01:00
Audrius Butkevicius
03019988b1 Skip unspecified IPs 2015-03-22 08:48:37 +01:00
Audrius Butkevicius
97115afa32 Print LANs on startup 2015-03-22 08:48:37 +01:00
Jakob Borg
c9f5bae177 Decide once and for all to return filepath.SkipDir or nil 2015-03-22 08:47:36 +01:00
Jakob Borg
2bd11ca4e3 Automatically fix file name normalization errors (fixes #430) 2015-03-22 08:47:34 +01:00
Jakob Borg
a5de1acb46 Use SVG format logos 2015-03-22 08:46:54 +01:00
Jakob Borg
5581751e9d Rename files to match type names 2015-03-22 08:46:43 +01:00
Jakob Borg
055ae92273 Refactor state tracking (...)
Move state tracking into the puller/scanner objects. This is a first
step towards resolving #1391.

Rename Puller and Scanner to roFolder and rwFolder as they have more
duties than just pulling and scanning, and don't need to be exported.
2015-03-22 08:46:43 +01:00
Audrius Butkevicius
dea7c77055 Rebuild assets 2015-03-22 08:46:41 +01:00
Audrius Butkevicius
765dda6ad7 Fix build 2015-03-22 08:46:26 +01:00
Jakob Borg
28702a1c9d Add /rest/filestatus 2015-03-22 08:46:26 +01:00
Jakob Borg
40d1226612 MPLv2 2015-03-22 08:46:25 +01:00
Johan Vromans
effe8ce8a9 Suppress 'Last File Received' if a node is folder master (fixes #1472) 2015-03-22 08:46:24 +01:00
Jakob Borg
4c3ba24826 Add sciurius 2015-03-22 08:45:42 +01:00
155 changed files with 2896 additions and 10834 deletions

View File

@@ -24,15 +24,14 @@ Felix Unterpaintner <bigbear2nd@gmail.com>
Gilli Sigurdsson <gilli@vx.is>
Jakob Borg <jakob@nym.se>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jaroslav Malec <dzardacz@gmail.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
Jochen Voss <voss@seehuhn.de>
Johan Vromans <jvromans@squirrel.nl>
Karol Różycki <rozycki.karol@gmail.com>
Ken'ichi Kamada <kamada@nanohz.org>
Kamada Ken'ichi <kamada@nanohz.org>
Lode Hoste <zillode@zillode.be>
Marcin Dziadus <dziadus.marcin@gmail.com>
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
Marc Laporte <marc@marclaporte.com>
Marc Pujol <kilburn@la3.org>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com>
@@ -42,7 +41,6 @@ Philippe Schommers <philippe@schommers.be>
Phill Luby <phill.luby@newredo.com>
Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Sergey Mishin <ralder@yandex.ru>
Stefan Tatschner <stefan@sevenbyte.org>
Tim Abell <tim@timwise.co.uk>
Tobias Nygren <tnn@nygren.pp.se>

24
Godeps/Godeps.json generated
View File

@@ -19,31 +19,27 @@
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "bccf335c34c01760bdc89f98c952fcda696e27d2"
"Rev": "ff948d7666c5e0fd18d398f6278881724d36a90b"
},
{
"ImportPath": "github.com/juju/ratelimit",
"Rev": "c5abe513796336ee2869745bff0638508450e9c5"
"Rev": "f9f36d11773655c0485207f0ad30dc2655f69d56"
},
{
"ImportPath": "github.com/kardianos/osext",
"Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
"Rev": "91292666f7e40f03185cdd1da7d85633c973eca7"
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "6277c0595c18d42e9db75dfe900463ef093a82d2"
"Rev": "1a4398cc55c8fe82a964097eaf59f2475b020a49"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "87e4e645d80ae9c537e8f2dee52b28036a5dd75e"
"Rev": "e3f32eb300aa1e514fe8ba58d008da90a062273d"
},
{
"ImportPath": "github.com/syndtr/gosnappy/snappy",
"Rev": "156a073208e131d7d2e212cb749feae7c339e846"
},
{
"ImportPath": "github.com/thejerf/suture",
"Rev": "ff19fb384c3fe30f42717967eaa69da91e5f317c"
"Rev": "ce8acff4829e0c2458a67ead32390ac0a381c862"
},
{
"ImportPath": "github.com/vitrun/qart/coding",
@@ -59,19 +55,19 @@
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/text/transform",
"Rev": "2076e9cab4147459c82bc81169e46c139d358547"
"Rev": "c980adc4a823548817b9c47d38c6ca6b7d7d8b6a"
},
{
"ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "2076e9cab4147459c82bc81169e46c139d358547"
"Rev": "c980adc4a823548817b9c47d38c6ca6b7d7d8b6a"
}
]
}

View File

@@ -52,7 +52,7 @@ import (
var encodeTpl = template.Must(template.New("encoder").Parse(`
func (o {{.TypeName}}) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}//+n
func (o {{.TypeName}}) MarshalXDR() ([]byte, error) {
@@ -70,11 +70,11 @@ func (o {{.TypeName}}) MustMarshalXDR() []byte {
func (o {{.TypeName}}) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}//+n
func (o {{.TypeName}}) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o {{.TypeName}}) encodeXDR(xw *xdr.Writer) (int, error) {
{{range $fieldInfo := .Fields}}
{{if not $fieldInfo.IsSlice}}
{{if ne $fieldInfo.Convert ""}}
@@ -87,7 +87,7 @@ func (o {{.TypeName}}) EncodeXDRInto(xw *xdr.Writer) (int, error) {
{{end}}
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}})
{{else}}
_, err := o.{{$fieldInfo.Name}}.EncodeXDRInto(xw)
_, err := o.{{$fieldInfo.Name}}.encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -105,7 +105,7 @@ func (o {{.TypeName}}) EncodeXDRInto(xw *xdr.Writer) (int, error) {
{{else if $fieldInfo.IsBasic}}
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}}[i])
{{else}}
_, err := o.{{$fieldInfo.Name}}[i].EncodeXDRInto(xw)
_, err := o.{{$fieldInfo.Name}}[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -118,16 +118,16 @@ func (o {{.TypeName}}) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *{{.TypeName}}) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}//+n
func (o *{{.TypeName}}) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}//+n
func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *{{.TypeName}}) decodeXDR(xr *xdr.Reader) error {
{{range $fieldInfo := .Fields}}
{{if not $fieldInfo.IsSlice}}
{{if ne $fieldInfo.Convert ""}}
@@ -139,7 +139,7 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
o.{{$fieldInfo.Name}} = xr.Read{{$fieldInfo.Encoder}}()
{{end}}
{{else}}
(&o.{{$fieldInfo.Name}}).DecodeXDRFrom(xr)
(&o.{{$fieldInfo.Name}}).decodeXDR(xr)
{{end}}
{{else}}
_{{$fieldInfo.Name}}Size := int(xr.ReadUint32())
@@ -155,7 +155,7 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
{{else if $fieldInfo.IsBasic}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
{{else}}
(&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr)
(&o.{{$fieldInfo.Name}}[i]).decodeXDR(xr)
{{end}}
}
{{end}}
@@ -257,18 +257,12 @@ func handleStruct(t *ast.StructType) []fieldInfo {
} else {
f = fieldInfo{
Name: fn,
IsBasic: false,
IsSlice: true,
FieldType: tn,
Max: max,
}
}
case *ast.SelectorExpr:
f = fieldInfo{
Name: fn,
FieldType: ft.Sel.Name,
Max: max,
}
}
fs = append(fs, f)
@@ -316,9 +310,10 @@ func generateDiagram(output io.Writer, s structInfo) {
for _, f := range fs {
tn := f.FieldType
sl := f.IsSlice
name := uncamelize(f.Name)
if f.IsSlice {
if sl {
fmt.Fprintf(output, "| %s |\n", center("Number of "+name, 61))
fmt.Fprintln(output, line)
}
@@ -345,16 +340,13 @@ func generateDiagram(output io.Writer, s structInfo) {
fmt.Fprintf(output, "/ %61s /\n", "")
fmt.Fprintln(output, line)
default:
if f.IsSlice {
if sl {
tn = "Zero or more " + tn + " Structures"
fmt.Fprintf(output, "/ %s /\n", center("", 61))
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
fmt.Fprintf(output, "/ %s /\n", center("", 61))
} else {
tn = tn + " Structure"
fmt.Fprintf(output, "/ %s /\n", center("", 61))
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
fmt.Fprintf(output, "/ %s /\n", center("", 61))
fmt.Fprintf(output, "| %s |\n", center(tn, 61))
}
fmt.Fprintln(output, line)
}

View File

@@ -1,6 +1,3 @@
This package contains an efficient token-bucket-based rate limiter.
Copyright (C) 2015 Canonical Ltd.
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3

View File

@@ -11,18 +11,12 @@ import (
"fmt"
"os"
"runtime"
"strings"
)
func executable() (string, error) {
switch runtime.GOOS {
case "linux":
const deletedSuffix = " (deleted)"
execpath, err := os.Readlink("/proc/self/exe")
if err != nil {
return execpath, err
}
return strings.TrimSuffix(execpath, deletedSuffix), nil
return os.Readlink("/proc/self/exe")
case "netbsd":
return os.Readlink("/proc/curproc/exe")
case "openbsd", "dragonfly":

View File

@@ -7,42 +7,35 @@
package osext
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
oexec "os/exec"
"path/filepath"
"runtime"
"testing"
)
const (
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
executableEnvValueMatch = "match"
executableEnvValueDelete = "delete"
)
func TestExecutableMatch(t *testing.T) {
func TestExecPath(t *testing.T) {
ep, err := Executable()
if err != nil {
t.Fatalf("Executable failed: %v", err)
t.Fatalf("ExecPath failed: %v", err)
}
// fullpath to be of the form "dir/prog".
// we want fn to be of the form "dir/prog"
dir := filepath.Dir(filepath.Dir(ep))
fullpath, err := filepath.Rel(dir, ep)
fn, err := filepath.Rel(dir, ep)
if err != nil {
t.Fatalf("filepath.Rel: %v", err)
}
// Make child start with a relative program path.
// Alter argv[0] for child to verify getting real path without argv[0].
cmd := &exec.Cmd{
Dir: dir,
Path: fullpath,
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
}
cmd := &oexec.Cmd{}
// make child start with a relative program path
cmd.Dir = dir
cmd.Path = fn
// forge argv[0] for child, so that we can verify we could correctly
// get real path of the executable without influenced by argv[0].
cmd.Args = []string{"-", "-test.run=XXXX"}
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("exec(self) failed: %v", err)
@@ -56,63 +49,6 @@ func TestExecutableMatch(t *testing.T) {
}
}
func TestExecutableDelete(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip()
}
fpath, err := Executable()
if err != nil {
t.Fatalf("Executable failed: %v", err)
}
r, w := io.Pipe()
stderrBuff := &bytes.Buffer{}
stdoutBuff := &bytes.Buffer{}
cmd := &exec.Cmd{
Path: fpath,
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
Stdin: r,
Stderr: stderrBuff,
Stdout: stdoutBuff,
}
err = cmd.Start()
if err != nil {
t.Fatalf("exec(self) start failed: %v", err)
}
tempPath := fpath + "_copy"
_ = os.Remove(tempPath)
err = copyFile(tempPath, fpath)
if err != nil {
t.Fatalf("copy file failed: %v", err)
}
err = os.Remove(fpath)
if err != nil {
t.Fatalf("remove running test file failed: %v", err)
}
err = os.Rename(tempPath, fpath)
if err != nil {
t.Fatalf("rename copy to previous name failed: %v", err)
}
w.Write([]byte{0})
w.Close()
err = cmd.Wait()
if err != nil {
t.Fatalf("exec wait failed: %v", err)
}
childPath := stderrBuff.String()
if !filepath.IsAbs(childPath) {
t.Fatalf("Child returned %q, want an absolute path", childPath)
}
if !sameFile(childPath, fpath) {
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
}
}
func sameFile(fn1, fn2 string) bool {
fi1, err := os.Stat(fn1)
if err != nil {
@@ -124,30 +60,10 @@ func sameFile(fn1, fn2 string) bool {
}
return os.SameFile(fi1, fi2)
}
func copyFile(dest, src string) error {
df, err := os.Create(dest)
if err != nil {
return err
}
defer df.Close()
sf, err := os.Open(src)
if err != nil {
return err
}
defer sf.Close()
_, err = io.Copy(df, sf)
return err
}
func TestMain(m *testing.M) {
env := os.Getenv(executableEnvVar)
switch env {
case "":
os.Exit(m.Run())
case executableEnvValueMatch:
// First chdir to another path.
func init() {
if e := os.Getenv(execPath_EnvVar); e != "" {
// first chdir to another path
dir := "/"
if runtime.GOOS == "windows" {
dir = filepath.VolumeName(".")
@@ -158,23 +74,6 @@ func TestMain(m *testing.M) {
} else {
fmt.Fprint(os.Stderr, ep)
}
case executableEnvValueDelete:
bb := make([]byte, 1)
var err error
n, err := os.Stdin.Read(bb)
if err != nil {
fmt.Fprint(os.Stderr, "ERROR: ", err)
os.Exit(2)
}
if n != 1 {
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
os.Exit(2)
}
if ep, err := Executable(); err != nil {
fmt.Fprint(os.Stderr, "ERROR: ", err)
} else {
fmt.Fprint(os.Stderr, ep)
}
os.Exit(0)
}
os.Exit(0)
}

View File

@@ -13,9 +13,6 @@ type TestModel struct {
name string
offset int64
size int
hash []byte
flags uint32
options []Option
closedCh chan bool
}
@@ -25,20 +22,17 @@ func newTestModel() *TestModel {
}
}
func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
}
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
}
func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, size int) ([]byte, error) {
t.folder = folder
t.name = name
t.offset = offset
t.size = size
t.hash = hash
t.flags = flags
t.options = options
return t.data, nil
}

View File

@@ -6,7 +6,6 @@ import (
"bytes"
"crypto/sha256"
"encoding/base32"
"encoding/binary"
"errors"
"fmt"
"regexp"
@@ -68,11 +67,6 @@ func (n DeviceID) Equals(other DeviceID) bool {
return bytes.Compare(n[:], other[:]) == 0
}
// Short returns an integer representing bits 0-63 of the device ID.
func (n DeviceID) Short() uint64 {
return binary.BigEndian.Uint64(n[:])
}
func (n *DeviceID) MarshalText() ([]byte, error) {
return []byte(n.String()), nil
}

View File

@@ -1,51 +0,0 @@
// Copyright (C) 2014 The Protocol Authors.
package protocol
import (
"errors"
)
const (
ecNoError int32 = iota
ecGeneric
ecNoSuchFile
ecInvalid
)
var (
ErrNoError error = nil
ErrGeneric = errors.New("generic error")
ErrNoSuchFile = errors.New("no such file")
ErrInvalid = errors.New("file is invalid")
)
var lookupError = map[int32]error{
ecNoError: ErrNoError,
ecGeneric: ErrGeneric,
ecNoSuchFile: ErrNoSuchFile,
ecInvalid: ErrInvalid,
}
var lookupCode = map[error]int32{
ErrNoError: ecNoError,
ErrGeneric: ecGeneric,
ErrNoSuchFile: ecNoSuchFile,
ErrInvalid: ecInvalid,
}
func codeToError(errcode int32) error {
err, ok := lookupError[errcode]
if !ok {
return ErrGeneric
}
return err
}
func errorToCode(err error) int32 {
code, ok := lookupCode[err]
if !ok {
return ecGeneric
}
return code
}

View File

@@ -1,6 +1,5 @@
// Copyright (C) 2014 The Protocol Authors.
//go:generate -command genxdr go run ../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o message_xdr.go message.go
package protocol
@@ -18,13 +17,13 @@ type FileInfo struct {
Name string // max:8192
Flags uint32
Modified int64
Version Vector
Version int64
LocalVersion int64
Blocks []BlockInfo
}
func (f FileInfo) String() string {
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%v, Size:%d, Blocks:%v}",
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, Blocks:%v}",
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.Blocks)
}
@@ -79,8 +78,8 @@ type RequestMessage struct {
}
type ResponseMessage struct {
Data []byte
Code int32
Data []byte
Error int32
}
type ClusterConfigMessage struct {
@@ -102,15 +101,12 @@ func (o *ClusterConfigMessage) GetOption(key string) string {
type Folder struct {
ID string // max:64
Devices []Device
Flags uint32
Options []Option // max:64
}
type Device struct {
ID []byte // max:32
MaxLocalVersion int64
Flags uint32
Options []Option // max:64
MaxLocalVersion int64
}
type Option struct {

View File

@@ -51,7 +51,7 @@ struct IndexMessage {
func (o IndexMessage) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o IndexMessage) MarshalXDR() ([]byte, error) {
@@ -69,15 +69,15 @@ func (o IndexMessage) MustMarshalXDR() []byte {
func (o IndexMessage) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o IndexMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteString(o.Folder)
xw.WriteUint32(uint32(len(o.Files)))
for i := range o.Files {
_, err := o.Files[i].EncodeXDRInto(xw)
_, err := o.Files[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -88,7 +88,7 @@ func (o IndexMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].EncodeXDRInto(xw)
_, err := o.Options[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -98,21 +98,21 @@ func (o IndexMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *IndexMessage) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *IndexMessage) decodeXDR(xr *xdr.Reader) error {
o.Folder = xr.ReadString()
_FilesSize := int(xr.ReadUint32())
o.Files = make([]FileInfo, _FilesSize)
for i := range o.Files {
(&o.Files[i]).DecodeXDRFrom(xr)
(&o.Files[i]).decodeXDR(xr)
}
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
@@ -121,7 +121,7 @@ func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error {
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).DecodeXDRFrom(xr)
(&o.Options[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -145,9 +145,9 @@ FileInfo Structure:
+ Modified (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Vector Structure \
/ /
| |
+ Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Local Version (64 bits) +
@@ -165,7 +165,7 @@ struct FileInfo {
string Name<8192>;
unsigned int Flags;
hyper Modified;
Vector Version;
hyper Version;
hyper LocalVersion;
BlockInfo Blocks<>;
}
@@ -174,7 +174,7 @@ struct FileInfo {
func (o FileInfo) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o FileInfo) MarshalXDR() ([]byte, error) {
@@ -192,25 +192,22 @@ func (o FileInfo) MustMarshalXDR() []byte {
func (o FileInfo) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o FileInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Name); l > 8192 {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
}
xw.WriteString(o.Name)
xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified))
_, err := o.Version.EncodeXDRInto(xw)
if err != nil {
return xw.Tot(), err
}
xw.WriteUint64(uint64(o.Version))
xw.WriteUint64(uint64(o.LocalVersion))
xw.WriteUint32(uint32(len(o.Blocks)))
for i := range o.Blocks {
_, err := o.Blocks[i].EncodeXDRInto(xw)
_, err := o.Blocks[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -220,25 +217,25 @@ func (o FileInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *FileInfo) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *FileInfo) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *FileInfo) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
o.Name = xr.ReadStringMax(8192)
o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64())
(&o.Version).DecodeXDRFrom(xr)
o.Version = int64(xr.ReadUint64())
o.LocalVersion = int64(xr.ReadUint64())
_BlocksSize := int(xr.ReadUint32())
o.Blocks = make([]BlockInfo, _BlocksSize)
for i := range o.Blocks {
(&o.Blocks[i]).DecodeXDRFrom(xr)
(&o.Blocks[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -269,7 +266,7 @@ struct BlockInfo {
func (o BlockInfo) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o BlockInfo) MarshalXDR() ([]byte, error) {
@@ -287,11 +284,11 @@ func (o BlockInfo) MustMarshalXDR() []byte {
func (o BlockInfo) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o BlockInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o BlockInfo) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteUint32(uint32(o.Size))
if l := len(o.Hash); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
@@ -302,16 +299,16 @@ func (o BlockInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *BlockInfo) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *BlockInfo) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *BlockInfo) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *BlockInfo) decodeXDR(xr *xdr.Reader) error {
o.Size = int32(xr.ReadUint32())
o.Hash = xr.ReadBytesMax(64)
return xr.Error()
@@ -372,7 +369,7 @@ struct RequestMessage {
func (o RequestMessage) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o RequestMessage) MarshalXDR() ([]byte, error) {
@@ -390,11 +387,11 @@ func (o RequestMessage) MustMarshalXDR() []byte {
func (o RequestMessage) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o RequestMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o RequestMessage) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Folder); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 64)
}
@@ -415,7 +412,7 @@ func (o RequestMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].EncodeXDRInto(xw)
_, err := o.Options[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -425,16 +422,16 @@ func (o RequestMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *RequestMessage) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *RequestMessage) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *RequestMessage) decodeXDR(xr *xdr.Reader) error {
o.Folder = xr.ReadStringMax(64)
o.Name = xr.ReadStringMax(8192)
o.Offset = int64(xr.ReadUint64())
@@ -447,7 +444,7 @@ func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error {
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).DecodeXDRFrom(xr)
(&o.Options[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -465,20 +462,20 @@ ResponseMessage Structure:
\ Data (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code |
| Error |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct ResponseMessage {
opaque Data<>;
int Code;
int Error;
}
*/
func (o ResponseMessage) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o ResponseMessage) MarshalXDR() ([]byte, error) {
@@ -496,30 +493,30 @@ func (o ResponseMessage) MustMarshalXDR() []byte {
func (o ResponseMessage) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o ResponseMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o ResponseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteBytes(o.Data)
xw.WriteUint32(uint32(o.Code))
xw.WriteUint32(uint32(o.Error))
return xw.Tot(), xw.Error()
}
func (o *ResponseMessage) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *ResponseMessage) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *ResponseMessage) decodeXDR(xr *xdr.Reader) error {
o.Data = xr.ReadBytes()
o.Code = int32(xr.ReadUint32())
o.Error = int32(xr.ReadUint32())
return xr.Error()
}
@@ -567,7 +564,7 @@ struct ClusterConfigMessage {
func (o ClusterConfigMessage) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o ClusterConfigMessage) MarshalXDR() ([]byte, error) {
@@ -585,11 +582,11 @@ func (o ClusterConfigMessage) MustMarshalXDR() []byte {
func (o ClusterConfigMessage) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o ClusterConfigMessage) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.ClientName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("ClientName", l, 64)
}
@@ -600,7 +597,7 @@ func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
xw.WriteString(o.ClientVersion)
xw.WriteUint32(uint32(len(o.Folders)))
for i := range o.Folders {
_, err := o.Folders[i].EncodeXDRInto(xw)
_, err := o.Folders[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -610,7 +607,7 @@ func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].EncodeXDRInto(xw)
_, err := o.Options[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -620,22 +617,22 @@ func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *ClusterConfigMessage) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *ClusterConfigMessage) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *ClusterConfigMessage) decodeXDR(xr *xdr.Reader) error {
o.ClientName = xr.ReadStringMax(64)
o.ClientVersion = xr.ReadStringMax(64)
_FoldersSize := int(xr.ReadUint32())
o.Folders = make([]Folder, _FoldersSize)
for i := range o.Folders {
(&o.Folders[i]).DecodeXDRFrom(xr)
(&o.Folders[i]).decodeXDR(xr)
}
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize > 64 {
@@ -643,7 +640,7 @@ func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).DecodeXDRFrom(xr)
(&o.Options[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -667,28 +664,18 @@ Folder Structure:
\ Zero or more Device Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Options |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Option Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Folder {
string ID<64>;
Device Devices<>;
unsigned int Flags;
Option Options<64>;
}
*/
func (o Folder) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o Folder) MarshalXDR() ([]byte, error) {
@@ -706,29 +693,18 @@ func (o Folder) MustMarshalXDR() []byte {
func (o Folder) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o Folder) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o Folder) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.ID); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 64)
}
xw.WriteString(o.ID)
xw.WriteUint32(uint32(len(o.Devices)))
for i := range o.Devices {
_, err := o.Devices[i].EncodeXDRInto(xw)
if err != nil {
return xw.Tot(), err
}
}
xw.WriteUint32(o.Flags)
if l := len(o.Options); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].EncodeXDRInto(xw)
_, err := o.Devices[i].encodeXDR(xw)
if err != nil {
return xw.Tot(), err
}
@@ -738,30 +714,21 @@ func (o Folder) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *Folder) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *Folder) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *Folder) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *Folder) decodeXDR(xr *xdr.Reader) error {
o.ID = xr.ReadStringMax(64)
_DevicesSize := int(xr.ReadUint32())
o.Devices = make([]Device, _DevicesSize)
for i := range o.Devices {
(&o.Devices[i]).DecodeXDRFrom(xr)
}
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).DecodeXDRFrom(xr)
(&o.Devices[i]).decodeXDR(xr)
}
return xr.Error()
}
@@ -779,32 +746,25 @@ Device Structure:
\ ID (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Max Local Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Options |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Zero or more Option Structures \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct Device {
opaque ID<32>;
hyper MaxLocalVersion;
unsigned int Flags;
Option Options<64>;
hyper MaxLocalVersion;
}
*/
func (o Device) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o Device) MarshalXDR() ([]byte, error) {
@@ -822,53 +782,35 @@ func (o Device) MustMarshalXDR() []byte {
func (o Device) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o Device) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.ID); l > 32 {
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
}
xw.WriteBytes(o.ID)
xw.WriteUint64(uint64(o.MaxLocalVersion))
xw.WriteUint32(o.Flags)
if l := len(o.Options); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
}
xw.WriteUint32(uint32(len(o.Options)))
for i := range o.Options {
_, err := o.Options[i].EncodeXDRInto(xw)
if err != nil {
return xw.Tot(), err
}
}
xw.WriteUint64(uint64(o.MaxLocalVersion))
return xw.Tot(), xw.Error()
}
func (o *Device) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *Device) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *Device) decodeXDR(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32)
o.MaxLocalVersion = int64(xr.ReadUint64())
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
if _OptionsSize > 64 {
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
}
o.Options = make([]Option, _OptionsSize)
for i := range o.Options {
(&o.Options[i]).DecodeXDRFrom(xr)
}
o.MaxLocalVersion = int64(xr.ReadUint64())
return xr.Error()
}
@@ -902,7 +844,7 @@ struct Option {
func (o Option) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o Option) MarshalXDR() ([]byte, error) {
@@ -920,11 +862,11 @@ func (o Option) MustMarshalXDR() []byte {
func (o Option) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o Option) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o Option) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Key); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 64)
}
@@ -938,16 +880,16 @@ func (o Option) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *Option) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *Option) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *Option) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *Option) decodeXDR(xr *xdr.Reader) error {
o.Key = xr.ReadStringMax(64)
o.Value = xr.ReadStringMax(1024)
return xr.Error()
@@ -979,7 +921,7 @@ struct CloseMessage {
func (o CloseMessage) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o CloseMessage) MarshalXDR() ([]byte, error) {
@@ -997,11 +939,11 @@ func (o CloseMessage) MustMarshalXDR() []byte {
func (o CloseMessage) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o CloseMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o CloseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Reason); l > 1024 {
return xw.Tot(), xdr.ElementSizeExceeded("Reason", l, 1024)
}
@@ -1012,16 +954,16 @@ func (o CloseMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
func (o *CloseMessage) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *CloseMessage) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *CloseMessage) decodeXDR(xr *xdr.Reader) error {
o.Reason = xr.ReadStringMax(1024)
o.Code = int32(xr.ReadUint32())
return xr.Error()
@@ -1043,7 +985,7 @@ struct EmptyMessage {
func (o EmptyMessage) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.EncodeXDRInto(xw)
return o.encodeXDR(xw)
}
func (o EmptyMessage) MarshalXDR() ([]byte, error) {
@@ -1061,25 +1003,25 @@ func (o EmptyMessage) MustMarshalXDR() []byte {
func (o EmptyMessage) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.EncodeXDRInto(xw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o EmptyMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
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.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *EmptyMessage) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.DecodeXDRFrom(xr)
return o.decodeXDR(xr)
}
func (o *EmptyMessage) DecodeXDRFrom(xr *xdr.Reader) error {
func (o *EmptyMessage) decodeXDR(xr *xdr.Reader) error {
return xr.Error()
}

View File

@@ -12,23 +12,23 @@ type nativeModel struct {
next Model
}
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
for i := range files {
files[i].Name = norm.NFD.String(files[i].Name)
}
m.next.Index(deviceID, folder, files, flags, options)
m.next.Index(deviceID, folder, files)
}
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
for i := range files {
files[i].Name = norm.NFD.String(files[i].Name)
}
m.next.IndexUpdate(deviceID, folder, files, flags, options)
m.next.IndexUpdate(deviceID, folder, files)
}
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int) ([]byte, error) {
name = norm.NFD.String(name)
return m.next.Request(deviceID, folder, name, offset, size, hash, flags, options)
return m.next.Request(deviceID, folder, name, offset, size)
}
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {

View File

@@ -10,16 +10,16 @@ type nativeModel struct {
next Model
}
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
m.next.Index(deviceID, folder, files, flags, options)
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
m.next.Index(deviceID, folder, files)
}
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
m.next.IndexUpdate(deviceID, folder, files, flags, options)
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
m.next.IndexUpdate(deviceID, folder, files)
}
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
return m.next.Request(deviceID, folder, name, offset, size, hash, flags, options)
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int) ([]byte, error) {
return m.next.Request(deviceID, folder, name, offset, size)
}
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {

View File

@@ -24,30 +24,23 @@ type nativeModel struct {
next Model
}
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
fixupFiles(files)
m.next.Index(deviceID, folder, files, flags, options)
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
for i, f := range files {
if strings.ContainsAny(f.Name, disallowedCharacters) {
if f.IsDeleted() {
// Don't complain if the file is marked as deleted, since it
// can't possibly exist here anyway.
continue
}
files[i].Flags |= FlagInvalid
l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
}
files[i].Name = filepath.FromSlash(f.Name)
}
m.next.Index(deviceID, folder, files)
}
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
fixupFiles(files)
m.next.IndexUpdate(deviceID, folder, files, flags, options)
}
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
name = filepath.FromSlash(name)
return m.next.Request(deviceID, folder, name, offset, size, hash, flags, options)
}
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
m.next.ClusterConfig(deviceID, config)
}
func (m nativeModel) Close(deviceID DeviceID, err error) {
m.next.Close(deviceID, err)
}
func fixupFiles(files []FileInfo) {
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
for i, f := range files {
if strings.ContainsAny(f.Name, disallowedCharacters) {
if f.IsDeleted() {
@@ -60,4 +53,18 @@ func fixupFiles(files []FileInfo) {
}
files[i].Name = filepath.FromSlash(files[i].Name)
}
m.next.IndexUpdate(deviceID, folder, files)
}
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int) ([]byte, error) {
name = filepath.FromSlash(name)
return m.next.Request(deviceID, folder, name, offset, size)
}
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
m.next.ClusterConfig(deviceID, config)
}
func (m nativeModel) Close(deviceID DeviceID, err error) {
m.next.Close(deviceID, err)
}

View File

@@ -35,7 +35,6 @@ const (
stateIdxRcvd
)
// FileInfo flags
const (
FlagDeleted uint32 = 1 << 12
FlagInvalid = 1 << 13
@@ -49,17 +48,6 @@ const (
SymlinkTypeMask = FlagDirectory | FlagSymlinkMissingTarget
)
// IndexMessage message flags (for IndexUpdate)
const (
FlagIndexTemporary uint32 = 1 << iota
)
// Request message flags
const (
FlagRequestTemporary uint32 = 1 << iota
)
// ClusterConfigMessage.Folders.Devices flags
const (
FlagShareTrusted uint32 = 1 << 0
FlagShareReadOnly = 1 << 1
@@ -78,11 +66,11 @@ type pongMessage struct{ EmptyMessage }
type Model interface {
// An index was received from the peer device
Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option)
Index(deviceID DeviceID, folder string, files []FileInfo)
// An index update was received from the peer device
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option)
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo)
// A request was made by the peer device
Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error)
Request(deviceID DeviceID, folder string, name string, offset int64, size int) ([]byte, error)
// A cluster configuration message was received
ClusterConfig(deviceID DeviceID, config ClusterConfigMessage)
// The peer device closed the connection
@@ -92,9 +80,9 @@ type Model interface {
type Connection interface {
ID() DeviceID
Name() string
Index(folder string, files []FileInfo, flags uint32, options []Option) error
IndexUpdate(folder string, files []FileInfo, flags uint32, options []Option) error
Request(folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error)
Index(folder string, files []FileInfo) error
IndexUpdate(folder string, files []FileInfo) error
Request(folder string, name string, offset int64, size int) ([]byte, error)
ClusterConfig(config ClusterConfigMessage)
Statistics() Statistics
}
@@ -181,7 +169,7 @@ func (c *rawConnection) Name() string {
}
// Index writes the list of file information to the connected peer device
func (c *rawConnection) Index(folder string, idx []FileInfo, flags uint32, options []Option) error {
func (c *rawConnection) Index(folder string, idx []FileInfo) error {
select {
case <-c.closed:
return ErrClosed
@@ -189,17 +177,15 @@ func (c *rawConnection) Index(folder string, idx []FileInfo, flags uint32, optio
}
c.idxMut.Lock()
c.send(-1, messageTypeIndex, IndexMessage{
Folder: folder,
Files: idx,
Flags: flags,
Options: options,
Folder: folder,
Files: idx,
})
c.idxMut.Unlock()
return nil
}
// IndexUpdate writes the list of file information to the connected peer device as an update
func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo, flags uint32, options []Option) error {
func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo) error {
select {
case <-c.closed:
return ErrClosed
@@ -207,17 +193,15 @@ func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo, flags uint32,
}
c.idxMut.Lock()
c.send(-1, messageTypeIndexUpdate, IndexMessage{
Folder: folder,
Files: idx,
Flags: flags,
Options: options,
Folder: folder,
Files: idx,
})
c.idxMut.Unlock()
return nil
}
// Request returns the bytes for the specified block after fetching them from the connected peer.
func (c *rawConnection) Request(folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
func (c *rawConnection) Request(folder string, name string, offset int64, size int) ([]byte, error) {
var id int
select {
case id = <-c.nextID:
@@ -234,13 +218,10 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
c.awaitingMut.Unlock()
ok := c.send(id, messageTypeRequest, RequestMessage{
Folder: folder,
Name: name,
Offset: offset,
Size: int32(size),
Hash: hash,
Flags: flags,
Options: options,
Folder: folder,
Name: name,
Offset: offset,
Size: int32(size),
})
if !ok {
return nil, ErrClosed
@@ -299,6 +280,11 @@ func (c *rawConnection) readerLoop() (err error) {
switch msg := msg.(type) {
case IndexMessage:
if msg.Flags != 0 {
// We don't currently support or expect any flags.
return fmt.Errorf("protocol error: unknown flags 0x%x in Index(Update) message", msg.Flags)
}
switch hdr.msgType {
case messageTypeIndex:
if c.state < stateCCRcvd {
@@ -315,6 +301,10 @@ func (c *rawConnection) readerLoop() (err error) {
}
case RequestMessage:
if msg.Flags != 0 {
// We don't currently support or expect any flags.
return fmt.Errorf("protocol error: unknown flags 0x%x in Request message", msg.Flags)
}
if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: request message in state %d", c.state)
}
@@ -470,16 +460,16 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
func (c *rawConnection) handleIndex(im IndexMessage) {
if debug {
l.Debugf("Index(%v, %v, %d file, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options)
l.Debugf("Index(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
}
c.receiver.Index(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options)
c.receiver.Index(c.id, im.Folder, filterIndexMessageFiles(im.Files))
}
func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
if debug {
l.Debugf("queueing IndexUpdate(%v, %v, %d files, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options)
l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
}
c.receiver.IndexUpdate(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options)
c.receiver.IndexUpdate(c.id, im.Folder, filterIndexMessageFiles(im.Files))
}
func filterIndexMessageFiles(fs []FileInfo) []FileInfo {
@@ -509,11 +499,10 @@ func filterIndexMessageFiles(fs []FileInfo) []FileInfo {
}
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
data, err := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), int(req.Size), req.Hash, req.Flags, req.Options)
data, _ := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), int(req.Size))
c.send(msgID, messageTypeResponse, ResponseMessage{
Data: data,
Code: errorToCode(err),
})
}
@@ -521,7 +510,7 @@ func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
c.awaitingMut.Lock()
if rc := c.awaiting[msgID]; rc != nil {
c.awaiting[msgID] = nil
rc <- asyncResult{resp.Data, codeToError(resp.Code)}
rc <- asyncResult{resp.Data, nil}
close(rc)
}
c.awaitingMut.Unlock()

View File

@@ -229,10 +229,10 @@ func TestClose(t *testing.T) {
t.Error("Ping should not return true")
}
c0.Index("default", nil, 0, nil)
c0.Index("default", nil, 0, nil)
c0.Index("default", nil)
c0.Index("default", nil)
if _, err := c0.Request("default", "foo", 0, 0, nil, 0, nil); err == nil {
if _, err := c0.Request("default", "foo", 0, 0); err == nil {
t.Error("Request should return an error")
}
}

View File

@@ -1,105 +0,0 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
// The Vector type represents a version vector. The zero value is a usable
// version vector. The vector has slice semantics and some operations on it
// are "append-like" in that they may return the same vector modified, or a
// new allocated Vector with the modified contents.
type Vector []Counter
// Counter represents a single counter in the version vector.
type Counter struct {
ID uint64
Value uint64
}
// Update returns a Vector with the index for the specific ID incremented by
// one. If it is possible, the vector v is updated and returned. If it is not,
// a copy will be created, updated and returned.
func (v Vector) Update(ID uint64) Vector {
for i := range v {
if v[i].ID == ID {
// Update an existing index
v[i].Value++
return v
} else if v[i].ID > ID {
// Insert a new index
nv := make(Vector, len(v)+1)
copy(nv, v[:i])
nv[i].ID = ID
nv[i].Value = 1
copy(nv[i+1:], v[i:])
return nv
}
}
// Append a new new index
return append(v, Counter{ID, 1})
}
// Merge returns the vector containing the maximum indexes from a and b. If it
// is possible, the vector a is updated and returned. If it is not, a copy
// will be created, updated and returned.
func (a Vector) Merge(b Vector) Vector {
var ai, bi int
for bi < len(b) {
if ai == len(a) {
// We've reach the end of a, all that remains are appends
return append(a, b[bi:]...)
}
if a[ai].ID > b[bi].ID {
// The index from b should be inserted here
n := make(Vector, len(a)+1)
copy(n, a[:ai])
n[ai] = b[bi]
copy(n[ai+1:], a[ai:])
a = n
}
if a[ai].ID == b[bi].ID {
if v := b[bi].Value; v > a[ai].Value {
a[ai].Value = v
}
}
if bi < len(b) && a[ai].ID == b[bi].ID {
bi++
}
ai++
}
return a
}
// Copy returns an identical vector that is not shared with v.
func (v Vector) Copy() Vector {
nv := make(Vector, len(v))
copy(nv, v)
return nv
}
// Equal returns true when the two vectors are equivalent.
func (a Vector) Equal(b Vector) bool {
return a.Compare(b) == Equal
}
// LesserEqual returns true when the two vectors are equivalent or a is Lesser
// than b.
func (a Vector) LesserEqual(b Vector) bool {
comp := a.Compare(b)
return comp == Lesser || comp == Equal
}
// LesserEqual returns true when the two vectors are equivalent or a is Greater
// than b.
func (a Vector) GreaterEqual(b Vector) bool {
comp := a.Compare(b)
return comp == Greater || comp == Equal
}
// Concurrent returns true when the two vectors are concrurrent.
func (a Vector) Concurrent(b Vector) bool {
comp := a.Compare(b)
return comp == ConcurrentGreater || comp == ConcurrentLesser
}

View File

@@ -1,89 +0,0 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
// Ordering represents the relationship between two Vectors.
type Ordering int
const (
Equal Ordering = iota
Greater
Lesser
ConcurrentLesser
ConcurrentGreater
)
// There's really no such thing as "concurrent lesser" and "concurrent
// greater" in version vectors, just "concurrent". But it's useful to be able
// to get a strict ordering between versions for stable sorts and so on, so we
// return both variants. The convenience method Concurrent() can be used to
// check for either case.
// Compare returns the Ordering that describes a's relation to b.
func (a Vector) Compare(b Vector) Ordering {
var ai, bi int // index into a and b
var av, bv Counter // value at current index
result := Equal
for ai < len(a) || bi < len(b) {
var aMissing, bMissing bool
if ai < len(a) {
av = a[ai]
} else {
av = Counter{}
aMissing = true
}
if bi < len(b) {
bv = b[bi]
} else {
bv = Counter{}
bMissing = true
}
switch {
case av.ID == bv.ID:
// We have a counter value for each side
if av.Value > bv.Value {
if result == Lesser {
return ConcurrentLesser
}
result = Greater
} else if av.Value < bv.Value {
if result == Greater {
return ConcurrentGreater
}
result = Lesser
}
case !aMissing && av.ID < bv.ID || bMissing:
// Value is missing on the b side
if av.Value > 0 {
if result == Lesser {
return ConcurrentLesser
}
result = Greater
}
case !bMissing && bv.ID < av.ID || aMissing:
// Value is missing on the a side
if bv.Value > 0 {
if result == Greater {
return ConcurrentGreater
}
result = Lesser
}
}
if ai < len(a) && (av.ID <= bv.ID || bMissing) {
ai++
}
if bi < len(b) && (bv.ID <= av.ID || aMissing) {
bi++
}
}
return result
}

View File

@@ -1,249 +0,0 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
import (
"math"
"testing"
)
func TestCompare(t *testing.T) {
testcases := []struct {
a, b Vector
r Ordering
}{
// Empty vectors are identical
{Vector{}, Vector{}, Equal},
{Vector{}, nil, Equal},
{nil, Vector{}, Equal},
{nil, Vector{Counter{42, 0}}, Equal},
{Vector{}, Vector{Counter{42, 0}}, Equal},
{Vector{Counter{42, 0}}, nil, Equal},
{Vector{Counter{42, 0}}, Vector{}, Equal},
// Zero is the implied value for a missing Counter
{
Vector{Counter{42, 0}},
Vector{Counter{77, 0}},
Equal,
},
// Equal vectors are equal
{
Vector{Counter{42, 33}},
Vector{Counter{42, 33}},
Equal,
},
{
Vector{Counter{42, 33}, Counter{77, 24}},
Vector{Counter{42, 33}, Counter{77, 24}},
Equal,
},
// These a-vectors are all greater than the b-vector
{
Vector{Counter{42, 1}},
nil,
Greater,
},
{
Vector{Counter{42, 1}},
Vector{},
Greater,
},
{
Vector{Counter{0, 1}},
Vector{Counter{0, 0}},
Greater,
},
{
Vector{Counter{42, 1}},
Vector{Counter{42, 0}},
Greater,
},
{
Vector{Counter{math.MaxUint64, 1}},
Vector{Counter{math.MaxUint64, 0}},
Greater,
},
{
Vector{Counter{0, math.MaxUint64}},
Vector{Counter{0, 0}},
Greater,
},
{
Vector{Counter{42, math.MaxUint64}},
Vector{Counter{42, 0}},
Greater,
},
{
Vector{Counter{math.MaxUint64, math.MaxUint64}},
Vector{Counter{math.MaxUint64, 0}},
Greater,
},
{
Vector{Counter{0, math.MaxUint64}},
Vector{Counter{0, math.MaxUint64 - 1}},
Greater,
},
{
Vector{Counter{42, math.MaxUint64}},
Vector{Counter{42, math.MaxUint64 - 1}},
Greater,
},
{
Vector{Counter{math.MaxUint64, math.MaxUint64}},
Vector{Counter{math.MaxUint64, math.MaxUint64 - 1}},
Greater,
},
{
Vector{Counter{42, 2}},
Vector{Counter{42, 1}},
Greater,
},
{
Vector{Counter{22, 22}, Counter{42, 2}},
Vector{Counter{22, 22}, Counter{42, 1}},
Greater,
},
{
Vector{Counter{42, 2}, Counter{77, 3}},
Vector{Counter{42, 1}, Counter{77, 3}},
Greater,
},
{
Vector{Counter{22, 22}, Counter{42, 2}, Counter{77, 3}},
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
Greater,
},
{
Vector{Counter{22, 23}, Counter{42, 2}, Counter{77, 4}},
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
Greater,
},
// These a-vectors are all lesser than the b-vector
{nil, Vector{Counter{42, 1}}, Lesser},
{Vector{}, Vector{Counter{42, 1}}, Lesser},
{
Vector{Counter{42, 0}},
Vector{Counter{42, 1}},
Lesser,
},
{
Vector{Counter{42, 1}},
Vector{Counter{42, 2}},
Lesser,
},
{
Vector{Counter{22, 22}, Counter{42, 1}},
Vector{Counter{22, 22}, Counter{42, 2}},
Lesser,
},
{
Vector{Counter{42, 1}, Counter{77, 3}},
Vector{Counter{42, 2}, Counter{77, 3}},
Lesser,
},
{
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
Vector{Counter{22, 22}, Counter{42, 2}, Counter{77, 3}},
Lesser,
},
{
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
Vector{Counter{22, 23}, Counter{42, 2}, Counter{77, 4}},
Lesser,
},
// These are all in conflict
{
Vector{Counter{42, 2}},
Vector{Counter{43, 1}},
ConcurrentGreater,
},
{
Vector{Counter{43, 1}},
Vector{Counter{42, 2}},
ConcurrentLesser,
},
{
Vector{Counter{22, 23}, Counter{42, 1}},
Vector{Counter{22, 22}, Counter{42, 2}},
ConcurrentGreater,
},
{
Vector{Counter{22, 21}, Counter{42, 2}},
Vector{Counter{22, 22}, Counter{42, 1}},
ConcurrentLesser,
},
{
Vector{Counter{22, 21}, Counter{42, 2}, Counter{43, 1}},
Vector{Counter{20, 1}, Counter{22, 22}, Counter{42, 1}},
ConcurrentLesser,
},
}
for i, tc := range testcases {
// Test real Compare
if r := tc.a.Compare(tc.b); r != tc.r {
t.Errorf("%d: %+v.Compare(%+v) == %v (expected %v)", i, tc.a, tc.b, r, tc.r)
}
// Test convenience functions
switch tc.r {
case Greater:
if tc.a.Equal(tc.b) {
t.Errorf("%+v == %+v", tc.a, tc.b)
}
if tc.a.Concurrent(tc.b) {
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
}
if !tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v not >= %+v", tc.a, tc.b)
}
if tc.a.LesserEqual(tc.b) {
t.Errorf("%+v <= %+v", tc.a, tc.b)
}
case Lesser:
if tc.a.Concurrent(tc.b) {
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
}
if tc.a.Equal(tc.b) {
t.Errorf("%+v == %+v", tc.a, tc.b)
}
if tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v >= %+v", tc.a, tc.b)
}
if !tc.a.LesserEqual(tc.b) {
t.Errorf("%+v not <= %+v", tc.a, tc.b)
}
case Equal:
if tc.a.Concurrent(tc.b) {
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
}
if !tc.a.Equal(tc.b) {
t.Errorf("%+v not == %+v", tc.a, tc.b)
}
if !tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v not <= %+v", tc.a, tc.b)
}
if !tc.a.LesserEqual(tc.b) {
t.Errorf("%+v not <= %+v", tc.a, tc.b)
}
case ConcurrentLesser, ConcurrentGreater:
if !tc.a.Concurrent(tc.b) {
t.Errorf("%+v not concurrent %+v", tc.a, tc.b)
}
if tc.a.Equal(tc.b) {
t.Errorf("%+v == %+v", tc.a, tc.b)
}
if tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v >= %+v", tc.a, tc.b)
}
if tc.a.LesserEqual(tc.b) {
t.Errorf("%+v <= %+v", tc.a, tc.b)
}
}
}
}

View File

@@ -1,122 +0,0 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
import "testing"
func TestUpdate(t *testing.T) {
var v Vector
// Append
v = v.Update(42)
expected := Vector{Counter{42, 1}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Insert at front
v = v.Update(36)
expected = Vector{Counter{36, 1}, Counter{42, 1}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Insert in moddle
v = v.Update(37)
expected = Vector{Counter{36, 1}, Counter{37, 1}, Counter{42, 1}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Update existing
v = v.Update(37)
expected = Vector{Counter{36, 1}, Counter{37, 2}, Counter{42, 1}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
}
func TestCopy(t *testing.T) {
v0 := Vector{Counter{42, 1}}
v1 := v0.Copy()
v1.Update(42)
if v0.Compare(v1) != Lesser {
t.Errorf("Copy error, %+v should be ancestor of %+v", v0, v1)
}
}
func TestMerge(t *testing.T) {
testcases := []struct {
a, b, m Vector
}{
// No-ops
{
Vector{},
Vector{},
Vector{},
},
{
Vector{Counter{22, 1}, Counter{42, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
},
// Appends
{
Vector{},
Vector{Counter{22, 1}, Counter{42, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
},
{
Vector{Counter{22, 1}},
Vector{Counter{42, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
},
{
Vector{Counter{22, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
},
// Insert
{
Vector{Counter{22, 1}, Counter{42, 1}},
Vector{Counter{22, 1}, Counter{23, 2}, Counter{42, 1}},
Vector{Counter{22, 1}, Counter{23, 2}, Counter{42, 1}},
},
{
Vector{Counter{42, 1}},
Vector{Counter{22, 1}},
Vector{Counter{22, 1}, Counter{42, 1}},
},
// Update
{
Vector{Counter{22, 1}, Counter{42, 2}},
Vector{Counter{22, 2}, Counter{42, 1}},
Vector{Counter{22, 2}, Counter{42, 2}},
},
// All of the above
{
Vector{Counter{10, 1}, Counter{20, 2}, Counter{30, 1}},
Vector{Counter{5, 1}, Counter{10, 2}, Counter{15, 1}, Counter{20, 1}, Counter{25, 1}, Counter{35, 1}},
Vector{Counter{5, 1}, Counter{10, 2}, Counter{15, 1}, Counter{20, 2}, Counter{25, 1}, Counter{30, 1}, Counter{35, 1}},
},
}
for i, tc := range testcases {
if m := tc.a.Merge(tc.b); m.Compare(tc.m) != Equal {
t.Errorf("%d: %+v.Merge(%+v) == %+v (expected %+v)", i, tc.a, tc.b, m, tc.m)
}
}
}

View File

@@ -1,38 +0,0 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
// This stuff is hacked up manually because genxdr doesn't support 'type
// Vector []Counter' declarations and it was tricky when I tried to add it...
type xdrWriter interface {
WriteUint32(uint32) (int, error)
WriteUint64(uint64) (int, error)
}
type xdrReader interface {
ReadUint32() uint32
ReadUint64() uint64
}
// EncodeXDRInto encodes the vector as an XDR object into the given XDR
// encoder.
func (v Vector) EncodeXDRInto(w xdrWriter) (int, error) {
w.WriteUint32(uint32(len(v)))
for i := range v {
w.WriteUint64(v[i].ID)
w.WriteUint64(v[i].Value)
}
return 4 + 16*len(v), nil
}
// DecodeXDRFrom decodes the XDR objects from the given reader into itself.
func (v *Vector) DecodeXDRFrom(r xdrReader) error {
l := int(r.ReadUint32())
n := make(Vector, l)
for i := range n {
n[i].ID = r.ReadUint64()
n[i].Value = r.ReadUint64()
}
*v = n
return nil
}

View File

@@ -20,7 +20,7 @@ func (c wireFormatConnection) Name() string {
return c.next.Name()
}
func (c wireFormatConnection) Index(folder string, fs []FileInfo, flags uint32, options []Option) error {
func (c wireFormatConnection) Index(folder string, fs []FileInfo) error {
var myFs = make([]FileInfo, len(fs))
copy(myFs, fs)
@@ -28,10 +28,10 @@ func (c wireFormatConnection) Index(folder string, fs []FileInfo, flags uint32,
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
}
return c.next.Index(folder, myFs, flags, options)
return c.next.Index(folder, myFs)
}
func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo, flags uint32, options []Option) error {
func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo) error {
var myFs = make([]FileInfo, len(fs))
copy(myFs, fs)
@@ -39,12 +39,12 @@ func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo, flags ui
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
}
return c.next.IndexUpdate(folder, myFs, flags, options)
return c.next.IndexUpdate(folder, myFs)
}
func (c wireFormatConnection) Request(folder, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
func (c wireFormatConnection) Request(folder, name string, offset int64, size int) ([]byte, error) {
name = norm.NFC.String(filepath.ToSlash(name))
return c.next.Request(folder, name, offset, size, hash, flags, options)
return c.next.Request(folder, name, offset, size)
}
func (c wireFormatConnection) ClusterConfig(config ClusterConfigMessage) {

View File

@@ -153,7 +153,7 @@ type Options struct {
BlockCacher Cacher
// BlockCacheCapacity defines the capacity of the 'sorted table' block caching.
// Use -1 for zero, this has same effect as specifying NoCacher to BlockCacher.
// Use -1 for zero, this has same effect with specifying NoCacher to BlockCacher.
//
// The default value is 8MiB.
BlockCacheCapacity int
@@ -308,7 +308,7 @@ type Options struct {
OpenFilesCacher Cacher
// OpenFilesCacheCapacity defines the capacity of the open files caching.
// Use -1 for zero, this has same effect as specifying NoCacher to OpenFilesCacher.
// Use -1 for zero, this has same effect with specifying NoCacher to OpenFilesCacher.
//
// The default value is 500.
OpenFilesCacheCapacity int
@@ -355,9 +355,9 @@ func (o *Options) GetBlockCacher() Cacher {
}
func (o *Options) GetBlockCacheCapacity() int {
if o == nil || o.BlockCacheCapacity == 0 {
if o == nil || o.BlockCacheCapacity <= 0 {
return DefaultBlockCacheCapacity
} else if o.BlockCacheCapacity < 0 {
} else if o.BlockCacheCapacity == -1 {
return 0
}
return o.BlockCacheCapacity
@@ -497,7 +497,7 @@ func (o *Options) GetMaxMemCompationLevel() int {
if o != nil {
if o.MaxMemCompationLevel > 0 {
level = o.MaxMemCompationLevel
} else if o.MaxMemCompationLevel < 0 {
} else if o.MaxMemCompationLevel == -1 {
level = 0
}
}
@@ -525,9 +525,9 @@ func (o *Options) GetOpenFilesCacher() Cacher {
}
func (o *Options) GetOpenFilesCacheCapacity() int {
if o == nil || o.OpenFilesCacheCapacity == 0 {
if o == nil || o.OpenFilesCacheCapacity <= 0 {
return DefaultOpenFilesCacheCapacity
} else if o.OpenFilesCacheCapacity < 0 {
} else if o.OpenFilesCacheCapacity == -1 {
return 0
}
return o.OpenFilesCacheCapacity

View File

@@ -7,15 +7,10 @@ package snappy
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrCorrupt reports that the input is invalid.
ErrCorrupt = errors.New("snappy: corrupt input")
// ErrUnsupported reports that the input isn't supported.
ErrUnsupported = errors.New("snappy: unsupported input")
)
// ErrCorrupt reports that the input is invalid.
var ErrCorrupt = errors.New("snappy: corrupt input")
// DecodedLen returns the length of the decoded block.
func DecodedLen(src []byte) (int, error) {
@@ -127,166 +122,3 @@ func Decode(dst, src []byte) ([]byte, error) {
}
return dst[:d], nil
}
// NewReader returns a new Reader that decompresses from r, using the framing
// format described at
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
func NewReader(r io.Reader) *Reader {
return &Reader{
r: r,
decoded: make([]byte, maxUncompressedChunkLen),
buf: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)+checksumSize),
}
}
// Reader is an io.Reader than can read Snappy-compressed bytes.
type Reader struct {
r io.Reader
err error
decoded []byte
buf []byte
// decoded[i:j] contains decoded bytes that have not yet been passed on.
i, j int
readHeader bool
}
// Reset discards any buffered data, resets all state, and switches the Snappy
// reader to read from r. This permits reusing a Reader rather than allocating
// a new one.
func (r *Reader) Reset(reader io.Reader) {
r.r = reader
r.err = nil
r.i = 0
r.j = 0
r.readHeader = false
}
func (r *Reader) readFull(p []byte) (ok bool) {
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
if r.err == io.ErrUnexpectedEOF {
r.err = ErrCorrupt
}
return false
}
return true
}
// Read satisfies the io.Reader interface.
func (r *Reader) Read(p []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
for {
if r.i < r.j {
n := copy(p, r.decoded[r.i:r.j])
r.i += n
return n, nil
}
if !r.readFull(r.buf[:4]) {
return 0, r.err
}
chunkType := r.buf[0]
if !r.readHeader {
if chunkType != chunkTypeStreamIdentifier {
r.err = ErrCorrupt
return 0, r.err
}
r.readHeader = true
}
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
if chunkLen > len(r.buf) {
r.err = ErrUnsupported
return 0, r.err
}
// The chunk types are specified at
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
switch chunkType {
case chunkTypeCompressedData:
// Section 4.2. Compressed data (chunk type 0x00).
if chunkLen < checksumSize {
r.err = ErrCorrupt
return 0, r.err
}
buf := r.buf[:chunkLen]
if !r.readFull(buf) {
return 0, r.err
}
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
buf = buf[checksumSize:]
n, err := DecodedLen(buf)
if err != nil {
r.err = err
return 0, r.err
}
if n > len(r.decoded) {
r.err = ErrCorrupt
return 0, r.err
}
if _, err := Decode(r.decoded, buf); err != nil {
r.err = err
return 0, r.err
}
if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt
return 0, r.err
}
r.i, r.j = 0, n
continue
case chunkTypeUncompressedData:
// Section 4.3. Uncompressed data (chunk type 0x01).
if chunkLen < checksumSize {
r.err = ErrCorrupt
return 0, r.err
}
buf := r.buf[:checksumSize]
if !r.readFull(buf) {
return 0, r.err
}
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
// Read directly into r.decoded instead of via r.buf.
n := chunkLen - checksumSize
if !r.readFull(r.decoded[:n]) {
return 0, r.err
}
if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt
return 0, r.err
}
r.i, r.j = 0, n
continue
case chunkTypeStreamIdentifier:
// Section 4.1. Stream identifier (chunk type 0xff).
if chunkLen != len(magicBody) {
r.err = ErrCorrupt
return 0, r.err
}
if !r.readFull(r.buf[:len(magicBody)]) {
return 0, r.err
}
for i := 0; i < len(magicBody); i++ {
if r.buf[i] != magicBody[i] {
r.err = ErrCorrupt
return 0, r.err
}
}
continue
}
if chunkType <= 0x7f {
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
r.err = ErrUnsupported
return 0, r.err
} else {
// Section 4.4 Padding (chunk type 0xfe).
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
if !r.readFull(r.buf[:chunkLen]) {
return 0, r.err
}
}
}
}

View File

@@ -6,7 +6,6 @@ package snappy
import (
"encoding/binary"
"io"
)
// We limit how far copy back-references can go, the same as the C++ code.
@@ -173,86 +172,3 @@ func MaxEncodedLen(srcLen int) int {
// This last factor dominates the blowup, so the final estimate is:
return 32 + srcLen + srcLen/6
}
// NewWriter returns a new Writer that compresses to w, using the framing
// format described at
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
func NewWriter(w io.Writer) *Writer {
return &Writer{
w: w,
enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)),
}
}
// Writer is an io.Writer than can write Snappy-compressed bytes.
type Writer struct {
w io.Writer
err error
enc []byte
buf [checksumSize + chunkHeaderSize]byte
wroteHeader bool
}
// Reset discards the writer's state and switches the Snappy writer to write to
// w. This permits reusing a Writer rather than allocating a new one.
func (w *Writer) Reset(writer io.Writer) {
w.w = writer
w.err = nil
w.wroteHeader = false
}
// Write satisfies the io.Writer interface.
func (w *Writer) Write(p []byte) (n int, errRet error) {
if w.err != nil {
return 0, w.err
}
if !w.wroteHeader {
copy(w.enc, magicChunk)
if _, err := w.w.Write(w.enc[:len(magicChunk)]); err != nil {
w.err = err
return n, err
}
w.wroteHeader = true
}
for len(p) > 0 {
var uncompressed []byte
if len(p) > maxUncompressedChunkLen {
uncompressed, p = p[:maxUncompressedChunkLen], p[maxUncompressedChunkLen:]
} else {
uncompressed, p = p, nil
}
checksum := crc(uncompressed)
// Compress the buffer, discarding the result if the improvement
// isn't at least 12.5%.
chunkType := uint8(chunkTypeCompressedData)
chunkBody, err := Encode(w.enc, uncompressed)
if err != nil {
w.err = err
return n, err
}
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 {
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed
}
chunkLen := 4 + len(chunkBody)
w.buf[0] = chunkType
w.buf[1] = uint8(chunkLen >> 0)
w.buf[2] = uint8(chunkLen >> 8)
w.buf[3] = uint8(chunkLen >> 16)
w.buf[4] = uint8(checksum >> 0)
w.buf[5] = uint8(checksum >> 8)
w.buf[6] = uint8(checksum >> 16)
w.buf[7] = uint8(checksum >> 24)
if _, err = w.w.Write(w.buf[:]); err != nil {
w.err = err
return n, err
}
if _, err = w.w.Write(chunkBody); err != nil {
w.err = err
return n, err
}
n += len(uncompressed)
}
return n, nil
}

View File

@@ -8,10 +8,6 @@
// The C++ snappy implementation is at http://code.google.com/p/snappy/
package snappy
import (
"hash/crc32"
)
/*
Each encoded block begins with the varint-encoded length of the decoded data,
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
@@ -40,29 +36,3 @@ const (
tagCopy2 = 0x02
tagCopy4 = 0x03
)
const (
checksumSize = 4
chunkHeaderSize = 4
magicChunk = "\xff\x06\x00\x00" + magicBody
magicBody = "sNaPpY"
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt says
// that "the uncompressed data in a chunk must be no longer than 65536 bytes".
maxUncompressedChunkLen = 65536
)
const (
chunkTypeCompressedData = 0x00
chunkTypeUncompressedData = 0x01
chunkTypePadding = 0xfe
chunkTypeStreamIdentifier = 0xff
)
var crcTable = crc32.MakeTable(crc32.Castagnoli)
// crc implements the checksum specified in section 3 of
// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
func crc(b []byte) uint32 {
c := crc32.Update(0, crcTable, b)
return uint32(c>>15|c<<17) + 0xa282ead8
}

View File

@@ -18,10 +18,7 @@ import (
"testing"
)
var (
download = flag.Bool("download", false, "If true, download any missing files before running benchmarks")
testdata = flag.String("testdata", "testdata", "Directory containing the test data")
)
var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks")
func roundtrip(b, ebuf, dbuf []byte) error {
e, err := Encode(ebuf, b)
@@ -58,11 +55,11 @@ func TestSmallCopy(t *testing.T) {
}
func TestSmallRand(t *testing.T) {
rng := rand.New(rand.NewSource(27354294))
rand.Seed(27354294)
for n := 1; n < 20000; n += 23 {
b := make([]byte, n)
for i := range b {
b[i] = uint8(rng.Uint32())
for i, _ := range b {
b[i] = uint8(rand.Uint32())
}
if err := roundtrip(b, nil, nil); err != nil {
t.Fatal(err)
@@ -73,7 +70,7 @@ func TestSmallRand(t *testing.T) {
func TestSmallRegular(t *testing.T) {
for n := 1; n < 20000; n += 23 {
b := make([]byte, n)
for i := range b {
for i, _ := range b {
b[i] = uint8(i%10 + 'a')
}
if err := roundtrip(b, nil, nil); err != nil {
@@ -82,120 +79,6 @@ func TestSmallRegular(t *testing.T) {
}
}
func cmp(a, b []byte) error {
if len(a) != len(b) {
return fmt.Errorf("got %d bytes, want %d", len(a), len(b))
}
for i := range a {
if a[i] != b[i] {
return fmt.Errorf("byte #%d: got 0x%02x, want 0x%02x", i, a[i], b[i])
}
}
return nil
}
func TestFramingFormat(t *testing.T) {
// src is comprised of alternating 1e5-sized sequences of random
// (incompressible) bytes and repeated (compressible) bytes. 1e5 was chosen
// because it is larger than maxUncompressedChunkLen (64k).
src := make([]byte, 1e6)
rng := rand.New(rand.NewSource(1))
for i := 0; i < 10; i++ {
if i%2 == 0 {
for j := 0; j < 1e5; j++ {
src[1e5*i+j] = uint8(rng.Intn(256))
}
} else {
for j := 0; j < 1e5; j++ {
src[1e5*i+j] = uint8(i)
}
}
}
buf := new(bytes.Buffer)
if _, err := NewWriter(buf).Write(src); err != nil {
t.Fatalf("Write: encoding: %v", err)
}
dst, err := ioutil.ReadAll(NewReader(buf))
if err != nil {
t.Fatalf("ReadAll: decoding: %v", err)
}
if err := cmp(dst, src); err != nil {
t.Fatal(err)
}
}
func TestReaderReset(t *testing.T) {
gold := bytes.Repeat([]byte("All that is gold does not glitter,\n"), 10000)
buf := new(bytes.Buffer)
if _, err := NewWriter(buf).Write(gold); err != nil {
t.Fatalf("Write: %v", err)
}
encoded, invalid, partial := buf.String(), "invalid", "partial"
r := NewReader(nil)
for i, s := range []string{encoded, invalid, partial, encoded, partial, invalid, encoded, encoded} {
if s == partial {
r.Reset(strings.NewReader(encoded))
if _, err := r.Read(make([]byte, 101)); err != nil {
t.Errorf("#%d: %v", i, err)
continue
}
continue
}
r.Reset(strings.NewReader(s))
got, err := ioutil.ReadAll(r)
switch s {
case encoded:
if err != nil {
t.Errorf("#%d: %v", i, err)
continue
}
if err := cmp(got, gold); err != nil {
t.Errorf("#%d: %v", i, err)
continue
}
case invalid:
if err == nil {
t.Errorf("#%d: got nil error, want non-nil", i)
continue
}
}
}
}
func TestWriterReset(t *testing.T) {
gold := bytes.Repeat([]byte("Not all those who wander are lost;\n"), 10000)
var gots, wants [][]byte
const n = 20
w, failed := NewWriter(nil), false
for i := 0; i <= n; i++ {
buf := new(bytes.Buffer)
w.Reset(buf)
want := gold[:len(gold)*i/n]
if _, err := w.Write(want); err != nil {
t.Errorf("#%d: Write: %v", i, err)
failed = true
continue
}
got, err := ioutil.ReadAll(NewReader(buf))
if err != nil {
t.Errorf("#%d: ReadAll: %v", i, err)
failed = true
continue
}
gots = append(gots, got)
wants = append(wants, want)
}
if failed {
return
}
for i := range gots {
if err := cmp(gots[i], wants[i]); err != nil {
t.Errorf("#%d: %v", i, err)
}
}
}
func benchDecode(b *testing.B, src []byte) {
encoded, err := Encode(nil, src)
if err != nil {
@@ -219,7 +102,7 @@ func benchEncode(b *testing.B, src []byte) {
}
}
func readFile(b testing.TB, filename string) []byte {
func readFile(b *testing.B, filename string) []byte {
src, err := ioutil.ReadFile(filename)
if err != nil {
b.Fatalf("failed reading %s: %s", filename, err)
@@ -261,7 +144,7 @@ func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) }
func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) }
// testFiles' values are copied directly from
// https://raw.githubusercontent.com/google/snappy/master/snappy_unittest.cc
// https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc.
// The label field is unused in snappy-go.
var testFiles = []struct {
label string
@@ -269,36 +152,29 @@ var testFiles = []struct {
}{
{"html", "html"},
{"urls", "urls.10K"},
{"jpg", "fireworks.jpeg"},
{"jpg_200", "fireworks.jpeg"},
{"pdf", "paper-100k.pdf"},
{"jpg", "house.jpg"},
{"pdf", "mapreduce-osdi-1.pdf"},
{"html4", "html_x_4"},
{"cp", "cp.html"},
{"c", "fields.c"},
{"lsp", "grammar.lsp"},
{"xls", "kennedy.xls"},
{"txt1", "alice29.txt"},
{"txt2", "asyoulik.txt"},
{"txt3", "lcet10.txt"},
{"txt4", "plrabn12.txt"},
{"bin", "ptt5"},
{"sum", "sum"},
{"man", "xargs.1"},
{"pb", "geo.protodata"},
{"gaviota", "kppkn.gtb"},
}
// The test data files are present at this canonical URL.
const baseURL = "https://raw.githubusercontent.com/google/snappy/master/testdata/"
const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/"
func downloadTestdata(basename string) (errRet error) {
filename := filepath.Join(*testdata, basename)
if stat, err := os.Stat(filename); err == nil && stat.Size() != 0 {
return nil
}
if !*download {
return fmt.Errorf("test data not found; skipping benchmark without the -download flag")
}
// Download the official snappy C++ implementation reference test data
// files for benchmarking.
if err := os.Mkdir(*testdata, 0777); err != nil && !os.IsExist(err) {
return fmt.Errorf("failed to create testdata: %s", err)
}
filename := filepath.Join("testdata", basename)
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create %s: %s", filename, err)
@@ -309,27 +185,36 @@ func downloadTestdata(basename string) (errRet error) {
os.Remove(filename)
}
}()
url := baseURL + basename
resp, err := http.Get(url)
resp, err := http.Get(baseURL + basename)
if err != nil {
return fmt.Errorf("failed to download %s: %s", url, err)
return fmt.Errorf("failed to download %s: %s", baseURL+basename, err)
}
defer resp.Body.Close()
if s := resp.StatusCode; s != http.StatusOK {
return fmt.Errorf("downloading %s: HTTP status code %d (%s)", url, s, http.StatusText(s))
}
_, err = io.Copy(f, resp.Body)
if err != nil {
return fmt.Errorf("failed to download %s to %s: %s", url, filename, err)
return fmt.Errorf("failed to write %s: %s", filename, err)
}
return nil
}
func benchFile(b *testing.B, n int, decode bool) {
if err := downloadTestdata(testFiles[n].filename); err != nil {
b.Fatalf("failed to download testdata: %s", err)
filename := filepath.Join("testdata", testFiles[n].filename)
if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 {
if !*download {
b.Fatal("test data not found; skipping benchmark without the -download flag")
}
// Download the official snappy C++ implementation reference test data
// files for benchmarking.
if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) {
b.Fatalf("failed to create testdata: %s", err)
}
for _, tf := range testFiles {
if err := downloadTestdata(tf.filename); err != nil {
b.Fatalf("failed to download testdata: %s", err)
}
}
}
data := readFile(b, filepath.Join(*testdata, testFiles[n].filename))
data := readFile(b, filename)
if decode {
benchDecode(b, data)
} else {
@@ -350,6 +235,12 @@ func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) }
func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) }
func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) }
func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) }
func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) }
func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) }
func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) }
func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) }
func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) }
func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) }
func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) }
func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) }
func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) }
@@ -362,3 +253,9 @@ func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) }
func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) }
func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) }
func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) }
func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) }
func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) }
func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) }
func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) }
func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) }
func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) }

View File

@@ -1,7 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.4
- tip

View File

@@ -1,19 +0,0 @@
Copyright (c) 2014 Barracuda Networks, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,45 +0,0 @@
Suture
======
[![Build Status](https://travis-ci.org/thejerf/suture.png?branch=master)](https://travis-ci.org/thejerf/suture)
Suture provides Erlang-ish supervisor trees for Go. "Supervisor trees" ->
"sutree" -> "suture" -> holds your code together when it's trying to die.
This is intended to be a production-quality library going into code that I
will be very early on the phone tree to support when it goes down. However,
it has not been deployed into something quite that serious yet. (I will
update this statement when that changes.)
It is intended to deal gracefully with the real failure cases that can
occur with supervision trees (such as burning all your CPU time endlessly
restarting dead services), while also making no unnecessary demands on the
"service" code, and providing hooks to perform adequate logging with in a
production environment.
[A blog post describing the design decisions](http://www.jerf.org/iri/post/2930)
is available.
This module is fully covered with [godoc](http://godoc.org/github.com/thejerf/suture),
including an example, usage, and everything else you might expect from a
README.md on GitHub. (DRY.)
This is not currently tagged with particular git tags for Go as this is
currently considered to be alpha code. As I move this into production and
feel more confident about it, I'll give it relevant tags.
Code Signing
------------
Starting with the commit after ac7cf8591b, I will be signing this repository
with the ["jerf" keybase account](https://keybase.io/jerf).
Aspiration
----------
One of the big wins the Erlang community has with their pervasive OTP
support is that it makes it easy for them to distribute libraries that
easily fit into the OTP paradigm. It ought to someday be considered a good
idea to distribute libraries that provide some sort of supervisor tree
functionality out of the box. It is possible to provide this functionality
without explicitly depending on the Suture library.

View File

@@ -1,11 +0,0 @@
#!/bin/bash
GOLINTOUT=$(golint *go)
if [ ! -z "$GOLINTOUT" -o "$?" != 0 ]; then
echo golint failed:
echo $GOLINTOUT
exit 1
fi
go test

View File

@@ -1,650 +0,0 @@
/*
Package suture provides Erlang-like supervisor trees.
This implements Erlang-esque supervisor trees, as adapted for Go. This is
intended to be an industrial-strength implementation, but it has not yet
been deployed in a hostile environment. (It's headed there, though.)
Supervisor Tree -> SuTree -> suture -> holds your code together when it's
trying to fall apart.
Why use Suture?
* You want to write bullet-resistant services that will remain available
despite unforeseen failure.
* You need the code to be smart enough not to consume 100% of the CPU
restarting things.
* You want to easily compose multiple such services in one program.
* You want the Erlang programmers to stop lording their supervision
trees over you.
Suture has 100% test coverage, and is golint clean. This doesn't prove it
free of bugs, but it shows I care.
A blog post describing the design decisions is available at
http://www.jerf.org/iri/post/2930 .
Using Suture
To idiomatically use Suture, create a Supervisor which is your top level
"application" supervisor. This will often occur in your program's "main"
function.
Create "Service"s, which implement the Service interface. .Add() them
to your Supervisor. Supervisors are also services, so you can create a
tree structure here, depending on the exact combination of restarts
you want to create.
Finally, as what is probably the last line of your main() function, call
.Serve() on your top level supervisor. This will start all the services
you've defined.
See the Example for an example, using a simple service that serves out
incrementing integers.
*/
package suture
import (
"errors"
"fmt"
"log"
"math"
"runtime"
"sync/atomic"
"time"
)
const (
notRunning = iota
normal
paused
)
type supervisorID uint32
type serviceID uint32
var currentSupervisorID uint32
// ErrWrongSupervisor is returned by the (*Supervisor).Remove method
// if you pass a ServiceToken from the wrong Supervisor.
var ErrWrongSupervisor = errors.New("wrong supervisor for this service token, no service removed")
// ServiceToken is an opaque identifier that can be used to terminate a service that
// has been Add()ed to a Supervisor.
type ServiceToken struct {
id uint64
}
/*
Supervisor is the core type of the module that represents a Supervisor.
Supervisors should be constructed either by New or NewSimple.
Once constructed, a Supervisor should be started in one of three ways:
1. Calling .Serve().
2. Calling .ServeBackground().
3. Adding it to an existing Supervisor.
Calling Serve will cause the supervisor to run until it is shut down by
an external user calling Stop() on it. If that never happens, it simply
runs forever. I suggest creating your services in Supervisors, then making
a Serve() call on your top-level Supervisor be the last line of your main
func.
Calling ServeBackground will CORRECTLY start the supervisor running in a
new goroutine. You do not want to just:
go supervisor.Serve()
because that will briefly create a race condition as it starts up, if you
try to .Add() services immediately afterward.
*/
type Supervisor struct {
Name string
id supervisorID
failureDecay float64
failureThreshold float64
failureBackoff time.Duration
timeout time.Duration
log func(string)
services map[serviceID]Service
lastFail time.Time
failures float64
restartQueue []serviceID
state uint8
serviceCounter serviceID
control chan supervisorMessage
resumeTimer <-chan time.Time
// The testing uses the ability to grab these individual logging functions
// and get inside of suture's handling at a deep level.
// If you ever come up with some need to get into these, submit a pull
// request to make them public and some smidge of justification, and
// I'll happily do it.
logBadStop func(Service)
logFailure func(service Service, currentFailures float64, failureThreshold float64, restarting bool, error interface{}, stacktrace []byte)
logBackoff func(*Supervisor, bool)
// avoid a dependency on github.com/thejerf/abtime by just implementing
// a minimal chunk.
getNow func() time.Time
getResume func(time.Duration) <-chan time.Time
}
// Spec is used to pass arguments to the New function to create a
// supervisor. See the New function for full documentation.
type Spec struct {
Log func(string)
FailureDecay float64
FailureThreshold float64
FailureBackoff time.Duration
Timeout time.Duration
}
/*
New is the full constructor function for a supervisor.
The name is a friendly human name for the supervisor, used in logging. Suture
does not care if this is unique, but it is good for your sanity if it is.
If not set, the following values are used:
* Log: A function is created that uses log.Print.
* FailureDecay: 30 seconds
* FailureThreshold: 5 failures
* FailureBackoff: 15 seconds
* Timeout: 10 seconds
The Log function will be called when errors occur. Suture will log the
following:
* When a service has failed, with a descriptive message about the
current backoff status, and whether it was immediately restarted
* When the supervisor has gone into its backoff mode, and when it
exits it
* When a service fails to stop
The failureRate, failureThreshold, and failureBackoff controls how failures
are handled, in order to avoid the supervisor failure case where the
program does nothing but restarting failed services. If you do not
care how failures behave, the default values should be fine for the
vast majority of services, but if you want the details:
The supervisor tracks the number of failures that have occurred, with an
exponential decay on the count. Every FailureDecay seconds, the number of
failures that have occurred is cut in half. (This is done smoothly with an
exponential function.) When a failure occurs, the number of failures
is incremented by one. When the number of failures passes the
FailureThreshold, the entire service waits for FailureBackoff seconds
before attempting any further restarts, at which point it resets its
failure count to zero.
Timeout is how long Suture will wait for a service to properly terminate.
*/
func New(name string, spec Spec) (s *Supervisor) {
s = new(Supervisor)
s.Name = name
s.id = supervisorID(atomic.AddUint32(&currentSupervisorID, 1))
if spec.Log == nil {
s.log = func(msg string) {
log.Print(fmt.Sprintf("Supervisor %s: %s", s.Name, msg))
}
} else {
s.log = spec.Log
}
if spec.FailureDecay == 0 {
s.failureDecay = 30
} else {
s.failureDecay = spec.FailureDecay
}
if spec.FailureThreshold == 0 {
s.failureThreshold = 5
} else {
s.failureThreshold = spec.FailureThreshold
}
if spec.FailureBackoff == 0 {
s.failureBackoff = time.Second * 15
} else {
s.failureBackoff = spec.FailureBackoff
}
if spec.Timeout == 0 {
s.timeout = time.Second * 10
} else {
s.timeout = spec.Timeout
}
// overriding these allows for testing the threshold behavior
s.getNow = time.Now
s.getResume = time.After
s.control = make(chan supervisorMessage)
s.services = make(map[serviceID]Service)
s.restartQueue = make([]serviceID, 0, 1)
s.resumeTimer = make(chan time.Time)
// set up the default logging handlers
s.logBadStop = func(service Service) {
s.log(fmt.Sprintf("Service %s failed to terminate in a timely manner", serviceName(service)))
}
s.logFailure = func(service Service, failures float64, threshold float64, restarting bool, err interface{}, st []byte) {
var errString string
e, canError := err.(error)
if canError {
errString = e.Error()
} else {
errString = fmt.Sprintf("%#v", err)
}
s.log(fmt.Sprintf("Failed service '%s' (%f failures of %f), restarting: %#v, error: %s, stacktrace: %s", serviceName(service), failures, threshold, restarting, errString, string(st)))
}
s.logBackoff = func(s *Supervisor, entering bool) {
if entering {
s.log("Entering the backoff state.")
} else {
s.log("Exiting backoff state.")
}
}
return
}
func serviceName(service Service) (serviceName string) {
stringer, canStringer := service.(fmt.Stringer)
if canStringer {
serviceName = stringer.String()
} else {
serviceName = fmt.Sprintf("%#v", service)
}
return
}
// NewSimple is a convenience function to create a service with just a name
// and the sensible defaults.
func NewSimple(name string) *Supervisor {
return New(name, Spec{})
}
/*
Service is the interface that describes a service to a Supervisor.
Serve Method
The Serve method is called by a Supervisor to start the service.
The service should execute within the goroutine that this is
called in. If this function either returns or panics, the Supervisor
will call it again.
A Serve method SHOULD do as much cleanup of the state as possible,
to prevent any corruption in the previous state from crashing the
service again.
Stop Method
This method is used by the supervisor to stop the service. Calling this
directly on a Service given to a Supervisor will simply result in the
Service being restarted; use the Supervisor's .Remove(ServiceToken) method
to stop a service. A supervisor will call .Stop() only once. Thus, it may
be as destructive as it likes to get the service to stop.
Once Stop has been called on a Service, the Service SHOULD NOT be
reused in any other supervisor! Because of the impossibility of
guaranteeing that the service has actually stopped in Go, you can't
prove that you won't be starting two goroutines using the exact
same memory to store state, causing completely unpredictable behavior.
Stop should not return until the service has actually stopped.
"Stopped" here is defined as "the service will stop servicing any
further requests in the future". For instance, a common implementation
is to receive a message on a dedicated "stop" channel and immediately
returning. Once the stop command has been processed, the service is
stopped.
Another common Stop implementation is to forcibly close an open socket
or other resource, which will cause detectable errors to manifest in the
service code. Bear in mind that to perfectly correctly use this
approach requires a bit more work to handle the chance of a Stop
command coming in before the resource has been created.
If a service does not Stop within the supervisor's timeout duration, a log
entry will be made with a descriptive string to that effect. This does
not guarantee that the service is hung; it may still get around to being
properly stopped in the future. Until the service is fully stopped,
both the service and the spawned goroutine trying to stop it will be
"leaked".
Stringer Interface
It is not mandatory to implement the fmt.Stringer interface on your
service, but if your Service does happen to implement that, the log
messages that describe your service will use that when naming the
service. Otherwise, you'll see the GoString of your service object,
obtained via fmt.Sprintf("%#v", service).
*/
type Service interface {
Serve()
Stop()
}
/*
Add adds a service to this supervisor.
If the supervisor is currently running, the service will be started
immediately. If the supervisor is not currently running, the service
will be started when the supervisor is.
The returned ServiceID may be passed to the Remove method of the Supervisor
to terminate the service.
*/
func (s *Supervisor) Add(service Service) ServiceToken {
if s == nil {
panic("can't add service to nil *suture.Supervisor")
}
if s.state == notRunning {
id := s.serviceCounter
s.serviceCounter++
s.services[id] = service
s.restartQueue = append(s.restartQueue, id)
return ServiceToken{uint64(s.id)<<32 | uint64(id)}
}
response := make(chan serviceID)
s.control <- addService{service, response}
return ServiceToken{uint64(s.id)<<32 | uint64(<-response)}
}
// ServeBackground starts running a supervisor in its own goroutine. This
// method does not return until it is safe to use .Add() on the Supervisor.
func (s *Supervisor) ServeBackground() {
go s.Serve()
s.sync()
}
/*
Serve starts the supervisor. You should call this on the top-level supervisor,
but nothing else.
*/
func (s *Supervisor) Serve() {
if s == nil {
panic("Can't serve with a nil *suture.Supervisor")
}
if s.id == 0 {
panic("Can't call Serve on an incorrectly-constructed *suture.Supervisor")
}
defer func() {
s.state = notRunning
}()
if s.state != notRunning {
// FIXME: Don't explain why I don't need a semaphore, just use one
// This doesn't use a semaphore because it's just a sanity check.
panic("Running a supervisor while it is already running?")
}
s.state = normal
// for all the services I currently know about, start them
for _, id := range s.restartQueue {
service, present := s.services[id]
if present {
s.runService(service, id)
}
}
s.restartQueue = make([]serviceID, 0, 1)
for {
select {
case m := <-s.control:
switch msg := m.(type) {
case serviceFailed:
s.handleFailedService(msg.id, msg.err, msg.stacktrace)
case serviceEnded:
service, monitored := s.services[msg.id]
if monitored {
s.handleFailedService(msg.id, fmt.Sprintf("%s returned unexpectedly", service), []byte("[unknown stack trace]"))
}
case addService:
id := s.serviceCounter
s.serviceCounter++
s.services[id] = msg.service
s.runService(msg.service, id)
msg.response <- id
case removeService:
s.removeService(msg.id)
case stopSupervisor:
for id := range s.services {
s.removeService(id)
}
return
case listServices:
services := []Service{}
for _, service := range s.services {
services = append(services, service)
}
msg.c <- services
case syncSupervisor:
// this does nothing on purpose; its sole purpose is to
// introduce a sync point via the channel receive
case panicSupervisor:
// used only by tests
panic("Panicking as requested!")
}
case _ = <-s.resumeTimer:
// We're resuming normal operation after a pause due to
// excessive thrashing
// FIXME: Ought to permit some spacing of these functions, rather
// than simply hammering through them
s.state = normal
s.failures = 0
s.logBackoff(s, false)
for _, id := range s.restartQueue {
service, present := s.services[id]
if present {
s.runService(service, id)
}
}
s.restartQueue = make([]serviceID, 0, 1)
}
}
}
func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktrace []byte) {
now := s.getNow()
if s.lastFail.IsZero() {
s.lastFail = now
s.failures = 1.0
} else {
sinceLastFail := now.Sub(s.lastFail).Seconds()
intervals := sinceLastFail / s.failureDecay
s.failures = s.failures*math.Pow(.5, intervals) + 1
}
if s.failures > s.failureThreshold {
s.state = paused
s.logBackoff(s, true)
s.resumeTimer = s.getResume(s.failureBackoff)
}
s.lastFail = now
failedService, monitored := s.services[id]
// It is possible for a service to be no longer monitored
// by the time we get here. In that case, just ignore it.
if monitored {
if s.state == normal {
s.runService(failedService, id)
s.logFailure(failedService, s.failures, s.failureThreshold, true, err, stacktrace)
} else {
// FIXME: When restarting, check that the service still
// exists (it may have been stopped in the meantime)
s.restartQueue = append(s.restartQueue, id)
s.logFailure(failedService, s.failures, s.failureThreshold, false, err, stacktrace)
}
}
}
func (s *Supervisor) runService(service Service, id serviceID) {
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 65535, 65535)
written := runtime.Stack(buf, false)
buf = buf[:written]
s.fail(id, r, buf)
}
}()
service.Serve()
s.serviceEnded(id)
}()
}
func (s *Supervisor) removeService(id serviceID) {
service, present := s.services[id]
if present {
delete(s.services, id)
go func() {
successChan := make(chan bool)
go func() {
service.Stop()
successChan <- true
}()
failChan := s.getResume(s.timeout)
select {
case <-successChan:
// Life is good!
case <-failChan:
s.logBadStop(service)
}
}()
}
}
// String implements the fmt.Stringer interface.
func (s *Supervisor) String() string {
return s.Name
}
// sum type pattern for type-safe message passing; see
// http://www.jerf.org/iri/post/2917
type supervisorMessage interface {
isSupervisorMessage()
}
/*
Remove will remove the given service from the Supervisor, and attempt to Stop() it.
The ServiceID token comes from the Add() call.
*/
func (s *Supervisor) Remove(id ServiceToken) error {
sID := supervisorID(id.id >> 32)
if sID != s.id {
return ErrWrongSupervisor
}
s.control <- removeService{serviceID(id.id & 0xffffffff)}
return nil
}
/*
Services returns a []Service containing a snapshot of the services this
Supervisor is managing.
*/
func (s *Supervisor) Services() []Service {
ls := listServices{make(chan []Service)}
s.control <- ls
return <-ls.c
}
type listServices struct {
c chan []Service
}
func (ls listServices) isSupervisorMessage() {}
type removeService struct {
id serviceID
}
func (rs removeService) isSupervisorMessage() {}
func (s *Supervisor) sync() {
s.control <- syncSupervisor{}
}
type syncSupervisor struct {
}
func (ss syncSupervisor) isSupervisorMessage() {}
func (s *Supervisor) fail(id serviceID, err interface{}, stacktrace []byte) {
s.control <- serviceFailed{id, err, stacktrace}
}
type serviceFailed struct {
id serviceID
err interface{}
stacktrace []byte
}
func (sf serviceFailed) isSupervisorMessage() {}
func (s *Supervisor) serviceEnded(id serviceID) {
s.control <- serviceEnded{id}
}
type serviceEnded struct {
id serviceID
}
func (s serviceEnded) isSupervisorMessage() {}
// added by the Add() method
type addService struct {
service Service
response chan serviceID
}
func (as addService) isSupervisorMessage() {}
// Stop stops the Supervisor.
func (s *Supervisor) Stop() {
s.control <- stopSupervisor{}
}
type stopSupervisor struct {
}
func (ss stopSupervisor) isSupervisorMessage() {}
func (s *Supervisor) panic() {
s.control <- panicSupervisor{}
}
type panicSupervisor struct {
}
func (ps panicSupervisor) isSupervisorMessage() {}

View File

@@ -1,49 +0,0 @@
package suture
import "fmt"
type Incrementor struct {
current int
next chan int
stop chan bool
}
func (i *Incrementor) Stop() {
fmt.Println("Stopping the service")
i.stop <- true
}
func (i *Incrementor) Serve() {
for {
select {
case i.next <- i.current:
i.current += 1
case <-i.stop:
// We sync here just to guarantee the output of "Stopping the service",
// so this passes the test reliably.
// Most services would simply "return" here.
i.stop <- true
return
}
}
}
func ExampleNew_simple() {
supervisor := NewSimple("Supervisor")
service := &Incrementor{0, make(chan int), make(chan bool)}
supervisor.Add(service)
go supervisor.ServeBackground()
fmt.Println("Got:", <-service.next)
fmt.Println("Got:", <-service.next)
supervisor.Stop()
// We sync here just to guarantee the output of "Stopping the service"
<-service.stop
// Output:
// Got: 0
// Got: 1
// Stopping the service
}

View File

@@ -1,592 +0,0 @@
package suture
import (
"errors"
"fmt"
"reflect"
"sync"
"testing"
"time"
)
const (
Happy = iota
Fail
Panic
Hang
UseStopChan
)
var everMultistarted = false
// Test that supervisors work perfectly when everything is hunky dory.
func TestTheHappyCase(t *testing.T) {
t.Parallel()
s := NewSimple("A")
if s.String() != "A" {
t.Fatal("Can't get name from a supervisor")
}
service := NewService("B")
s.Add(service)
go s.Serve()
<-service.started
// If we stop the service, it just gets restarted
service.Stop()
<-service.started
// And it is shut down when we stop the supervisor
service.take <- UseStopChan
s.Stop()
<-service.stop
}
// Test that adding to a running supervisor does indeed start the service.
func TestAddingToRunningSupervisor(t *testing.T) {
t.Parallel()
s := NewSimple("A1")
s.ServeBackground()
defer s.Stop()
service := NewService("B1")
s.Add(service)
<-service.started
services := s.Services()
if !reflect.DeepEqual([]Service{service}, services) {
t.Fatal("Can't get list of services as expected.")
}
}
// Test what happens when services fail.
func TestFailures(t *testing.T) {
t.Parallel()
s := NewSimple("A2")
s.failureThreshold = 3.5
go s.Serve()
defer func() {
// to avoid deadlocks during shutdown, we have to not try to send
// things out on channels while we're shutting down (this undoes the
// logFailure overide about 25 lines down)
s.logFailure = func(Service, float64, float64, bool, interface{}, []byte) {}
s.Stop()
}()
s.sync()
service1 := NewService("B2")
service2 := NewService("C2")
s.Add(service1)
<-service1.started
s.Add(service2)
<-service2.started
nowFeeder := NewNowFeeder()
pastVal := time.Unix(1000000, 0)
nowFeeder.appendTimes(pastVal)
s.getNow = nowFeeder.getter
resumeChan := make(chan time.Time)
s.getResume = func(d time.Duration) <-chan time.Time {
return resumeChan
}
failNotify := make(chan bool)
// use this to synchronize on here
s.logFailure = func(s Service, cf float64, ft float64, r bool, error interface{}, stacktrace []byte) {
failNotify <- r
}
// All that setup was for this: Service1, please return now.
service1.take <- Fail
restarted := <-failNotify
<-service1.started
if !restarted || s.failures != 1 || s.lastFail != pastVal {
t.Fatal("Did not fail in the expected manner")
}
// Getting past this means the service was restarted.
service1.take <- Happy
// Service2, your turn.
service2.take <- Fail
nowFeeder.appendTimes(pastVal)
restarted = <-failNotify
<-service2.started
if !restarted || s.failures != 2 || s.lastFail != pastVal {
t.Fatal("Did not fail in the expected manner")
}
// And you're back. (That is, the correct service was restarted.)
service2.take <- Happy
// Now, one failureDecay later, is everything working correctly?
oneDecayLater := time.Unix(1000030, 0)
nowFeeder.appendTimes(oneDecayLater)
service2.take <- Fail
restarted = <-failNotify
<-service2.started
// playing a bit fast and loose here with floating point, but...
// we get 2 by taking the current failure value of 2, decaying it
// by one interval, which cuts it in half to 1, then adding 1 again,
// all of which "should" be precise
if !restarted || s.failures != 2 || s.lastFail != oneDecayLater {
t.Fatal("Did not decay properly", s.lastFail, oneDecayLater)
}
// For a change of pace, service1 would you be so kind as to panic?
nowFeeder.appendTimes(oneDecayLater)
service1.take <- Panic
restarted = <-failNotify
<-service1.started
if !restarted || s.failures != 3 || s.lastFail != oneDecayLater {
t.Fatal("Did not correctly recover from a panic")
}
nowFeeder.appendTimes(oneDecayLater)
backingoff := make(chan bool)
s.logBackoff = func(s *Supervisor, backingOff bool) {
backingoff <- backingOff
}
// And with this failure, we trigger the backoff code.
service1.take <- Fail
backoff := <-backingoff
restarted = <-failNotify
if !backoff || restarted || s.failures != 4 {
t.Fatal("Broke past the threshold but did not log correctly", s.failures)
}
if service1.existing != 0 {
t.Fatal("service1 still exists according to itself?")
}
// service2 is still running, because we don't shut anything down in a
// backoff, we just stop restarting.
service2.take <- Happy
var correct bool
timer := time.NewTimer(time.Millisecond * 10)
// verify the service has not been restarted
// hard to get around race conditions here without simply using a timer...
select {
case service1.take <- Happy:
correct = false
case <-timer.C:
correct = true
}
if !correct {
t.Fatal("Restarted the service during the backoff interval")
}
// tell the supervisor the restart interval has passed
resumeChan <- time.Time{}
backoff = <-backingoff
<-service1.started
s.sync()
if s.failures != 0 {
t.Fatal("Did not reset failure count after coming back from timeout.")
}
nowFeeder.appendTimes(oneDecayLater)
service1.take <- Fail
restarted = <-failNotify
<-service1.started
if !restarted || backoff {
t.Fatal("For some reason, got that we were backing off again.", restarted, backoff)
}
}
func TestRunningAlreadyRunning(t *testing.T) {
t.Parallel()
s := NewSimple("A3")
go s.Serve()
defer s.Stop()
// ensure the supervisor has made it to its main loop
s.sync()
var errored bool
func() {
defer func() {
if r := recover(); r != nil {
errored = true
}
}()
s.Serve()
}()
if !errored {
t.Fatal("Supervisor failed to prevent itself from double-running.")
}
}
func TestFullConstruction(t *testing.T) {
t.Parallel()
s := New("Moo", Spec{
Log: func(string) {},
FailureDecay: 1,
FailureThreshold: 2,
FailureBackoff: 3,
Timeout: time.Second * 29,
})
if s.String() != "Moo" || s.failureDecay != 1 || s.failureThreshold != 2 || s.failureBackoff != 3 || s.timeout != time.Second*29 {
t.Fatal("Full construction failed somehow")
}
}
// This is mostly for coverage testing.
func TestDefaultLogging(t *testing.T) {
t.Parallel()
s := NewSimple("A4")
service := NewService("B4")
s.Add(service)
s.failureThreshold = .5
s.failureBackoff = time.Millisecond * 25
go s.Serve()
s.sync()
<-service.started
resumeChan := make(chan time.Time)
s.getResume = func(d time.Duration) <-chan time.Time {
return resumeChan
}
service.take <- UseStopChan
service.take <- Fail
<-service.stop
resumeChan <- time.Time{}
<-service.started
service.take <- Happy
serviceName(&BarelyService{})
s.logBadStop(service)
s.logFailure(service, 1, 1, true, errors.New("test error"), []byte{})
s.Stop()
}
func TestNestedSupervisors(t *testing.T) {
t.Parallel()
super1 := NewSimple("Top5")
super2 := NewSimple("Nested5")
service := NewService("Service5")
super1.Add(super2)
super2.Add(service)
go super1.Serve()
super1.sync()
<-service.started
service.take <- Happy
super1.Stop()
}
func TestStoppingSupervisorStopsServices(t *testing.T) {
t.Parallel()
s := NewSimple("Top6")
service := NewService("Service 6")
s.Add(service)
go s.Serve()
s.sync()
<-service.started
service.take <- UseStopChan
s.Stop()
<-service.stop
}
func TestStoppingStillWorksWithHungServices(t *testing.T) {
t.Parallel()
s := NewSimple("Top7")
service := NewService("Service WillHang7")
s.Add(service)
go s.Serve()
<-service.started
service.take <- UseStopChan
service.take <- Hang
resumeChan := make(chan time.Time)
s.getResume = func(d time.Duration) <-chan time.Time {
return resumeChan
}
failNotify := make(chan struct{})
s.logBadStop = func(s Service) {
failNotify <- struct{}{}
}
s.Stop()
resumeChan <- time.Time{}
<-failNotify
service.release <- true
<-service.stop
}
func TestRemoveService(t *testing.T) {
t.Parallel()
s := NewSimple("Top")
service := NewService("ServiceToRemove8")
id := s.Add(service)
go s.Serve()
<-service.started
service.take <- UseStopChan
err := s.Remove(id)
if err != nil {
t.Fatal("Removing service somehow failed")
}
<-service.stop
err = s.Remove(ServiceToken{1<<36 + 1})
if err != ErrWrongSupervisor {
t.Fatal("Did not detect that the ServiceToken was wrong")
}
}
func TestFailureToConstruct(t *testing.T) {
t.Parallel()
var s *Supervisor
panics(func() {
s.Serve()
})
s = new(Supervisor)
panics(func() {
s.Serve()
})
}
func TestFailingSupervisors(t *testing.T) {
t.Parallel()
// This is a bit of a complicated test, so let me explain what
// all this is doing:
// 1. Set up a top-level supervisor with a hair-trigger backoff.
// 2. Add a supervisor to that.
// 3. To that supervisor, add a service.
// 4. Panic the supervisor in the middle, sending the top-level into
// backoff.
// 5. Kill the lower level service too.
// 6. Verify that when the top-level service comes out of backoff,
// the service ends up restarted as expected.
// Ultimately, we can't have more than a best-effort recovery here.
// A panic'ed supervisor can't really be trusted to have consistent state,
// and without *that*, we can't trust it to do anything sensible with
// the children it may have been running. So unlike Erlang, we can't
// can't really expect to be able to safely restart them or anything.
// Really, the "correct" answer is that the Supervisor must never panic,
// but in the event that it does, this verifies that it at least tries
// to get on with life.
// This also tests that if a Supervisor itself panics, and one of its
// monitored services goes down in the meantime, that the monitored
// service also gets correctly restarted when the supervisor does.
s1 := NewSimple("Top9")
s2 := NewSimple("Nested9")
service := NewService("Service9")
s1.Add(s2)
s2.Add(service)
go s1.Serve()
<-service.started
s1.failureThreshold = .5
// let us control precisely when s1 comes back
resumeChan := make(chan time.Time)
s1.getResume = func(d time.Duration) <-chan time.Time {
return resumeChan
}
failNotify := make(chan string)
// use this to synchronize on here
s1.logFailure = func(s Service, cf float64, ft float64, r bool, error interface{}, stacktrace []byte) {
failNotify <- fmt.Sprintf("%s", s)
}
s2.panic()
failing := <-failNotify
// that's enough sync to guarantee this:
if failing != "Nested9" || s1.state != paused {
t.Fatal("Top-level supervisor did not go into backoff as expected")
}
service.take <- Fail
resumeChan <- time.Time{}
<-service.started
}
func TestNilSupervisorAdd(t *testing.T) {
t.Parallel()
var s *Supervisor
defer func() {
if r := recover(); r == nil {
t.Fatal("did not panic as expected on nil add")
}
}()
s.Add(s)
}
// http://golangtutorials.blogspot.com/2011/10/gotest-unit-testing-and-benchmarking-go.html
// claims test function are run in the same order as the source file...
// I'm not sure if this is part of the contract, though. Especially in the
// face of "t.Parallel()"...
func TestEverMultistarted(t *testing.T) {
if everMultistarted {
t.Fatal("Seem to have multistarted a service at some point, bummer.")
}
}
// A test service that can be induced to fail, panic, or hang on demand.
func NewService(name string) *FailableService {
return &FailableService{name, make(chan bool), make(chan int),
make(chan bool, 1), make(chan bool), make(chan bool), 0}
}
type FailableService struct {
name string
started chan bool
take chan int
shutdown chan bool
release chan bool
stop chan bool
existing int
}
func (s *FailableService) Serve() {
if s.existing != 0 {
everMultistarted = true
panic("Multi-started the same service! " + s.name)
}
s.existing += 1
s.started <- true
useStopChan := false
for {
select {
case val := <-s.take:
switch val {
case Happy:
// Do nothing on purpose. Life is good!
case Fail:
s.existing -= 1
if useStopChan {
s.stop <- true
}
return
case Panic:
s.existing -= 1
panic("Panic!")
case Hang:
// or more specifically, "hang until I release you"
<-s.release
case UseStopChan:
useStopChan = true
}
case <-s.shutdown:
s.existing -= 1
if useStopChan {
s.stop <- true
}
return
}
}
}
func (s *FailableService) String() string {
return s.name
}
func (s *FailableService) Stop() {
s.shutdown <- true
}
type NowFeeder struct {
values []time.Time
getter func() time.Time
m sync.Mutex
}
// This is used to test serviceName; it's a service without a Stringer.
type BarelyService struct{}
func (bs *BarelyService) Serve() {}
func (bs *BarelyService) Stop() {}
func NewNowFeeder() (nf *NowFeeder) {
nf = new(NowFeeder)
nf.getter = func() time.Time {
nf.m.Lock()
defer nf.m.Unlock()
if len(nf.values) > 0 {
ret := nf.values[0]
nf.values = nf.values[1:]
return ret
}
panic("Ran out of values for NowFeeder")
}
return
}
func (nf *NowFeeder) appendTimes(t ...time.Time) {
nf.m.Lock()
defer nf.m.Unlock()
nf.values = append(nf.values, t...)
}
func panics(doesItPanic func()) (panics bool) {
defer func() {
if r := recover(); r != nil {
panics = true
}
}()
doesItPanic()
return
}

View File

@@ -0,0 +1,23 @@
# Copyright 2011 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
maketables: maketables.go triegen.go
go build $^
normregtest: normregtest.go
go build $^
tables: maketables
./maketables > tables.go
gofmt -w tables.go
# Downloads from www.unicode.org, so not part
# of standard test scripts.
test: testtables regtest
testtables: maketables
./maketables -test > data_test.go && go test -tags=test
regtest: normregtest
./normregtest

View File

@@ -16,17 +16,20 @@ import (
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
"golang.org/x/text/internal/gen"
"golang.org/x/text/internal/triegen"
"golang.org/x/text/internal/ucd"
)
func main() {
gen.Init()
flag.Parse()
loadUnicodeData()
compactCCC()
loadCompositionExclusions()
@@ -43,18 +46,24 @@ func main() {
}
}
var (
tablelist = flag.String("tables",
"all",
"comma-separated list of which tables to generate; "+
"can be 'decomp', 'recomp', 'info' and 'all'")
test = flag.Bool("test",
false,
"test existing tables against DerivedNormalizationProps and generate test data for regression testing")
verbose = flag.Bool("verbose",
false,
"write data to stdout as it is parsed")
)
var url = flag.String("url",
"http://www.unicode.org/Public/"+unicode.Version+"/ucd/",
"URL of Unicode database directory")
var tablelist = flag.String("tables",
"all",
"comma-separated list of which tables to generate; "+
"can be 'decomp', 'recomp', 'info' and 'all'")
var test = flag.Bool("test",
false,
"test existing tables against DerivedNormalizationProps and generate test data for regression testing")
var verbose = flag.Bool("verbose",
false,
"write data to stdout as it is parsed")
var localFiles = flag.Bool("local",
false,
"data files have been copied to the current directory; for debugging only")
var logger = log.New(os.Stderr, "", log.Lshortfile)
const MaxChar = 0x10FFFF // anything above this shouldn't exist
@@ -180,6 +189,27 @@ func (f FormInfo) String() string {
type Decomposition []rune
func openReader(file string) (input io.ReadCloser) {
if *localFiles {
f, err := os.Open(file)
if err != nil {
logger.Fatal(err)
}
input = f
} else {
path := *url + file
resp, err := http.Get(path)
if err != nil {
logger.Fatal(err)
}
if resp.StatusCode != 200 {
logger.Fatal("bad GET status for "+file, resp.Status)
}
input = resp.Body
}
return
}
func parseDecomposition(s string, skipfirst bool) (a []rune, err error) {
decomp := strings.Split(s, " ")
if len(decomp) > 0 && skipfirst {
@@ -196,7 +226,7 @@ func parseDecomposition(s string, skipfirst bool) (a []rune, err error) {
}
func loadUnicodeData() {
f := gen.OpenUCDFile("UnicodeData.txt")
f := openReader("UnicodeData.txt")
defer f.Close()
p := ucd.New(f)
for p.Next() {
@@ -212,7 +242,7 @@ func loadUnicodeData() {
if len(decmap) > 0 {
exp, err = parseDecomposition(decmap, true)
if err != nil {
log.Fatalf(`%U: bad decomp |%v|: "%s"`, r, decmap, err)
logger.Fatalf(`%U: bad decomp |%v|: "%s"`, r, decmap, err)
}
isCompat = true
}
@@ -231,7 +261,7 @@ func loadUnicodeData() {
}
}
if err := p.Err(); err != nil {
log.Fatal(err)
logger.Fatal(err)
}
}
@@ -266,18 +296,18 @@ func compactCCC() {
// 0958 # ...
// See http://unicode.org/reports/tr44/ for full explanation
func loadCompositionExclusions() {
f := gen.OpenUCDFile("CompositionExclusions.txt")
f := openReader("CompositionExclusions.txt")
defer f.Close()
p := ucd.New(f)
for p.Next() {
c := &chars[p.Rune(0)]
if c.excludeInComp {
log.Fatalf("%U: Duplicate entry in exclusions.", c.codePoint)
logger.Fatalf("%U: Duplicate entry in exclusions.", c.codePoint)
}
c.excludeInComp = true
}
if e := p.Err(); e != nil {
log.Fatal(e)
logger.Fatal(e)
}
}
@@ -512,19 +542,19 @@ func computeNonStarterCounts() {
}
}
func printBytes(w io.Writer, b []byte, name string) {
fmt.Fprintf(w, "// %s: %d bytes\n", name, len(b))
fmt.Fprintf(w, "var %s = [...]byte {", name)
func printBytes(b []byte, name string) {
fmt.Printf("// %s: %d bytes\n", name, len(b))
fmt.Printf("var %s = [...]byte {", name)
for i, c := range b {
switch {
case i%64 == 0:
fmt.Fprintf(w, "\n// Bytes %x - %x\n", i, i+63)
fmt.Printf("\n// Bytes %x - %x\n", i, i+63)
case i%8 == 0:
fmt.Fprintf(w, "\n")
fmt.Printf("\n")
}
fmt.Fprintf(w, "0x%.2X, ", c)
fmt.Printf("0x%.2X, ", c)
}
fmt.Fprint(w, "\n}\n\n")
fmt.Print("\n}\n\n")
}
// See forminfo.go for format.
@@ -580,13 +610,13 @@ func (m *decompSet) insert(key int, s string) {
m[key][s] = true
}
func printCharInfoTables(w io.Writer) int {
func printCharInfoTables() int {
mkstr := func(r rune, f *FormInfo) (int, string) {
d := f.expandedDecomp
s := string([]rune(d))
if max := 1 << 6; len(s) >= max {
const msg = "%U: too many bytes in decomposition: %d >= %d"
log.Fatalf(msg, r, len(s), max)
logger.Fatalf(msg, r, len(s), max)
}
head := uint8(len(s))
if f.quickCheck[MComposed] != QCYes {
@@ -601,11 +631,11 @@ func printCharInfoTables(w io.Writer) int {
tccc := ccc(d[len(d)-1])
cc := ccc(r)
if cc != 0 && lccc == 0 && tccc == 0 {
log.Fatalf("%U: trailing and leading ccc are 0 for non-zero ccc %d", r, cc)
logger.Fatalf("%U: trailing and leading ccc are 0 for non-zero ccc %d", r, cc)
}
if tccc < lccc && lccc != 0 {
const msg = "%U: lccc (%d) must be <= tcc (%d)"
log.Fatalf(msg, r, lccc, tccc)
logger.Fatalf(msg, r, lccc, tccc)
}
index := normalDecomp
nTrail := chars[r].nTrailingNonStarters
@@ -622,13 +652,13 @@ func printCharInfoTables(w io.Writer) int {
if lccc > 0 {
s += string([]byte{lccc})
if index == firstCCC {
log.Fatalf("%U: multi-segment decomposition not supported for decompositions with leading CCC != 0", r)
logger.Fatalf("%U: multi-segment decomposition not supported for decompositions with leading CCC != 0", r)
}
index = firstLeadingCCC
}
if cc != lccc {
if cc != 0 {
log.Fatalf("%U: for lccc != ccc, expected ccc to be 0; was %d", r, cc)
logger.Fatalf("%U: for lccc != ccc, expected ccc to be 0; was %d", r, cc)
}
index = firstCCCZeroExcept
}
@@ -650,7 +680,7 @@ func printCharInfoTables(w io.Writer) int {
continue
}
if f.combinesBackward {
log.Fatalf("%U: combinesBackward and decompose", c.codePoint)
logger.Fatalf("%U: combinesBackward and decompose", c.codePoint)
}
index, s := mkstr(c.codePoint, &f)
decompSet.insert(index, s)
@@ -661,7 +691,7 @@ func printCharInfoTables(w io.Writer) int {
size := 0
positionMap := make(map[string]uint16)
decompositions.WriteString("\000")
fmt.Fprintln(w, "const (")
fmt.Println("const (")
for i, m := range decompSet {
sa := []string{}
for s := range m {
@@ -674,13 +704,13 @@ func printCharInfoTables(w io.Writer) int {
positionMap[s] = uint16(p)
}
if cname[i] != "" {
fmt.Fprintf(w, "%s = 0x%X\n", cname[i], decompositions.Len())
fmt.Printf("%s = 0x%X\n", cname[i], decompositions.Len())
}
}
fmt.Fprintln(w, "maxDecomp = 0x8000")
fmt.Fprintln(w, ")")
fmt.Println("maxDecomp = 0x8000")
fmt.Println(")")
b := decompositions.Bytes()
printBytes(w, b, "decomps")
printBytes(b, "decomps")
size += len(b)
varnames := []string{"nfc", "nfkc"}
@@ -696,7 +726,7 @@ func printCharInfoTables(w io.Writer) int {
if c.ccc != ccc(d[0]) {
// We assume the lead ccc of a decomposition !=0 in this case.
if ccc(d[0]) == 0 {
log.Fatalf("Expected leading CCC to be non-zero; ccc is %d", c.ccc)
logger.Fatalf("Expected leading CCC to be non-zero; ccc is %d", c.ccc)
}
}
} else if c.nLeadingNonStarters > 0 && len(f.expandedDecomp) == 0 && c.ccc == 0 && !f.combinesBackward {
@@ -707,9 +737,9 @@ func printCharInfoTables(w io.Writer) int {
trie.Insert(c.codePoint, uint64(0x8000|v))
}
}
sz, err := trie.Gen(w, triegen.Compact(&normCompacter{name: varnames[i]}))
sz, err := trie.Gen(os.Stdout, triegen.Compact(&normCompacter{name: varnames[i]}))
if err != nil {
log.Fatal(err)
logger.Fatal(err)
}
size += sz
}
@@ -725,9 +755,30 @@ func contains(sa []string, s string) bool {
return false
}
func makeTables() {
w := &bytes.Buffer{}
// Extract the version number from the URL.
func version() string {
// From http://www.unicode.org/standard/versions/#Version_Numbering:
// for the later Unicode versions, data files are located in
// versioned directories.
fields := strings.Split(*url, "/")
for _, f := range fields {
if match, _ := regexp.MatchString(`[0-9]\.[0-9]\.[0-9]`, f); match {
return f
}
}
logger.Fatal("unknown version")
return "Unknown"
}
const fileHeader = `// Generated by running
// maketables --tables=%s --url=%s
// DO NOT EDIT
package norm
`
func makeTables() {
size := 0
if *tablelist == "" {
return
@@ -736,6 +787,7 @@ func makeTables() {
if *tablelist == "all" {
list = []string{"recomp", "info"}
}
fmt.Printf(fileHeader, *tablelist, *url)
// Compute maximum decomposition size.
max := 0
@@ -745,30 +797,30 @@ func makeTables() {
}
}
fmt.Fprintln(w, "const (")
fmt.Fprintln(w, "\t// Version is the Unicode edition from which the tables are derived.")
fmt.Fprintf(w, "\tVersion = %q\n", gen.UnicodeVersion())
fmt.Fprintln(w)
fmt.Fprintln(w, "\t// MaxTransformChunkSize indicates the maximum number of bytes that Transform")
fmt.Fprintln(w, "\t// may need to write atomically for any Form. Making a destination buffer at")
fmt.Fprintln(w, "\t// least this size ensures that Transform can always make progress and that")
fmt.Fprintln(w, "\t// the user does not need to grow the buffer on an ErrShortDst.")
fmt.Fprintf(w, "\tMaxTransformChunkSize = %d+maxNonStarters*4\n", len(string(0x034F))+max)
fmt.Fprintln(w, ")\n")
fmt.Println("const (")
fmt.Println("\t// Version is the Unicode edition from which the tables are derived.")
fmt.Printf("\tVersion = %q\n", version())
fmt.Println()
fmt.Println("\t// MaxTransformChunkSize indicates the maximum number of bytes that Transform")
fmt.Println("\t// may need to write atomically for any Form. Making a destination buffer at")
fmt.Println("\t// least this size ensures that Transform can always make progress and that")
fmt.Println("\t// the user does not need to grow the buffer on an ErrShortDst.")
fmt.Printf("\tMaxTransformChunkSize = %d+maxNonStarters*4\n", len(string(0x034F))+max)
fmt.Println(")\n")
// Print the CCC remap table.
size += len(cccMap)
fmt.Fprintf(w, "var ccc = [%d]uint8{", len(cccMap))
fmt.Printf("var ccc = [%d]uint8{", len(cccMap))
for i := 0; i < len(cccMap); i++ {
if i%8 == 0 {
fmt.Fprintln(w)
fmt.Println()
}
fmt.Fprintf(w, "%3d, ", cccMap[uint8(i)])
fmt.Printf("%3d, ", cccMap[uint8(i)])
}
fmt.Fprintln(w, "\n}\n")
fmt.Println("\n}\n")
if contains(list, "info") {
size += printCharInfoTables(w)
size += printCharInfoTables()
}
if contains(list, "recomp") {
@@ -790,21 +842,20 @@ func makeTables() {
}
sz := nrentries * 8
size += sz
fmt.Fprintf(w, "// recompMap: %d bytes (entries only)\n", sz)
fmt.Fprintln(w, "var recompMap = map[uint32]rune{")
fmt.Printf("// recompMap: %d bytes (entries only)\n", sz)
fmt.Println("var recompMap = map[uint32]rune{")
for i, c := range chars {
f := c.forms[FCanonical]
d := f.decomp
if !f.isOneWay && len(d) > 0 {
key := uint32(uint16(d[0]))<<16 + uint32(uint16(d[1]))
fmt.Fprintf(w, "0x%.8X: 0x%.4X,\n", key, i)
fmt.Printf("0x%.8X: 0x%.4X,\n", key, i)
}
}
fmt.Fprintf(w, "}\n\n")
fmt.Printf("}\n\n")
}
fmt.Fprintf(w, "// Total size of tables: %dKB (%d bytes)\n", (size+512)/1024, size)
gen.WriteGoFile("tables.go", "norm", w.Bytes())
fmt.Printf("// Total size of tables: %dKB (%d bytes)\n", (size+512)/1024, size)
}
func printChars() {
@@ -850,7 +901,7 @@ func verifyComputed() {
nfc := c.forms[FCanonical]
nfkc := c.forms[FCompatibility]
if nfc.combinesBackward != nfkc.combinesBackward {
log.Fatalf("%U: Cannot combine combinesBackward\n", c.codePoint)
logger.Fatalf("%U: Cannot combine combinesBackward\n", c.codePoint)
}
}
}
@@ -862,7 +913,7 @@ func verifyComputed() {
// 0374 ; NFD_QC; N # ...
// See http://unicode.org/reports/tr44/ for full explanation
func testDerived() {
f := gen.OpenUCDFile("DerivedNormalizationProps.txt")
f := openReader("DerivedNormalizationProps.txt")
defer f.Close()
p := ucd.New(f)
for p.Next() {
@@ -895,12 +946,12 @@ func testDerived() {
log.Fatalf(`Unexpected quick check value "%s"`, p.String(2))
}
if got := c.forms[ftype].quickCheck[mode]; got != qr {
log.Printf("%U: FAILED %s (was %v need %v)\n", r, qt, got, qr)
logger.Printf("%U: FAILED %s (was %v need %v)\n", r, qt, got, qr)
}
c.forms[ftype].verified[mode] = true
}
if err := p.Err(); err != nil {
log.Fatal(err)
logger.Fatal(err)
}
// Any unspecified value must be QCYes. Verify this.
for i, c := range chars {
@@ -908,14 +959,20 @@ func testDerived() {
for k, qr := range fd.quickCheck {
if !fd.verified[k] && qr != QCYes {
m := "%U: FAIL F:%d M:%d (was %v need Yes) %s\n"
log.Printf(m, i, j, k, qr, c.name)
logger.Printf(m, i, j, k, qr, c.name)
}
}
}
}
}
var testHeader = `const (
var testHeader = `// Generated by running
// maketables --test --url=%s
// +build test
package norm
const (
Yes = iota
No
Maybe
@@ -953,10 +1010,8 @@ func printTestdata() {
nTrail uint8
f string
}
last := lastInfo{}
w := &bytes.Buffer{}
fmt.Fprintf(w, testHeader)
fmt.Printf(testHeader, *url)
for r, c := range chars {
f := c.forms[FCanonical]
qc, cf, d := f.quickCheck[MComposed], f.combinesForward, string(f.expandedDecomp)
@@ -970,10 +1025,9 @@ func printTestdata() {
}
current := lastInfo{c.ccc, c.nLeadingNonStarters, c.nTrailingNonStarters, s}
if last != current {
fmt.Fprintf(w, "\t{0x%x, %d, %d, %d, %s},\n", r, c.origCCC, c.nLeadingNonStarters, c.nTrailingNonStarters, s)
fmt.Printf("\t{0x%x, %d, %d, %d, %s},\n", r, c.origCCC, c.nLeadingNonStarters, c.nTrailingNonStarters, s)
last = current
}
}
fmt.Fprintln(w, "}")
gen.WriteGoFile("data_test.go", "norm", w.Bytes())
fmt.Println("}")
}

View File

@@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run maketables.go triegen.go
//go:generate go run maketables.go triegen.go -test
// Package norm contains types and functions for normalizing Unicode strings.
package norm

View File

@@ -2,37 +2,52 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package norm
// +build ignore
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"log"
"net/http"
"os"
"path"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"
"unicode"
"unicode/utf8"
"golang.org/x/text/internal/gen"
"golang.org/x/text/unicode/norm"
)
var long = flag.Bool("long", false,
"run time-consuming tests, such as tests that fetch data online")
var once sync.Once
func skipShort(t *testing.T) {
if !gen.IsLocal() && !*long {
t.Skip("skipping test to prevent downloading; to run use -long or use -local to specify a local source")
func main() {
flag.Parse()
loadTestData()
CharacterByCharacterTests()
StandardTests()
PerformanceTest()
if errorCount == 0 {
fmt.Println("PASS")
}
once.Do(func() { loadTestData(t) })
}
const file = "NormalizationTest.txt"
var url = flag.String("url",
"http://www.unicode.org/Public/"+unicode.Version+"/ucd/"+file,
"URL of Unicode database directory")
var localFiles = flag.Bool("local",
false,
"data files have been copied to the current directory; for debugging only")
var logger = log.New(os.Stderr, "", log.Lshortfile)
// This regression test runs the test set in NormalizationTest.txt
// (taken from http://www.unicode.org/Public/<unicode.Version>/ucd/).
//
@@ -109,8 +124,22 @@ var testRe = regexp.MustCompile(`^` + strings.Repeat(`([\dA-F ]+);`, 5) + ` # (.
var counter int
// Load the data form NormalizationTest.txt
func loadTestData(t *testing.T) {
f := gen.OpenUCDFile("NormalizationTest.txt")
func loadTestData() {
if *localFiles {
pwd, _ := os.Getwd()
*url = "file://" + path.Join(pwd, file)
}
t := &http.Transport{}
t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
c := &http.Client{Transport: t}
resp, err := c.Get(*url)
if err != nil {
logger.Fatal(err)
}
if resp.StatusCode != 200 {
logger.Fatal("bad GET status for "+file, resp.Status)
}
f := resp.Body
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
@@ -121,11 +150,11 @@ func loadTestData(t *testing.T) {
m := partRe.FindStringSubmatch(line)
if m != nil {
if len(m) < 3 {
t.Fatal("Failed to parse Part: ", line)
logger.Fatal("Failed to parse Part: ", line)
}
i, err := strconv.Atoi(m[1])
if err != nil {
t.Fatal(err)
logger.Fatal(err)
}
name := m[2]
part = append(part, Part{name: name[:len(name)-1], number: i})
@@ -133,7 +162,7 @@ func loadTestData(t *testing.T) {
}
m = testRe.FindStringSubmatch(line)
if m == nil || len(m) < 7 {
t.Fatalf(`Failed to parse: "%s" result: %#v`, line, m)
logger.Fatalf(`Failed to parse: "%s" result: %#v`, line, m)
}
test := Test{name: m[6], partnr: len(part) - 1, number: counter}
counter++
@@ -141,7 +170,7 @@ func loadTestData(t *testing.T) {
for _, split := range strings.Split(m[j], " ") {
r, err := strconv.ParseUint(split, 16, 64)
if err != nil {
t.Fatal(err)
logger.Fatal(err)
}
if test.r == 0 {
// save for CharacterByCharacterTests
@@ -156,38 +185,50 @@ func loadTestData(t *testing.T) {
part.tests = append(part.tests, test)
}
if scanner.Err() != nil {
t.Fatal(scanner.Err())
logger.Fatal(scanner.Err())
}
}
func cmpResult(t *testing.T, tc *Test, name string, f Form, gold, test, result string) {
var fstr = []string{"NFC", "NFD", "NFKC", "NFKD"}
var errorCount int
func cmpResult(t *Test, name string, f norm.Form, gold, test, result string) {
if gold != result {
t.Errorf("%s:%s: %s(%+q)=%+q; want %+q: %s",
tc.Name(), name, fstr[f], test, result, gold, tc.name)
errorCount++
if errorCount > 20 {
return
}
logger.Printf("%s:%s: %s(%+q)=%+q; want %+q: %s",
t.Name(), name, fstr[f], test, result, gold, t.name)
}
}
func cmpIsNormal(t *testing.T, tc *Test, name string, f Form, test string, result, want bool) {
func cmpIsNormal(t *Test, name string, f norm.Form, test string, result, want bool) {
if result != want {
t.Errorf("%s:%s: %s(%+q)=%v; want %v", tc.Name(), name, fstr[f], test, result, want)
errorCount++
if errorCount > 20 {
return
}
logger.Printf("%s:%s: %s(%+q)=%v; want %v", t.Name(), name, fstr[f], test, result, want)
}
}
func doTest(t *testing.T, tc *Test, f Form, gold, test string) {
func doTest(t *Test, f norm.Form, gold, test string) {
testb := []byte(test)
result := f.Bytes(testb)
cmpResult(t, tc, "Bytes", f, gold, test, string(result))
cmpResult(t, "Bytes", f, gold, test, string(result))
sresult := f.String(test)
cmpResult(t, tc, "String", f, gold, test, sresult)
cmpResult(t, "String", f, gold, test, sresult)
acc := []byte{}
i := Iter{}
i := norm.Iter{}
i.InitString(f, test)
for !i.Done() {
acc = append(acc, i.Next()...)
}
cmpResult(t, tc, "Iter.Next", f, gold, test, string(acc))
cmpResult(t, "Iter.Next", f, gold, test, string(acc))
buf := make([]byte, 128)
acc = nil
@@ -196,33 +237,32 @@ func doTest(t *testing.T, tc *Test, f Form, gold, test string) {
acc = append(acc, buf[:nDst]...)
p += nSrc
}
cmpResult(t, tc, "Transform", f, gold, test, string(acc))
cmpResult(t, "Transform", f, gold, test, string(acc))
for i := range test {
out := f.Append(f.Bytes([]byte(test[:i])), []byte(test[i:])...)
cmpResult(t, tc, fmt.Sprintf(":Append:%d", i), f, gold, test, string(out))
cmpResult(t, fmt.Sprintf(":Append:%d", i), f, gold, test, string(out))
}
cmpIsNormal(t, tc, "IsNormal", f, test, f.IsNormal([]byte(test)), test == gold)
cmpIsNormal(t, tc, "IsNormalString", f, test, f.IsNormalString(test), test == gold)
cmpIsNormal(t, "IsNormal", f, test, f.IsNormal([]byte(test)), test == gold)
cmpIsNormal(t, "IsNormalString", f, test, f.IsNormalString(test), test == gold)
}
func doConformanceTests(t *testing.T, tc *Test, partn int) {
func doConformanceTests(t *Test, partn int) {
for i := 0; i <= 2; i++ {
doTest(t, tc, NFC, tc.cols[1], tc.cols[i])
doTest(t, tc, NFD, tc.cols[2], tc.cols[i])
doTest(t, tc, NFKC, tc.cols[3], tc.cols[i])
doTest(t, tc, NFKD, tc.cols[4], tc.cols[i])
doTest(t, norm.NFC, t.cols[1], t.cols[i])
doTest(t, norm.NFD, t.cols[2], t.cols[i])
doTest(t, norm.NFKC, t.cols[3], t.cols[i])
doTest(t, norm.NFKD, t.cols[4], t.cols[i])
}
for i := 3; i <= 4; i++ {
doTest(t, tc, NFC, tc.cols[3], tc.cols[i])
doTest(t, tc, NFD, tc.cols[4], tc.cols[i])
doTest(t, tc, NFKC, tc.cols[3], tc.cols[i])
doTest(t, tc, NFKD, tc.cols[4], tc.cols[i])
doTest(t, norm.NFC, t.cols[3], t.cols[i])
doTest(t, norm.NFD, t.cols[4], t.cols[i])
doTest(t, norm.NFKC, t.cols[3], t.cols[i])
doTest(t, norm.NFKD, t.cols[4], t.cols[i])
}
}
func TestCharacterByCharacter(t *testing.T) {
skipShort(t)
func CharacterByCharacterTests() {
tests := part[1].tests
var last rune = 0
for i := 0; i <= len(tests); i++ { // last one is special case
@@ -234,39 +274,37 @@ func TestCharacterByCharacter(t *testing.T) {
}
for last++; last < r; last++ {
// Check all characters that were not explicitly listed in the test.
tc := &Test{partnr: 1, number: -1}
t := &Test{partnr: 1, number: -1}
char := string(last)
doTest(t, tc, NFC, char, char)
doTest(t, tc, NFD, char, char)
doTest(t, tc, NFKC, char, char)
doTest(t, tc, NFKD, char, char)
doTest(t, norm.NFC, char, char)
doTest(t, norm.NFD, char, char)
doTest(t, norm.NFKC, char, char)
doTest(t, norm.NFKD, char, char)
}
if i < len(tests) {
doConformanceTests(t, &tests[i], 1)
doConformanceTests(&tests[i], 1)
}
}
}
func TestStandardTests(t *testing.T) {
skipShort(t)
func StandardTests() {
for _, j := range []int{0, 2, 3} {
for _, test := range part[j].tests {
doConformanceTests(t, &test, j)
doConformanceTests(&test, j)
}
}
}
// TestPerformance verifies that normalization is O(n). If any of the
// PerformanceTest verifies that normalization is O(n). If any of the
// code does not properly check for maxCombiningChars, normalization
// may exhibit O(n**2) behavior.
func TestPerformance(t *testing.T) {
skipShort(t)
func PerformanceTest() {
runtime.GOMAXPROCS(2)
success := make(chan bool, 1)
go func() {
buf := bytes.Repeat([]byte("\u035D"), 1024*1024)
buf = append(buf, "\u035B"...)
NFC.Append(nil, buf...)
norm.NFC.Append(nil, buf...)
success <- true
}()
timeout := time.After(1 * time.Second)
@@ -274,6 +312,7 @@ func TestPerformance(t *testing.T) {
case <-success:
// test completed before the timeout
case <-timeout:
t.Errorf(`unexpectedly long time to complete PerformanceTest`)
errorCount++
logger.Printf(`unexpectedly long time to complete PerformanceTest`)
}
}

View File

@@ -1,4 +1,6 @@
// This file was generated by go generate; DO NOT EDIT
// Generated by running
// maketables --tables=all --url=http://www.unicode.org/Public/7.0.0/ucd/
// DO NOT EDIT
package norm

2
NICKS
View File

@@ -18,7 +18,6 @@ calmh <jakob@nym.se>
cdata <chris@scriptolo.gy>
ceh <emil@hessman.se>
cqcallaw <enlightened.despot@gmail.com>
dzarda <dzardacz@gmail.com>
facastagnini <federico.castagnini@gmail.com>
filoozoom <philippe@schommers.be>
frioux <frew@afoolishmanifesto.com> <frioux@gmail.com>
@@ -39,7 +38,6 @@ philips <brandon@ifup.org>
piobpl <piotrb10@gmail.com>
pluby <phill.luby@newredo.com>
pyfisch <pyfisch@gmail.com>
ralder <ralder@yandex.ru>
rumpelsepp <stefan@sevenbyte.org>
qbit <qbit@deftly.net>
sciurius <jvromans@squirrel.nl>

View File

@@ -1,58 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build ignore
// Generates the list of contributors in gui/index.html based on contents of
// AUTHORS.
package main
import (
"io/ioutil"
"log"
"os"
"regexp"
"sort"
"strings"
)
func main() {
bs := readAll("AUTHORS")
lines := strings.Split(string(bs), "\n")
nameRe := regexp.MustCompile(`(.+?)\s+<`)
authors := make([]string, 0, len(lines))
for _, line := range lines {
if m := nameRe.FindStringSubmatch(line); len(m) == 2 {
authors = append(authors, " <li class=\"auto-generated\">"+m[1]+"</li>")
}
}
sort.Strings(authors)
replacement := strings.Join(authors, "\n")
authorsRe := regexp.MustCompile(`(?s)id="contributor-list">.*?</ul>`)
bs = readAll("gui/index.html")
bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n </ul>"))
if err := ioutil.WriteFile("gui/index.html", bs, 0644); err != nil {
log.Fatal(err)
}
}
func readAll(path string) []byte {
fd, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
bs, err := ioutil.ReadAll(fd)
if err != nil {
log.Fatal(err)
}
return bs
}

View File

@@ -118,7 +118,7 @@ case "${1:-default}" in
-e "STTRACE=$STTRACE" \
syncthing/build:latest \
sh -c './build.sh clean \
&& go tool vet -composites=false cmd/*/*.go internal/*/*.go \
&& go vet ./cmd/... ./internal/... \
&& ( golint ./cmd/... ; golint ./internal/... ) | egrep -v "comment on exported|should have comment" \
&& ./build.sh all \
&& STTRACE=all ./build.sh test-cov'
@@ -134,7 +134,7 @@ case "${1:-default}" in
&& go run build.go -race \
&& export GOPATH=$(pwd)/Godeps/_workspace:$GOPATH \
&& cd test \
&& go test -tags integration -v -timeout 90m -short \
&& go test -tags integration -v -timeout 60m -short \
&& git clean -fxd .'
;;

View File

@@ -1,70 +0,0 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"bytes"
"flag"
"fmt"
"log"
"os/exec"
"regexp"
)
var (
subjectIssues = regexp.MustCompile(`^([^(]+)\s+\((?:fixes|ref) ([^)]+)\)(?:[^\w])?$`)
issueNumbers = regexp.MustCompile(`(#\d+)`)
)
func main() {
flag.Parse()
// Display changelog since the version given on the command line, or
// figure out the last release if there were no arguments.
var prevRel string
if flag.NArg() > 0 {
prevRel = flag.Arg(0)
} else {
bs, err := runError("git", "describe", "--abbrev=0", "HEAD^")
if err != nil {
log.Fatal(err)
}
prevRel = string(bs)
}
// Get the git log with subject and author nickname
bs, err := runError("git", "log", "--reverse", "--pretty=format:%s|%aN", prevRel+"..")
if err != nil {
log.Fatal(err)
}
// Split into lines
for _, line := range bytes.Split(bs, []byte{'\n'}) {
// Split into subject and author
fields := bytes.Split(line, []byte{'|'})
subj := fields[0]
author := fields[1]
// Check if subject contains a "(fixes ...)" or "(ref ...)""
if m := subjectIssues.FindSubmatch(subj); len(m) > 0 {
// Find all issue numbers
issues := issueNumbers.FindAll(m[2], -1)
// Format a changelog entry
fmt.Printf("* %s (%s, @%s)\n", m[1], bytes.Join(issues, []byte(", ")), author)
}
}
}
func runError(cmd string, args ...string) ([]byte, error) {
ecmd := exec.Command(cmd, args...)
bs, err := ecmd.CombinedOutput()
if err != nil {
return nil, err
}
return bytes.TrimSpace(bs), nil
}

11
changelog.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
since="$1"
if [[ -z $since ]] ; then
since="$(git describe --abbrev=0 HEAD^).."
fi
git log --reverse --pretty=format:'* %s, @%aN)' "$since" | egrep 'fixes #\d|ref #\d' | sed 's/)[,. ]*,/,/' | sed 's/fixes #/#/g' | sed 's/ref #/#/g'
git diff "$since" -- AUTHORS

View File

@@ -15,32 +15,33 @@ import (
"flag"
"go/format"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
var tpl = template.Must(template.New("assets").Parse(`package auto
import (
"bytes"
"compress/gzip"
"encoding/base64"
)
const (
AssetsBuildDate = "{{.BuildDate}}"
"io/ioutil"
)
func Assets() map[string][]byte {
var assets = make(map[string][]byte, {{.Assets | len}})
{{range $asset := .Assets}}
assets["{{$asset.Name}}"], _ = base64.StdEncoding.DecodeString("{{$asset.Data}}")
var assets = make(map[string][]byte, {{.assets | len}})
var bs []byte
var gr *gzip.Reader
{{range $asset := .assets}}
bs, _ = base64.StdEncoding.DecodeString("{{$asset.Data}}")
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
assets["{{$asset.Name}}"] = bs
{{end}}
return assets
}
`))
type asset struct {
@@ -85,20 +86,12 @@ func walkerFor(basePath string) filepath.WalkFunc {
}
}
type templateVars struct {
Assets []asset
BuildDate string
}
func main() {
flag.Parse()
filepath.Walk(flag.Arg(0), walkerFor(flag.Arg(0)))
var buf bytes.Buffer
tpl.Execute(&buf, templateVars{
Assets: assets,
BuildDate: time.Now().UTC().Format(http.TimeFormat),
})
tpl.Execute(&buf, map[string][]asset{"assets": assets})
bs, err := format.Source(buf.Bytes())
if err != nil {
panic(err)

View File

@@ -27,7 +27,7 @@ func main() {
log.SetOutput(os.Stdout)
log.SetFlags(0)
target := flag.String("target", "localhost:8384", "Target Syncthing instance")
target := flag.String("target", "localhost:8080", "Target Syncthing instance")
apikey := flag.String("apikey", "", "Syncthing API key")
flag.Parse()

View File

@@ -7,8 +7,6 @@
package main
import (
"bytes"
"compress/gzip"
"crypto/tls"
"encoding/json"
"fmt"
@@ -39,23 +37,18 @@ import (
)
type guiError struct {
Time time.Time `json:"time"`
Error string `json:"error"`
Time time.Time
Error string
}
var (
configInSync = true
guiErrors = []guiError{}
guiErrorsMut sync.Mutex
startTime = time.Now()
modt = time.Now().UTC().Format(http.TimeFormat)
eventSub *events.BufferedSubscription
)
var (
lastEventRequest time.Time
lastEventRequestMut sync.Mutex
)
func init() {
l.AddHandler(logger.LevelWarn, showGuiError)
sub := events.Default.Subscribe(events.AllEvents)
@@ -65,7 +58,7 @@ func init() {
func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) error {
var err error
cert, err := tls.LoadX509KeyPair(locations[locHTTPSCertFile], locations[locHTTPSKeyFile])
cert, err := loadCert(confDir, "https-")
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
l.Infoln("Creating new HTTPS certificate")
@@ -78,7 +71,8 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
name = tlsDefaultCommonName
}
cert, err = newCertificate(locations[locHTTPSCertFile], locations[locHTTPSKeyFile], name)
newCertificate(confDir, "https-", name)
cert, err = loadCert(confDir, "https-")
}
if err != nil {
return err
@@ -109,48 +103,48 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
// The GET handlers
getRestMux := http.NewServeMux()
getRestMux.HandleFunc("/rest/db/completion", withModel(m, restGetDBCompletion)) // device folder
getRestMux.HandleFunc("/rest/db/file", withModel(m, restGetDBFile)) // folder file [blocks]
getRestMux.HandleFunc("/rest/db/ignores", withModel(m, restGetDBIgnores)) // folder
getRestMux.HandleFunc("/rest/db/need", withModel(m, restGetDBNeed)) // folder
getRestMux.HandleFunc("/rest/db/status", withModel(m, restGetDBStatus)) // folder
getRestMux.HandleFunc("/rest/db/browse", withModel(m, restGetDBBrowse)) // folder [prefix] [dirsonly] [levels]
getRestMux.HandleFunc("/rest/events", restGetEvents) // since [limit]
getRestMux.HandleFunc("/rest/stats/device", withModel(m, restGetDeviceStats)) // -
getRestMux.HandleFunc("/rest/stats/folder", withModel(m, restGetFolderStats)) // -
getRestMux.HandleFunc("/rest/svc/deviceid", restGetDeviceID) // id
getRestMux.HandleFunc("/rest/svc/lang", restGetLang) // -
getRestMux.HandleFunc("/rest/svc/report", withModel(m, restGetReport)) // -
getRestMux.HandleFunc("/rest/system/browse", restGetSystemBrowse) // current
getRestMux.HandleFunc("/rest/system/config", restGetSystemConfig) // -
getRestMux.HandleFunc("/rest/system/config/insync", RestGetSystemConfigInsync) // -
getRestMux.HandleFunc("/rest/system/connections", withModel(m, restGetSystemConnections)) // -
getRestMux.HandleFunc("/rest/system/discovery", restGetSystemDiscovery) // -
getRestMux.HandleFunc("/rest/system/error", restGetSystemError) // -
getRestMux.HandleFunc("/rest/system/ping", restPing) // -
getRestMux.HandleFunc("/rest/system/status", restGetSystemStatus) // -
getRestMux.HandleFunc("/rest/system/upgrade", restGetSystemUpgrade) // -
getRestMux.HandleFunc("/rest/system/version", restGetSystemVersion) // -
// The POST handlers
postRestMux := http.NewServeMux()
postRestMux.HandleFunc("/rest/db/prio", withModel(m, restPostDBPrio)) // folder file
postRestMux.HandleFunc("/rest/db/ignores", withModel(m, restPostDBIgnores)) // folder
postRestMux.HandleFunc("/rest/db/override", withModel(m, restPostDBOverride)) // folder
postRestMux.HandleFunc("/rest/db/scan", withModel(m, restPostDBScan)) // folder [sub...]
postRestMux.HandleFunc("/rest/system/config", withModel(m, restPostSystemConfig)) // <body>
postRestMux.HandleFunc("/rest/system/discovery", restPostSystemDiscovery) // device addr
postRestMux.HandleFunc("/rest/system/error", restPostSystemError) // <body>
postRestMux.HandleFunc("/rest/system/error/clear", restPostSystemErrorClear) // -
postRestMux.HandleFunc("/rest/system/ping", restPing) // -
postRestMux.HandleFunc("/rest/system/reset", withModel(m, restPostSystemReset)) // [folder]
postRestMux.HandleFunc("/rest/system/restart", restPostSystemRestart) // -
postRestMux.HandleFunc("/rest/system/shutdown", restPostSystemShutdown) // -
postRestMux.HandleFunc("/rest/system/upgrade", restPostSystemUpgrade) // -
getRestMux.HandleFunc("/rest/ping", restPing)
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/autocomplete/directory", restGetAutocompleteDirectory)
getRestMux.HandleFunc("/rest/discovery", restGetDiscovery)
getRestMux.HandleFunc("/rest/errors", restGetErrors)
getRestMux.HandleFunc("/rest/events", restGetEvents)
getRestMux.HandleFunc("/rest/ignores", withModel(m, restGetIgnores))
getRestMux.HandleFunc("/rest/lang", restGetLang)
getRestMux.HandleFunc("/rest/model", withModel(m, restGetModel))
getRestMux.HandleFunc("/rest/need", withModel(m, restGetNeed))
getRestMux.HandleFunc("/rest/deviceid", restGetDeviceID)
getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport))
getRestMux.HandleFunc("/rest/system", restGetSystem)
getRestMux.HandleFunc("/rest/upgrade", restGetUpgrade)
getRestMux.HandleFunc("/rest/version", restGetVersion)
getRestMux.HandleFunc("/rest/tree", withModel(m, restGetTree))
getRestMux.HandleFunc("/rest/stats/device", withModel(m, restGetDeviceStats))
getRestMux.HandleFunc("/rest/stats/folder", withModel(m, restGetFolderStats))
getRestMux.HandleFunc("/rest/filestatus", withModel(m, restGetFileStatus))
// Debug endpoints, not for general use
getRestMux.HandleFunc("/rest/debug/peerCompletion", withModel(m, restGetPeerCompletion))
// The POST handlers
postRestMux := http.NewServeMux()
postRestMux.HandleFunc("/rest/ping", restPing)
postRestMux.HandleFunc("/rest/config", withModel(m, restPostConfig))
postRestMux.HandleFunc("/rest/discovery/hint", restPostDiscoveryHint)
postRestMux.HandleFunc("/rest/error", restPostError)
postRestMux.HandleFunc("/rest/error/clear", restClearErrors)
postRestMux.HandleFunc("/rest/ignores", withModel(m, restPostIgnores))
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)
postRestMux.HandleFunc("/rest/scan", withModel(m, restPostScan))
postRestMux.HandleFunc("/rest/bump", withModel(m, restPostBump))
// A handler that splits requests between the two above and disables
// caching
restMux := noCacheMiddleware(getPostHandler(getRestMux, postRestMux))
@@ -185,9 +179,6 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
ReadTimeout: 10 * time.Second,
}
csrv := &folderSummarySvc{model: m}
go csrv.Serve()
go func() {
err := srv.Serve(listener)
if err != nil {
@@ -254,7 +245,7 @@ func restPing(w http.ResponseWriter, r *http.Request) {
})
}
func restGetSystemVersion(w http.ResponseWriter, r *http.Request) {
func restGetVersion(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]string{
"version": Version,
@@ -264,7 +255,7 @@ func restGetSystemVersion(w http.ResponseWriter, r *http.Request) {
})
}
func restGetDBBrowse(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restGetTree(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
prefix := qs.Get("prefix")
@@ -282,7 +273,7 @@ func restGetDBBrowse(m *model.Model, w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(tree)
}
func restGetDBCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restGetCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
var deviceStr = qs.Get("device")
@@ -301,15 +292,9 @@ func restGetDBCompletion(m *model.Model, w http.ResponseWriter, r *http.Request)
json.NewEncoder(w).Encode(res)
}
func restGetDBStatus(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
res := folderSummary(m, folder)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
func folderSummary(m *model.Model, folder string) map[string]interface{} {
func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
var res = make(map[string]interface{})
res["invalid"] = cfg.Folders()[folder].Invalid
@@ -337,16 +322,17 @@ func folderSummary(m *model.Model, folder string) map[string]interface{} {
}
}
return res
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
func restPostDBOverride(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restPostOverride(m *model.Model, w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
go m.Override(folder)
}
func restGetDBNeed(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restGetNeed(m *model.Model, w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
@@ -362,7 +348,7 @@ func restGetDBNeed(m *model.Model, w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(output)
}
func restGetSystemConnections(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restGetConnections(m *model.Model, w http.ResponseWriter, r *http.Request) {
var res = m.ConnectionStats()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
@@ -380,7 +366,7 @@ func restGetFolderStats(m *model.Model, w http.ResponseWriter, r *http.Request)
json.NewEncoder(w).Encode(res)
}
func restGetDBFile(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restGetFileStatus(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
file := qs.Get("file")
@@ -401,12 +387,12 @@ func restGetDBFile(m *model.Model, w http.ResponseWriter, r *http.Request) {
})
}
func restGetSystemConfig(w http.ResponseWriter, r *http.Request) {
func restGetConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(cfg.Raw())
}
func restPostSystemConfig(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restPostConfig(m *model.Model, w http.ResponseWriter, r *http.Request) {
var newCfg config.Configuration
err := json.NewDecoder(r.Body).Decode(&newCfg)
if err != nil {
@@ -453,38 +439,23 @@ func restPostSystemConfig(m *model.Model, w http.ResponseWriter, r *http.Request
cfg.Save()
}
func RestGetSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
func restGetConfigInSync(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]bool{"configInSync": configInSync})
}
func restPostSystemRestart(w http.ResponseWriter, r *http.Request) {
func restPostRestart(w http.ResponseWriter, r *http.Request) {
flushResponse(`{"ok": "restarting"}`, w)
go restart()
}
func restPostSystemReset(m *model.Model, w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
folder := qs.Get("folder")
var err error
if len(folder) == 0 {
err = resetDB()
} else {
err = m.ResetFolder(folder)
}
if err != nil {
http.Error(w, err.Error(), 500)
return
}
if len(folder) == 0 {
flushResponse(`{"ok": "resetting database"}`, w)
} else {
flushResponse(`{"ok": "resetting folder " + folder}`, w)
}
func restPostReset(w http.ResponseWriter, r *http.Request) {
flushResponse(`{"ok": "resetting folders"}`, w)
resetFolders()
go restart()
}
func restPostSystemShutdown(w http.ResponseWriter, r *http.Request) {
func restPostShutdown(w http.ResponseWriter, r *http.Request) {
flushResponse(`{"ok": "shutting down"}`, w)
go shutdown()
}
@@ -498,7 +469,7 @@ func flushResponse(s string, w http.ResponseWriter) {
var cpuUsagePercent [10]float64 // The last ten seconds
var cpuUsageLock sync.RWMutex
func restGetSystemStatus(w http.ResponseWriter, r *http.Request) {
func restGetSystem(w http.ResponseWriter, r *http.Request) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
@@ -520,26 +491,25 @@ func restGetSystemStatus(w http.ResponseWriter, r *http.Request) {
cpuUsageLock.RUnlock()
res["cpuPercent"] = cpusum / float64(len(cpuUsagePercent)) / float64(runtime.NumCPU())
res["pathSeparator"] = string(filepath.Separator)
res["uptime"] = int(time.Since(startTime).Seconds())
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
func restGetSystemError(w http.ResponseWriter, r *http.Request) {
func restGetErrors(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
guiErrorsMut.Lock()
json.NewEncoder(w).Encode(map[string][]guiError{"errors": guiErrors})
guiErrorsMut.Unlock()
}
func restPostSystemError(w http.ResponseWriter, r *http.Request) {
func restPostError(w http.ResponseWriter, r *http.Request) {
bs, _ := ioutil.ReadAll(r.Body)
r.Body.Close()
showGuiError(0, string(bs))
}
func restPostSystemErrorClear(w http.ResponseWriter, r *http.Request) {
func restClearErrors(w http.ResponseWriter, r *http.Request) {
guiErrorsMut.Lock()
guiErrors = []guiError{}
guiErrorsMut.Unlock()
@@ -554,7 +524,7 @@ func showGuiError(l logger.LogLevel, err string) {
guiErrorsMut.Unlock()
}
func restPostSystemDiscovery(w http.ResponseWriter, r *http.Request) {
func restPostDiscoveryHint(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var device = qs.Get("device")
var addr = qs.Get("addr")
@@ -563,7 +533,7 @@ func restPostSystemDiscovery(w http.ResponseWriter, r *http.Request) {
}
}
func restGetSystemDiscovery(w http.ResponseWriter, r *http.Request) {
func restGetDiscovery(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
devices := map[string][]discover.CacheEntry{}
@@ -584,7 +554,7 @@ func restGetReport(m *model.Model, w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(reportData(m))
}
func restGetDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restGetIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -600,7 +570,7 @@ func restGetDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
})
}
func restPostDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restPostIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
var data map[string][]string
@@ -618,7 +588,7 @@ func restPostDBIgnores(m *model.Model, w http.ResponseWriter, r *http.Request) {
return
}
restGetDBIgnores(m, w, r)
restGetIgnores(m, w, r)
}
func restGetEvents(w http.ResponseWriter, r *http.Request) {
@@ -628,10 +598,6 @@ func restGetEvents(w http.ResponseWriter, r *http.Request) {
since, _ := strconv.Atoi(sinceStr)
limit, _ := strconv.Atoi(limitStr)
lastEventRequestMut.Lock()
lastEventRequest = time.Now()
lastEventRequestMut.Unlock()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// Flush before blocking, to indicate that we've received the request
@@ -647,12 +613,12 @@ func restGetEvents(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(evs)
}
func restGetSystemUpgrade(w http.ResponseWriter, r *http.Request) {
func restGetUpgrade(w http.ResponseWriter, r *http.Request) {
if noUpgrade {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return
}
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(strings.Contains(Version, "-beta"))
if err != nil {
http.Error(w, err.Error(), 500)
return
@@ -693,8 +659,8 @@ func restGetLang(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(langs)
}
func restPostSystemUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestGithubRelease(Version)
func restPostUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestRelease(strings.Contains(Version, "-beta"))
if err != nil {
l.Warnln("getting latest release:", err)
http.Error(w, err.Error(), 500)
@@ -715,12 +681,12 @@ func restPostSystemUpgrade(w http.ResponseWriter, r *http.Request) {
}
}
func restPostDBScan(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restPostScan(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
if folder != "" {
subs := qs["sub"]
err := m.ScanFolderSubs(folder, subs)
sub := qs.Get("sub")
err := m.ScanFolderSub(folder, sub)
if err != nil {
http.Error(w, err.Error(), 500)
}
@@ -733,12 +699,12 @@ func restPostDBScan(m *model.Model, w http.ResponseWriter, r *http.Request) {
}
}
func restPostDBPrio(m *model.Model, w http.ResponseWriter, r *http.Request) {
func restPostBump(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
file := qs.Get("file")
m.BringToFront(folder, file)
restGetDBNeed(m, w, r)
restGetNeed(m, w, r)
}
func getQR(w http.ResponseWriter, r *http.Request) {
@@ -779,7 +745,7 @@ func restGetPeerCompletion(m *model.Model, w http.ResponseWriter, r *http.Reques
json.NewEncoder(w).Encode(comp)
}
func restGetSystemBrowse(w http.ResponseWriter, r *http.Request) {
func restGetAutocompleteDirectory(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
qs := r.URL.Query()
current := qs.Get("current")
@@ -835,17 +801,8 @@ func embeddedStatic(assetDir string) http.Handler {
if len(mtype) != 0 {
w.Header().Set("Content-Type", mtype)
}
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
} else {
// ungzip if browser not send gzip accepted header
var gr *gzip.Reader
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
gr.Close()
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
w.Header().Set("Last-Modified", auto.AssetsBuildDate)
w.Header().Set("Last-Modified", modt)
w.Write(bs)
})
@@ -871,8 +828,6 @@ func mimeTypeForFile(file string) string {
return "application/x-font-ttf"
case ".woff":
return "application/x-font-woff"
case ".svg":
return "image/svg+xml"
default:
return mime.TypeByExtension(ext)
}
@@ -882,12 +837,12 @@ func toNeedSlice(fs []db.FileInfoTruncated) []map[string]interface{} {
output := make([]map[string]interface{}, len(fs))
for i, file := range fs {
output[i] = map[string]interface{}{
"name": file.Name,
"flags": file.Flags,
"modified": file.Modified,
"version": file.Version,
"localVersion": file.LocalVersion,
"size": file.Size(),
"Name": file.Name,
"Flags": file.Flags,
"Modified": file.Modified,
"Version": file.Version,
"LocalVersion": file.LocalVersion,
"Size": file.Size(),
}
}
return output

View File

@@ -11,6 +11,7 @@ import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
@@ -91,7 +92,7 @@ func newCsrfToken() string {
}
func saveCsrfTokens() {
name := locations[locCsrfTokens]
name := filepath.Join(confDir, "csrftokens.txt")
tmp := fmt.Sprintf("%s.tmp.%d", name, time.Now().UnixNano())
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
@@ -116,7 +117,8 @@ func saveCsrfTokens() {
}
func loadCsrfTokens() {
f, err := os.Open(locations[locCsrfTokens])
name := filepath.Join(confDir, "csrftokens.txt")
f, err := os.Open(name)
if err != nil {
return
}

View File

@@ -1,109 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"os"
"path/filepath"
"runtime"
"strings"
"github.com/syncthing/syncthing/internal/osutil"
)
type locationEnum string
// Use strings as keys to make printout and serialization of the locations map
// more meaningful.
const (
locConfigFile locationEnum = "config"
locCertFile = "certFile"
locKeyFile = "keyFile"
locHTTPSCertFile = "httpsCertFile"
locHTTPSKeyFile = "httpsKeyFile"
locDatabase = "database"
locLogFile = "logFile"
locCsrfTokens = "csrfTokens"
locPanicLog = "panicLog"
locDefFolder = "defFolder"
)
// Platform dependent directories
var baseDirs = map[string]string{
"config": defaultConfigDir(), // Overridden by -home flag
"home": homeDir(), // User's home directory, *not* -home flag
}
// Use the variables from baseDirs here
var locations = map[locationEnum]string{
locConfigFile: "${config}/config.xml",
locCertFile: "${config}/cert.pem",
locKeyFile: "${config}/key.pem",
locHTTPSCertFile: "${config}/https-cert.pem",
locHTTPSKeyFile: "${config}/https-key.pem",
locDatabase: "${config}/index-v0.11.0.db",
locLogFile: "${config}/syncthing.log", // -logfile on Windows
locCsrfTokens: "${config}/csrftokens.txt",
locPanicLog: "${config}/panic-20060102-150405.log", // passed through time.Format()
locDefFolder: "${home}/Sync",
}
// expandLocations replaces the variables in the location map with actual
// directory locations.
func expandLocations() error {
for key, dir := range locations {
for varName, value := range baseDirs {
dir = strings.Replace(dir, "${"+varName+"}", value, -1)
}
var err error
dir, err = osutil.ExpandTilde(dir)
if err != nil {
return err
}
locations[key] = dir
}
return nil
}
// defaultConfigDir returns the default configuration directory, as figured
// out by various the environment variables present on each platform, or dies
// trying.
func defaultConfigDir() string {
switch runtime.GOOS {
case "windows":
if p := os.Getenv("LocalAppData"); p != "" {
return filepath.Join(p, "Syncthing")
}
return filepath.Join(os.Getenv("AppData"), "Syncthing")
case "darwin":
dir, err := osutil.ExpandTilde("~/Library/Application Support/Syncthing")
if err != nil {
l.Fatalln(err)
}
return dir
default:
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
return filepath.Join(xdgCfg, "syncthing")
}
dir, err := osutil.ExpandTilde("~/.config/syncthing")
if err != nil {
l.Fatalln(err)
}
return dir
}
}
// homeDir returns the user's home directory, or dies trying.
func homeDir() string {
home, err := osutil.ExpandTilde("~")
if err != nil {
l.Fatalln(err)
}
return home
}

View File

@@ -19,6 +19,7 @@ import (
"path/filepath"
"regexp"
"runtime"
"runtime/debug"
"runtime/pprof"
"strconv"
"strings"
@@ -50,6 +51,7 @@ var (
BuildHost = "unknown"
BuildUser = "unknown"
IsRelease bool
IsBeta bool
LongVersion string
)
@@ -61,10 +63,7 @@ const (
exitUpgrading = 4
)
const (
bepProtocolName = "bep/1.0"
pingEventInterval = time.Minute
)
const bepProtocolName = "bep/1.0"
var l = logger.DefaultLogger
@@ -81,6 +80,9 @@ func init() {
exp := regexp.MustCompile(`^v\d+\.\d+\.\d+(-beta[\d\.]+)?$`)
IsRelease = exp.MatchString(Version)
// Check for a beta build
IsBeta = strings.Contains(Version, "-beta")
stamp, _ := strconv.Atoi(BuildStamp)
BuildDate = time.Unix(int64(stamp), 0)
@@ -168,11 +170,7 @@ are mostly useful for developers. Use with care.
STNOUPGRADE Disable automatic upgrades.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.
GOGC Percentage of heap growth at which to trigger GC. Default is
100. Lower numbers keep peak memory usage down, at the price
of CPU usage (ie. performance).`
available CPU cores.`
)
// Command line and environment options
@@ -199,11 +197,17 @@ var (
)
func main() {
defConfDir, err := getDefaultConfDir()
if err != nil {
l.Fatalln("home:", err)
}
if runtime.GOOS == "windows" {
// On Windows, we use a log file by default. Setting the -logfile flag
// to "-" disables this behavior.
// to the empty string disables this behavior.
flag.StringVar(&logFile, "logfile", "", "Log file name (use \"-\" for stdout)")
logFile = filepath.Join(defConfDir, "syncthing.log")
flag.StringVar(&logFile, "logfile", logFile, "Log file name (blank for stdout)")
// We also add an option to hide the console window
flag.BoolVar(&noConsole, "no-console", false, "Hide console window")
@@ -217,36 +221,29 @@ func main() {
flag.IntVar(&logFlags, "logflags", logFlags, "Select information in log line prefix")
flag.BoolVar(&noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&noRestart, "no-restart", noRestart, "Do not restart; just exit")
flag.BoolVar(&reset, "reset", false, "Reset the database")
flag.BoolVar(&reset, "reset", false, "Prepare to resync from cluster")
flag.BoolVar(&doUpgrade, "upgrade", false, "Perform upgrade")
flag.BoolVar(&doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
flag.BoolVar(&showVersion, "version", false, "Show version")
flag.StringVar(&upgradeTo, "upgrade-to", upgradeTo, "Force upgrade directly from specified URL")
flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, baseDirs["config"]))
flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, defConfDir))
flag.Parse()
if noConsole {
osutil.HideConsole()
}
if confDir != "" {
if confDir == "" {
// Not set as default above because the string can be really long.
baseDirs["config"] = confDir
confDir = defConfDir
}
if err := expandLocations(); err != nil {
l.Fatalln(err)
}
if runtime.GOOS == "windows" {
if logFile == "" {
// Use the default log file location
logFile = locations[locLogFile]
} else if logFile == "-" {
// Don't use a logFile
logFile = ""
}
if confDir != defConfDir && filepath.Dir(logFile) == defConfDir {
// The user changed the config dir with -home, but not the log file
// location. In this case we assume they meant for the logfile to
// still live in it's default location *relative to the config dir*.
logFile = filepath.Join(confDir, "syncthing.log")
}
if showVersion {
@@ -273,13 +270,13 @@ func main() {
}
}
certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
cert, err := loadCert(dir, "")
if err == nil {
l.Warnln("Key exists; will not overwrite.")
l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0]))
} else {
cert, err = newCertificate(certFile, keyFile, tlsDefaultCommonName)
newCertificate(dir, "", tlsDefaultCommonName)
cert, err = loadCert(dir, "")
myID = protocol.NewDeviceID(cert.Certificate[0])
if err != nil {
l.Fatalln("load cert:", err)
@@ -305,12 +302,17 @@ func main() {
return
}
if info, err := os.Stat(baseDirs["config"]); err == nil && !info.IsDir() {
l.Fatalln("Config directory", baseDirs["config"], "is not a directory")
confDir, err := osutil.ExpandTilde(confDir)
if err != nil {
l.Fatalln("home:", err)
}
if info, err := os.Stat(confDir); err == nil && !info.IsDir() {
l.Fatalln("Config directory", confDir, "is not a directory")
}
// Ensure that our home directory exists.
ensureDir(baseDirs["config"], 0700)
ensureDir(confDir, 0700)
if upgradeTo != "" {
err := upgrade.ToURL(upgradeTo)
@@ -322,7 +324,7 @@ func main() {
}
if doUpgrade || doUpgradeCheck {
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(IsBeta)
if err != nil {
l.Fatalln("Upgrade:", err) // exits 1
}
@@ -336,7 +338,7 @@ func main() {
if doUpgrade {
// Use leveldb database locks to protect against concurrent upgrades
_, err = leveldb.OpenFile(locations[locDatabase], &opt.Options{OpenFilesCacheCapacity: 100})
_, err = leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{OpenFilesCacheCapacity: 100})
if err != nil {
l.Fatalln("Cannot upgrade, database seems to be locked. Is another copy of Syncthing already running?")
}
@@ -352,7 +354,7 @@ func main() {
}
if reset {
resetDB()
resetFolders()
return
}
@@ -366,16 +368,21 @@ func main() {
func syncthingMain() {
var err error
if len(os.Getenv("GOGC")) == 0 {
debug.SetGCPercent(25)
}
if len(os.Getenv("GOMAXPROCS")) == 0 {
runtime.GOMAXPROCS(runtime.NumCPU())
}
events.Default.Log(events.Starting, map[string]string{"home": baseDirs["config"]})
events.Default.Log(events.Starting, map[string]string{"home": confDir})
// Ensure that that we have a certificate and key.
cert, err = tls.LoadX509KeyPair(locations[locCertFile], locations[locKeyFile])
cert, err = loadCert(confDir, "")
if err != nil {
cert, err = newCertificate(locations[locCertFile], locations[locKeyFile], tlsDefaultCommonName)
newCertificate(confDir, "", tlsDefaultCommonName)
cert, err = loadCert(confDir, "")
if err != nil {
l.Fatalln("load cert:", err)
}
@@ -393,7 +400,7 @@ func syncthingMain() {
// Prepare to be able to save configuration
cfgFile := locations[locConfigFile]
cfgFile := filepath.Join(confDir, "config.xml")
var myName string
@@ -488,7 +495,7 @@ func syncthingMain() {
l.Infoln("Local networks:", strings.Join(networks, ", "))
}
dbFile := locations[locDatabase]
dbFile := filepath.Join(confDir, "index")
dbOpts := &opt.Options{OpenFilesCacheCapacity: 100}
ldb, err := leveldb.OpenFile(dbFile, dbOpts)
if err != nil && errors.IsCorrupted(err) {
@@ -507,7 +514,9 @@ func syncthingMain() {
}
}
m := model.NewModel(cfg, myID, myName, "syncthing", Version, ldb)
m := model.NewModel(cfg, myName, "syncthing", Version, ldb)
sanityCheckFolders(cfg, m)
// GUI
@@ -517,12 +526,14 @@ func syncthingMain() {
// start needing a bunch of files which are nowhere to be found. This
// needs to be changed when we correctly do persistent indexes.
for _, folderCfg := range cfg.Folders() {
m.AddFolder(folderCfg)
if folderCfg.Invalid != "" {
continue
}
for _, device := range folderCfg.DeviceIDs() {
if device == myID {
continue
}
m.Index(device, folderCfg.ID, nil, 0, nil)
m.Index(device, folderCfg.ID, nil)
}
}
@@ -546,6 +557,10 @@ func syncthingMain() {
go listenConnect(myID, m, tlsCfg)
for _, folder := range cfg.Folders() {
if folder.Invalid != "" {
continue
}
// Routine to pull blocks from other devices to synchronize the local
// folder. Does not run when we are in read only (publish only) mode.
if folder.ReadOnly {
@@ -611,9 +626,7 @@ func syncthingMain() {
}
events.Default.Log(events.StartupComplete, nil)
go generatePingEvents()
cleanConfigDirectory()
go generateEvents()
code := <-stop
@@ -664,12 +677,66 @@ func setupGUI(cfg *config.Wrapper, m *model.Model) {
}
}
func sanityCheckFolders(cfg *config.Wrapper, m *model.Model) {
nextFolder:
for id, folder := range cfg.Folders() {
if folder.Invalid != "" {
continue
}
m.AddFolder(folder)
fi, err := os.Stat(folder.Path)
if m.CurrentLocalVersion(id) > 0 {
// Safety check. If the cached index contains files but the
// folder doesn't exist, we have a problem. We would assume
// that all files have been deleted which might not be the case,
// so mark it as invalid instead.
if err != nil || !fi.IsDir() {
l.Warnf("Stopping folder %q - path does not exist, but has files in index", folder.ID)
cfg.InvalidateFolder(id, "folder path missing")
continue nextFolder
} else if !folder.HasMarker() {
l.Warnf("Stopping folder %q - path exists, but folder marker missing, check for mount issues", folder.ID)
cfg.InvalidateFolder(id, "folder marker missing")
continue nextFolder
}
} else if os.IsNotExist(err) {
// If we don't have any files in the index, and the directory
// doesn't exist, try creating it.
err = os.MkdirAll(folder.Path, 0700)
if err != nil {
l.Warnf("Stopping folder %q - %v", folder.ID, err)
cfg.InvalidateFolder(id, err.Error())
continue nextFolder
}
err = folder.CreateMarker()
} else if !folder.HasMarker() {
// If we don't have any files in the index, and the path does exist
// but the marker is not there, create it.
err = folder.CreateMarker()
}
if err != nil {
// If there was another error or we could not create the
// path, the folder is invalid.
l.Warnf("Stopping folder %q - %v", folder.ID, err)
cfg.InvalidateFolder(id, err.Error())
continue nextFolder
}
}
}
func defaultConfig(myName string) config.Configuration {
defaultFolder, err := osutil.ExpandTilde("~/Sync")
if err != nil {
l.Fatalln("home:", err)
}
newCfg := config.New(myID)
newCfg.Folders = []config.FolderConfiguration{
{
ID: "default",
Path: locations[locDefFolder],
Path: defaultFolder,
RescanIntervalS: 60,
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
},
@@ -682,7 +749,7 @@ func defaultConfig(myName string) config.Configuration {
},
}
port, err := getFreePort("127.0.0.1", 8384)
port, err := getFreePort("127.0.0.1", 8080)
if err != nil {
l.Fatalln("get free port (GUI):", err)
}
@@ -696,9 +763,9 @@ func defaultConfig(myName string) config.Configuration {
return newCfg
}
func generatePingEvents() {
func generateEvents() {
for {
time.Sleep(pingEventInterval)
time.Sleep(300 * time.Second)
events.Default.Log(events.Ping, nil)
}
}
@@ -805,8 +872,30 @@ func renewUPnP(port int) {
}
}
func resetDB() error {
return os.RemoveAll(locations[locDatabase])
func resetFolders() {
confDir, err := osutil.ExpandTilde(confDir)
if err != nil {
log.Fatal(err)
}
cfgFile := filepath.Join(confDir, "config.xml")
cfg, err := config.Load(cfgFile, myID)
if err != nil {
log.Fatal(err)
}
suffix := fmt.Sprintf(".syncthing-reset-%d", time.Now().UnixNano())
for _, folder := range cfg.Folders() {
if _, err := os.Stat(folder.Path); err == nil {
base := filepath.Base(folder.Path)
dir := filepath.Dir(filepath.Join(folder.Path, ".."))
l.Infof("Reset: Moving %s -> %s", folder.Path, filepath.Join(dir, base+suffix))
os.Rename(folder.Path, filepath.Join(dir, base+suffix))
}
}
idx := filepath.Join(confDir, "index")
os.RemoveAll(idx)
}
func restart() {
@@ -852,6 +941,25 @@ func ensureDir(dir string, mode int) {
}
}
func getDefaultConfDir() (string, error) {
switch runtime.GOOS {
case "windows":
if p := os.Getenv("LocalAppData"); p != "" {
return filepath.Join(p, "Syncthing"), nil
}
return filepath.Join(os.Getenv("AppData"), "Syncthing"), nil
case "darwin":
return osutil.ExpandTilde("~/Library/Application Support/Syncthing")
default:
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
return filepath.Join(xdgCfg, "syncthing"), nil
}
return osutil.ExpandTilde("~/.config/syncthing")
}
}
// getFreePort returns a free TCP port fort listening on. The ports given are
// tried in succession and the first to succeed is returned. If none succeed,
// a random high port is returned.
@@ -949,7 +1057,7 @@ func autoUpgrade() {
case <-timer.C:
}
rel, err := upgrade.LatestGithubRelease(Version)
rel, err := upgrade.LatestRelease(IsBeta)
if err == upgrade.ErrUpgradeUnsupported {
events.Default.Unsubscribe(sub)
return
@@ -982,40 +1090,3 @@ func autoUpgrade() {
return
}
}
// cleanConfigDirectory removes old, unused configuration and index formats, a
// suitable time after they have gone out of fashion.
func cleanConfigDirectory() {
patterns := map[string]time.Duration{
"panic-*.log": 7 * 24 * time.Hour, // keep panic logs for a week
"index": 14 * 24 * time.Hour, // keep old index format for two weeks
"config.xml.v*": 30 * 24 * time.Hour, // old config versions for a month
"*.idx.gz": 30 * 24 * time.Hour, // these should for sure no longer exist
"backup-of-v0.8": 30 * 24 * time.Hour, // these neither
}
for pat, dur := range patterns {
pat = filepath.Join(baseDirs["config"], pat)
files, err := filepath.Glob(pat)
if err != nil {
l.Infoln("Cleaning:", err)
continue
}
for _, file := range files {
info, err := os.Lstat(file)
if err != nil {
l.Infoln("Cleaning:", err)
continue
}
if time.Since(info.ModTime()) > dur {
if err = os.RemoveAll(file); err != nil {
l.Infoln("Cleaning:", err)
} else {
l.Infoln("Cleaned away old file", filepath.Base(file))
}
}
}
}
}

View File

@@ -19,7 +19,7 @@ import (
"github.com/syndtr/goleveldb/leveldb/storage"
)
func TestFolderErrors(t *testing.T) {
func TestSanityCheck(t *testing.T) {
fcfg := config.FolderConfiguration{
ID: "folder",
Path: "testdata/testfolder",
@@ -28,8 +28,7 @@ func TestFolderErrors(t *testing.T) {
Folders: []config.FolderConfiguration{fcfg},
})
for _, file := range []string{".stfolder", "testfolder/.stfolder", "testfolder"} {
os.Remove("testdata/" + file)
for _, file := range []string{".stfolder", "testfolder", "testfolder/.stfolder"} {
_, err := os.Stat("testdata/" + file)
if err == nil {
t.Error("Found unexpected file")
@@ -40,10 +39,10 @@ func TestFolderErrors(t *testing.T) {
// Case 1 - new folder, directory and marker created
m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m := model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if err := m.CheckFolderHealth("folder"); err != nil {
if cfg.Folders()["folder"].Invalid != "" {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
}
@@ -67,10 +66,10 @@ func TestFolderErrors(t *testing.T) {
Folders: []config.FolderConfiguration{fcfg},
})
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m = model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if err := m.CheckFolderHealth("folder"); err != nil {
if cfg.Folders()["folder"].Invalid != "" {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
}
@@ -81,62 +80,31 @@ func TestFolderErrors(t *testing.T) {
os.Remove("testdata/.stfolder")
// Case 3 - Folder marker missing
// Case 3 - marker missing
set := db.NewFileSet("folder", ldb)
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
{Name: "dummyfile"},
})
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m = model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
if cfg.Folders()["folder"].Invalid != "folder marker missing" {
t.Error("Incorrect error")
}
// Case 3.1 - recover after folder marker missing
if err = fcfg.CreateMarker(); err != nil {
t.Error(err)
}
if err := m.CheckFolderHealth("folder"); err != nil {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
}
// Case 4 - Folder path missing
os.Remove("testdata/testfolder/.stfolder")
os.Remove("testdata/testfolder/")
// Case 4 - path missing
fcfg.Path = "testdata/testfolder"
cfg = config.Wrap("testdata/subfolder", config.Configuration{
cfg = config.Wrap("/tmp/test", config.Configuration{
Folders: []config.FolderConfiguration{fcfg},
})
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m = model.NewModel(cfg, "device", "syncthing", "dev", ldb)
sanityCheckFolders(cfg, m)
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder path missing" {
t.Error("Incorrect error: Folder path missing !=", m.CheckFolderHealth("folder"))
}
// Case 4.1 - recover after folder path missing
os.Mkdir("testdata/testfolder", 0700)
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
}
// Case 4.2 - recover after missing marker
if err = fcfg.CreateMarker(); err != nil {
t.Error(err)
}
if err := m.CheckFolderHealth("folder"); err != nil {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
if cfg.Folders()["folder"].Invalid != "folder path missing" {
t.Error("Incorrect error")
}
}

View File

@@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"sync"
@@ -163,7 +164,7 @@ func copyStderr(stderr io.ReadCloser, dst io.Writer) {
dst.Write([]byte(line))
if strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:") {
panicFd, err = os.Create(time.Now().Format(locations[locPanicLog]))
panicFd, err = os.Create(filepath.Join(confDir, time.Now().Format("panic-20060102-150405.log")))
if err != nil {
l.Warnln("Create panic log:", err)
continue

View File

@@ -1,190 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"sync"
"time"
"github.com/syncthing/syncthing/internal/events"
"github.com/syncthing/syncthing/internal/model"
"github.com/thejerf/suture"
)
// The folderSummarySvc adds summary information events (FolderSummary and
// FolderCompletion) into the event stream at certain intervals.
type folderSummarySvc struct {
model *model.Model
srv suture.Service
stop chan struct{}
immediate chan string
// For keeping track of folders to recalculate for
foldersMut sync.Mutex
folders map[string]struct{}
}
func (c *folderSummarySvc) Serve() {
srv := suture.NewSimple("folderSummarySvc")
srv.Add(serviceFunc(c.listenForUpdates))
srv.Add(serviceFunc(c.calculateSummaries))
c.immediate = make(chan string)
c.stop = make(chan struct{})
c.folders = make(map[string]struct{})
c.srv = srv
srv.Serve()
}
func (c *folderSummarySvc) Stop() {
// c.srv.Stop() is mostly a no-op here, but we need to call it anyway so
// c.srv doesn't try to restart the serviceFuncs when they exit after we
// close the stop channel.
c.srv.Stop()
close(c.stop)
}
// listenForUpdates subscribes to the event bus and makes note of folders that
// need their data recalculated.
func (c *folderSummarySvc) listenForUpdates() {
sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged)
defer events.Default.Unsubscribe(sub)
for {
// This loop needs to be fast so we don't miss too many events.
select {
case ev := <-sub.C():
// Whenever the local or remote index is updated for a given
// folder we make a note of it.
data := ev.Data.(map[string]interface{})
folder := data["folder"].(string)
if ev.Type == events.StateChanged && data["to"].(string) == "idle" && data["from"].(string) == "syncing" {
// The folder changed to idle from syncing. We should do an
// immediate refresh to update the GUI. The send to
// c.immediate must be nonblocking so that we can continue
// handling events.
select {
case c.immediate <- folder:
c.foldersMut.Lock()
delete(c.folders, folder)
c.foldersMut.Unlock()
default:
}
} else {
// This folder needs to be refreshed whenever we do the next
// refresh.
c.foldersMut.Lock()
c.folders[folder] = struct{}{}
c.foldersMut.Unlock()
}
case <-c.stop:
return
}
}
}
// calculateSummaries periodically recalculates folder summaries and
// completion percentage, and sends the results on the event bus.
func (c *folderSummarySvc) calculateSummaries() {
const pumpInterval = 2 * time.Second
pump := time.NewTimer(pumpInterval)
for {
select {
case <-pump.C:
t0 := time.Now()
for _, folder := range c.foldersToHandle() {
c.sendSummary(folder)
}
// We don't want to spend all our time calculating summaries. Lets
// set an arbitrary limit at not spending more than about 30% of
// our time here...
wait := 2*time.Since(t0) + pumpInterval
pump.Reset(wait)
case folder := <-c.immediate:
c.sendSummary(folder)
case <-c.stop:
return
}
}
}
// foldersToHandle returns the list of folders needing a summary update, and
// clears the list.
func (c *folderSummarySvc) foldersToHandle() []string {
// We only recalculate sumamries if someone is listening to events
// (a request to /rest/events has been made within the last
// pingEventInterval).
lastEventRequestMut.Lock()
// XXX: Reaching out to a global var here is very ugly :( Should
// we make the gui stuff a proper object with methods on it that
// we can query about this kind of thing?
last := lastEventRequest
lastEventRequestMut.Unlock()
if time.Since(last) > pingEventInterval {
return nil
}
c.foldersMut.Lock()
res := make([]string, 0, len(c.folders))
for folder := range c.folders {
res = append(res, folder)
delete(c.folders, folder)
}
c.foldersMut.Unlock()
return res
}
// sendSummary send the summary events for a single folder
func (c *folderSummarySvc) sendSummary(folder string) {
// The folder summary contains how many bytes, files etc
// are in the folder and how in sync we are.
data := folderSummary(c.model, folder)
events.Default.Log(events.FolderSummary, map[string]interface{}{
"folder": folder,
"summary": data,
})
for _, devCfg := range cfg.Folders()[folder].Devices {
if devCfg.DeviceID.Equals(myID) {
// We already know about ourselves.
continue
}
if !c.model.ConnectedTo(devCfg.DeviceID) {
// We're not interested in disconnected devices.
continue
}
// Get completion percentage of this folder for the
// remote device.
comp := c.model.Completion(devCfg.DeviceID, folder)
events.Default.Log(events.FolderCompletion, map[string]interface{}{
"folder": folder,
"device": devCfg.DeviceID.String(),
"completion": comp,
})
}
}
// serviceFunc wraps a function to create a suture.Service without stop
// functionality.
type serviceFunc func()
func (f serviceFunc) Serve() { f() }
func (f serviceFunc) Stop() {}

View File

View File

View File

@@ -19,6 +19,7 @@ import (
mr "math/rand"
"net"
"os"
"path/filepath"
"time"
)
@@ -27,7 +28,13 @@ const (
tlsDefaultCommonName = "syncthing"
)
func newCertificate(certFile, keyFile, name string) (tls.Certificate, error) {
func loadCert(dir string, prefix string) (tls.Certificate, error) {
cf := filepath.Join(dir, prefix+"cert.pem")
kf := filepath.Join(dir, prefix+"key.pem")
return tls.LoadX509KeyPair(cf, kf)
}
func newCertificate(dir, prefix, name string) {
l.Infof("Generating RSA key and certificate for %s...", name)
priv, err := rsa.GenerateKey(rand.Reader, tlsRSABits)
@@ -56,7 +63,7 @@ func newCertificate(certFile, keyFile, name string) (tls.Certificate, error) {
l.Fatalln("create cert:", err)
}
certOut, err := os.Create(certFile)
certOut, err := os.Create(filepath.Join(dir, prefix+"cert.pem"))
if err != nil {
l.Fatalln("save cert:", err)
}
@@ -69,7 +76,7 @@ func newCertificate(certFile, keyFile, name string) (tls.Certificate, error) {
l.Fatalln("save cert:", err)
}
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
keyOut, err := os.OpenFile(filepath.Join(dir, prefix+"key.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
l.Fatalln("save key:", err)
}
@@ -81,8 +88,6 @@ func newCertificate(certFile, keyFile, name string) (tls.Certificate, error) {
if err != nil {
l.Fatalln("save key:", err)
}
return tls.LoadX509KeyPair(certFile, keyFile)
}
type DowngradingListener struct {

View File

@@ -8,7 +8,8 @@ User=%i
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing -no-browser -logflags=0
Restart=on-failure
SuccessExitStatus=2 3 4
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]

View File

@@ -7,7 +7,8 @@ After=network.target
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing -no-browser -logflags=0
Restart=on-failure
SuccessExitStatus=2 3 4
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]

View File

@@ -153,45 +153,6 @@ table.table-condensed td {
padding-right: 15px;
}
/**
* Menu for select language
*/
@media (min-width:480px) {
*[language-select] > .dropdown-menu > li {
width: 50%;
float: left;
}
*[language-select] > .dropdown-menu {
width: 400px;
}
}
@media (max-width:479px) {
.dropdown-menu {
padding-top: 55px;
}
*[language-select] > .dropdown-toggle {
font-size: 14px;
}
.dropdown-toggle {
float: left;
}
.navbar-brand {
padding-left: 0;
padding-top: 16px;
}
.navbar-nav .open .dropdown-menu > li > a {
padding: 12px 15px 12px 25px;
}
}
.panel-body .table-condensed {
margin-bottom: 0;
}

View File

@@ -9,7 +9,6 @@
"Addresses": "Адрасы",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Automatic upgrades": "Automatic upgrades",
@@ -17,13 +16,13 @@
"CPU Utilization": "Выкарыстаньне працэсара",
"Changelog": "Сьпіс зьменаў",
"Close": "Зачыніць",
"Command": "Command",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression": "Compression",
"Compression is recommended in most setups.": "Сьцісканьне рэкамэндавана ў большасьці выпадкаў.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Выдаліць",
"Device ID": "ID прылады",
"Device Identification": "Ідэнтыфікацыя прылады",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error",
"External File Versioning": "External File Versioning",
"File Versioning": "Вэрсіі файлаў",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Folder ID": "ID каталёгу",
"Folder Master": "Folder Master",
@@ -59,6 +57,7 @@
"Global Discovery": "Глябальнае вызначэньне",
"Global Discovery Server": "Сэрвер глябальнага вызначэньня",
"Global State": "Глябальны стан",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ігнараваць шаблёны",
"Ignore Permissions": "Ігнараваць правы",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Трымаць вэрсій",
"Last File Received": "Апошні атрыманы файл",
"Last File Synced": "Апошні сынхранізаваны файл",
"Last seen": "Апошні раз бачылі",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Лякальнае вызначэньне",
"Local State": "Лякальны стан",
"Maximum Age": "Maximum Age",
@@ -83,6 +84,8 @@
"Notice": "Notice",
"OK": "Добра",
"Off": "Off",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Несынхранізавана",
"Out of Sync Items": "Несынхранізаваныя складнікі",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
@@ -123,23 +126,24 @@
"Source Code": "Зыходнікі",
"Staggered File Versioning": "Адаптыўнае захоўваньне вэрсій",
"Start Browser": "Start Browser",
"Stopped": "Спынена",
"Stopped": "Stopped",
"Support": "Падтрымка",
"Support / Forum": "Падтрымка / Форум",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Сынхранізацыя",
"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 is restarting.": "Syncthing перастартоўвае.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"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…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The device ID cannot be blank.": "The device ID cannot be blank.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.",
"The folder ID must be unique.": "The folder ID must be unique.",
@@ -149,15 +153,16 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
"The number of old versions to keep, per file.": "Колькі старых вэрсій трымаць, для кожнага файлу.",
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"Unknown": "Невядома",
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unused": "Unused",
"Up to Date": "Найноўшае",
"Upgrade To {%version%}": "Upgrade To {{version}}",
"Upgrading": "Абнаўленьне",
"Upload Rate": "Хуткасьць запампоўваньня",
"Use Compression": "Выкарыстоўваць сьцісканьне",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Вэрсія",
"Versions Path": "Versions Path",

View File

@@ -9,7 +9,6 @@
"Addresses": "Адреси",
"All Data": "Всички данни",
"Allow Anonymous Usage Reporting?": "Разреши анонимен доклад за ползване на програмата?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Друга команда се занимава с версиите. Тази команда трябва да премахни файла от синхронизираната папка.",
"Anonymous Usage Reporting": "Анонимен Доклад",
"Any devices configured on an introducer device will be added to this device as well.": "Устройства настроени на introducer компютъра също ще бъдат добавени към този компютър.",
"Automatic upgrades": "Автоматични ъпдейти",
@@ -17,13 +16,13 @@
"CPU Utilization": "Натоварване на Процесора",
"Changelog": "Сипъск с промени",
"Close": "Затвори",
"Command": "Команда",
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
"Compression": "Компресия",
"Compression is recommended in most setups.": "Компресията е препоръчителна в повечето конфигурации.",
"Connection Error": "Грешка при Свързването",
"Copied from elsewhere": "Копиране от някъде другаде",
"Copied from original": "Копиран от оригинала",
"Copyright © 2015 the following Contributors:": "Правата запазени © 2015 Сътрудници:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Правата запазени © 2014 Jakob Borg и следните Сътрудници:",
"Delete": "Изтрий",
"Device ID": "Идентификатор на устройство",
"Device Identification": "Идентификация на устройство",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Въведи \"ip:port\" адреси разделени със запетая или \"dynamic\", за да извършиш автоматична връзка на адреси.",
"Enter ignore patterns, one per line.": "Добави шаблони за игнориране, по един на ред.",
"Error": "Грешка",
"External File Versioning": "Външно упраление на версиите",
"File Versioning": "Файлови Версии",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Битовете за права за достъп са игнорирани, когато се проверява за промени. Използвай с файлови системи тип FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Когато syncthing замени или изтрие файл той се премества в .stversions и преименува с дабавени дата и час.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Битовете за права за достъп са игнорирани, когато се проверява за промени. Използвай с файлови системи тип FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Когато syncthing замени или изтрие файл той се премества в .stversions и преименува с дабавени дата и час.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файловете са защитени от промени направени на други устройства, но промени направени на това устройство ще бъдат синхронизирани с другите устройства.",
"Folder ID": "Идентификатор на папка",
"Folder Master": "Главна папка",
@@ -59,6 +57,7 @@
"Global Discovery": "Глобавно Откриване",
"Global Discovery Server": "Сървър за Глобално Откриване",
"Global State": "Глобално състояние",
"Idle": "Без Работа",
"Ignore": "Игнорирай",
"Ignore Patterns": "Шаблони за Игнориране",
"Ignore Permissions": "Игнорирай Права за Достъп",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
"Keep Versions": "Пази Версии",
"Last File Received": "Последния получен файл",
"Last File Synced": "Последния синхронизиран файл",
"Last seen": "Последно видян",
"Later": "По-късно",
"Latest Release": "Най-новата Версия",
"Local Discovery": "Локално Откриване",
"Local State": "Локално състояние",
"Maximum Age": "Максимална Възраст",
@@ -83,6 +84,8 @@
"Notice": "Известие",
"OK": "ОК",
"Off": "Изключено",
"Offline": "Не е на линия",
"Online": "На линия",
"Out Of Sync": "Не Синхронизиран",
"Out of Sync Items": "Несинхронизирани елементи",
"Outgoing Rate Limit (KiB/s)": "Лимит на Изходящата Скорост (KiB/s)",
@@ -125,7 +128,9 @@
"Start Browser": "Стартирай Браузъра",
"Stopped": "Спряна",
"Support": "Помощ",
"Support / Forum": "Помощ / Форум",
"Sync Protocol Listen Addresses": "Адрес за слушане на синхронизиращия протокол",
"Synchronization": "Синхронизация",
"Syncing": "Синхронизиране",
"Syncthing has been shut down.": "Syncthing е спрян.",
"Syncthing includes the following software or portions thereof:": "Syncthing включва следният софтуер пълно или частично:",
@@ -139,7 +144,6 @@
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор на устройство за въвеждане тук, може да бъде намерен в \"Промени > Покажи Идентификатора\". Интервалите и тиретата са пожелание(биват прескачани).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Криптираният доклад се изпраща дневно. Използва се, за да следи общи платформи, размери на папки и версии на приложението. Ако събираните данни се променят, ще бъдете информиран с подобен на този диалог.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Въведни идентификатор на устройство не е валиден. Трябва да бъде 52 или 56 символа и да се състои от букви и цифри, като интервалите и тиретата са пожелание.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Първият параметър за командата е пътя до папката, а вторият е релативния път в самата папка.",
"The folder ID cannot be blank.": "Полето идентификатор на папка неможе да бъде празно.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Идентификаторът на папка трябва да бъде къс (64 символа или по-малко) състоящ се само от букви, цифри, точка(.), тире(-) и подчерта (_).",
"The folder ID must be unique.": "Идентификаторът на папката тряба да бъде уникален.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максималното време да се пазят весрсии (в дни, сложи 0, за да пазиш версии завинаги).",
"The number of old versions to keep, per file.": "Броят стари версии, които да бъдат пазени за всеки файл.",
"The number of versions must be a number and cannot be blank.": "Броят версии трябва да бъде число и не може да бъде празно.",
"The path cannot be blank.": "Пътят неможе да бъде празен.",
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
"The rescan interval must be at least 5 seconds.": "Интервала за повторно сканиране трябва да бъде поне 5 секунди.",
"Unknown": "Неясен",
"Unshared": "Споделянето прекратено",
"Unused": "Неизползван",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Обновен До {{version}}",
"Upgrading": "Обновяване",
"Upload Rate": "Скорост на Качване",
"Use Compression": "Използвай Компресиране",
"Use HTTPS for GUI": "Използвай HTTPS за Потребителския Интерфейс",
"Version": "Версия",
"Versions Path": "Път до Версиите",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adreces",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Permetre l'enviament anònim d'informes d'ús?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Informe anònim d'ús",
"Any devices configured on an introducer device will be added to this device as well.": "Qualsevol dispositiu configurat com a dispositiu introductor s'afegirà a aquest dispositiu tambè.",
"Automatic upgrades": "Actualitzacions automàtiques",
@@ -17,13 +16,13 @@
"CPU Utilization": "Utilització del CPU",
"Changelog": "Historial de canvis",
"Close": "Tancar",
"Command": "Command",
"Comment, when used at the start of a line": "Comentari quan és usat al principi d'una línia",
"Compression": "Compression",
"Compression is recommended in most setups.": "Generalment, la compressió és recomanada.",
"Connection Error": "Error de connexió",
"Copied from elsewhere": "Copiat d'un altre lloc",
"Copied from original": "Copiat de l'original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Drets d'autor © 2014 Jakob Borg i els següents contribuïdors:",
"Delete": "Esborrar",
"Device ID": "ID del dispositiu",
"Device Identification": "Identificació del dispositiu",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduir, separat per comes, adreces \"ip:port\" o \"dynamic\" per descobrir automàticament les adreces.",
"Enter ignore patterns, one per line.": "Introduïx els patrons d'ignoració, un per línia.",
"Error": "Error",
"External File Versioning": "External File Versioning",
"File Versioning": "Versionat de Fitxers",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Els bits de permisos dels fitxers son ignorats quan es cerquen canvis. Utilitzar en sistemes de fitxers FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Els fitxers es mouen amb l'estampat de la data a la carpeta .stversions quan son substituïts o esborrats per syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Els fitxers estan protegits de canvis fets per altres dispositius, però els canvis fets en aquest dispositiu seran enviats a la resta del cluster.",
"Folder ID": "ID de carpeta",
"Folder Master": "Carpeta mestre",
@@ -59,6 +57,7 @@
"Global Discovery": "Descobriment Global",
"Global Discovery Server": "Servidor de Descobriment Global",
"Global State": "Estat global",
"Idle": "Inactiu",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrons d'ignoració",
"Ignore Permissions": "Ignora Permisos",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversió del patrò introduït",
"Keep Versions": "Mantenir Versions",
"Last File Received": "Últim fitxer rebut",
"Last File Synced": "Últim fitxer sincronitzat",
"Last seen": "Vist per última vegada",
"Later": "Després",
"Latest Release": "Última publicació",
"Local Discovery": "Descobriment Local",
"Local State": "Estat local",
"Maximum Age": "Antiguitat Màxima",
@@ -83,6 +84,8 @@
"Notice": "Avís",
"OK": "OK",
"Off": "Off",
"Offline": "Desconnectat",
"Online": "Connectat",
"Out Of Sync": "Fora de la Sincronització",
"Out of Sync Items": "Arxius encara no sincronitzats",
"Outgoing Rate Limit (KiB/s)": "Tasca Límit de Sortida (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Arrancar Navegador",
"Stopped": "Aturat",
"Support": "Suport",
"Support / Forum": "Suport / Fòrum",
"Sync Protocol Listen Addresses": "Adreça d'escolta del Protocol Sync",
"Synchronization": "Sincronització",
"Syncing": "Synthing",
"Syncthing has been shut down.": "S'ha aturat el synthing.",
"Syncthing includes the following software or portions thereof:": "Syncthing inclou el següent programari o parts dels mateixos:",
"Syncthing is restarting.": "Reiniciant syncthing.",
"Syncthing is upgrading.": "Actualitzant syncthing.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Synthing sembla parat, o hi ha algun problema amb la connexió a Internet. Reintentant...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing sembla estar experimentant un problema en processar la teva sol·licitud. Si us plau, recarregeu el navegador o reinicieu Syncthing si el problema persisteix.",
"The aggregated statistics are publicly available at {%url%}.": "Les estadístiques agregades estan públicament disponibles a {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuració s'ha guardar però no s'ha activat. S'ha de reiniciar el synthing per activar la nova configuració.",
"The device ID cannot be blank.": "El ID del dispositiu no pot estar en blanc.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "El ID del dispositiu per introduir ací es pot trobar al diàleg \"Editar > Mostrar ID\" en l'altre dispositiu. Els espais i les barres son opcionals (s'ignoren).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "L'informe d'ús encriptat s'envia diàriament. Es fa servir per rastrejar plataformes habituals, mides de carpetes i versions de l'aplicació. Si es canvia el conjunt de dades reportades es demanarà amb aquest diàleg de nou.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "El ID del dispositiu introduït no sembla vàlid. Hauria de tenir 52 o 56 caràcters amb lletres i números, els espais i les barres son opcionals.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "El ID del dispositiu no pot estar en blanc.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "El ID de la carpeta ha de ser un identificador curt (64 caràcters o menys) format només per lletres, nombres i el punt (.), barra (-) i barra baixa (_).",
"The folder ID must be unique.": "El ID de la carpeta ha de ser únic.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Temps màxim en mantenir una versió (en dies, si es deixa en 0 es mantenen les versions per sempre).",
"The number of old versions to keep, per file.": "El nombre de versions antigues que es mantenen per fitxer.",
"The number of versions must be a number and cannot be blank.": "El nombre de versions ha de ser un número i no es pot deixar en blanc.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "El interval de re-escaneig ha der ser un nombre positiu de segons.",
"The rescan interval must be at least 5 seconds.": "El interval de re-escaneig ha de ser com a mínim de 5 segons.",
"Unknown": "Desconegut",
"Unshared": "No compartit",
"Unused": "No usat",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Actualitzar a {{version}}",
"Upgrading": "Actualitzant",
"Upload Rate": "Tasca de Pujada",
"Use Compression": "Utilitza compressió",
"Use HTTPS for GUI": "Utilitzar HTTPS pel GUI",
"Version": "Versió",
"Versions Path": "Carpeta de les Versions",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adresy",
"All Data": "Všechna data",
"Allow Anonymous Usage Reporting?": "Povolit anonymní hlášení o používání?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
"Any devices configured on an introducer device will be added to this device as well.": "Jakékoliv přístroje nakonfigurované na zavaděči budou přidány také na tento přístroj.",
"Automatic upgrades": "Automatický upgrade",
@@ -17,13 +16,13 @@
"CPU Utilization": "Využití CPU",
"Changelog": "Changelog",
"Close": "Zavřít",
"Command": "Příkaz",
"Comment, when used at the start of a line": "Komentář, pokud použito na začátku řádku",
"Compression": "Komprese",
"Compression is recommended in most setups.": "Komprese je doporučena pro většinu nastavení.",
"Connection Error": "Chyba připojení",
"Copied from elsewhere": "Zkopírováno odjinud",
"Copied from original": "Zkopírováno z originálu",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg a následující přispěvatelé:",
"Delete": "Smazat",
"Device ID": "ID přístroje",
"Device Identification": "Identifikace přístroje",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Vlož čárkou oddělené adresy \"ip:port\" nebo \"dynamic\" (pro automatické zjišťování adres). ",
"Enter ignore patterns, one per line.": "Vložit ignorované vzory, jeden na řádek.",
"Error": "Chyba",
"External File Versioning": "Externí verzování souborů",
"File Versioning": "Verze souborů",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Bity označující práva souborů jsou při hledání změn ignorovány. Použít pro souborové systémy FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do verzí označených daty v adresáři .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Soubory jsou chráněny před změnami na ostatních přístrojích, ale změny provedené z tohoto přístroje budou rozeslány na zbytek clusteru.",
"Folder ID": "ID adresáře",
"Folder Master": "Master adresář",
@@ -59,6 +57,7 @@
"Global Discovery": "Globální oznamování",
"Global Discovery Server": "Server globálního oznamování",
"Global State": "Všeobecný status",
"Idle": "Nečinný",
"Ignore": "Ignorovat",
"Ignore Patterns": "Ignorované vzory",
"Ignore Permissions": "Ignorovat oprávnění",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
"Keep Versions": "Ponechat verze",
"Last File Received": "Poslední přijatý soubor",
"Last File Synced": "Poslední synchronizovaný soubor",
"Last seen": "Naposledy spatřen",
"Later": "Později",
"Latest Release": "Poslední vydání",
"Local Discovery": "Místní oznamování",
"Local State": "Místní status",
"Maximum Age": "Maximální časový limit",
@@ -83,6 +84,8 @@
"Notice": "Oznámení",
"OK": "OK",
"Off": "Vypnuta",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Nesesynchronizováno",
"Out of Sync Items": "Nesesynchronizované položky",
"Outgoing Rate Limit (KiB/s)": "Omezení odchozí rychlosti (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Otevřít prohlížeč",
"Stopped": "Pozastaveno",
"Support": "Podpora",
"Support / Forum": "Podpora / Fórum",
"Sync Protocol Listen Addresses": "Adresa naslouchání synchronizačního protokolu",
"Synchronization": "Synchronizace",
"Syncing": "Synchronizuje se",
"Syncthing has been shut down.": "Syncthing byl vypnut.",
"Syncthing includes the following software or portions thereof:": "Syncthing obsahuje následující software nebo jejich část:",
"Syncthing is restarting.": "Syncthing se restartuje.",
"Syncthing is upgrading.": "Syncthing se aktualizuje.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing se zdá být nefunkční, nebo je problém s připojením k Internetu. Opakuji...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing má nejspíše problém s provedením vašeho požadavku. Pokud problém přetrvává, načtěte znovu data v prohlížeči nebo restartujte Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Souhrnné statistiky jsou veřejně dostupné na {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurace byla uložena, ale není aktivována. Pro aktivaci nové konfigurace je třeba restartovat Syncthing.",
"The device ID cannot be blank.": "ID přístroje nemůže být prázdné.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID přístroje, které je třeba vložit, lze nalézt v dialogu \"Upravit > Zobrazit ID\" na druhém přístroji. Mezery a pomlčky nejsou nutné (budou ignorovány).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Šifrovaná data o využití jsou zasílána denně. Jsou používána pro zjištění nejobvyklejších platforem, velikosti adresářů a verzí aplikace. Pokud se hlášená data změní, budete opět upozorněni tímto dialogem.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Zadané ID přístroje není platné. Mělo by mít 52 nebo 56 znaků a mělo by obsahovat písmena a čísla. Mezery a pomlčky jsou nepovinné.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "První parametr příkazové řádky je cesta k adresáři, druhý je relativní cesta v témže adresáři.",
"The folder ID cannot be blank.": "ID adresáře nemůže být prázdné.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID adresáře musí být stručný popisek (64 znaků nebo méně) obsahující pouze písmena, čísla, tečku (.), pomlčku (-) a nebo podtržítko (_).",
"The folder ID must be unique.": "ID adresáře musí být unikátní.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maximální doba pro zachování verze (dny, zapsáním hodnoty 0 bude ponecháno navždy).",
"The number of old versions to keep, per file.": "Počet starších verzí k zachování pro každý soubor.",
"The number of versions must be a number and cannot be blank.": "Počet verzí musí být číslo a nemůže být prázdné.",
"The path cannot be blank.": "Cesta nesmí být prázdná.",
"The rescan interval must be a non-negative number of seconds.": "Interval opakování skenování musí být pozitivní číslo.",
"The rescan interval must be at least 5 seconds.": "Interval opakování skenování musí být delší než 5 sekund.",
"Unknown": "Neznámý",
"Unshared": "Nesdílený",
"Unused": "Nepoužitý",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Aktualizovat na {{version}}",
"Upgrading": "Aktualizuji",
"Upload Rate": "Rychlost odesílání",
"Use Compression": "Použít kompresi",
"Use HTTPS for GUI": "Použít HTTPS pro grafické rozhraní",
"Version": "Verze",
"Versions Path": "Cesta verzí",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adressen",
"All Data": "Alle Daten",
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteiler eingetragen sind, werden auch bei diesem Gerät eingetragen",
"Automatic upgrades": "automatische Updates",
@@ -17,13 +16,13 @@
"CPU Utilization": "Prozessorauslastung",
"Changelog": "Versionsinfo",
"Close": "Schließen",
"Command": "Command",
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
"Compression": "Komprimierung",
"Compression is recommended in most setups.": "Datenkomprimierung ist für die meisten Anwendungen empfohlen",
"Connection Error": "Verbindungsfehler",
"Copied from elsewhere": "Von woanders kopiert",
"Copied from original": "Vom Original kopiert",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copied from original": "Vom Originial kopiert",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg und folgende Unterstützer:",
"Delete": "Löschen",
"Device ID": "Geräte ID",
"Device Identification": "Gerät Identifikation",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Trage durch ein Komma getrennte \"IP:Port\" Adressen oder \"dynamic\" ein um automatische Adresserkennung durchzuführen.",
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
"Error": "Fehler",
"External File Versioning": "External File Versioning",
"File Versioning": "Dateiversionierung",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT 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, bevor Syncthing sie löscht oder ersetzt, als datierte Versionen in ein Verzeichnis names .stversions verschoben.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind vor Veränderung durch andere Geräte geschützt, auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Folder ID": "Verzeichnis ID",
"Folder Master": "Keine Veränderungen zulassen",
@@ -59,6 +57,7 @@
"Global Discovery": "Globale Auffindung",
"Global Discovery Server": "Globaler Auffindungsserver",
"Global State": "Globaler Status",
"Idle": "Untätig",
"Ignore": "Ignorieren",
"Ignore Patterns": "Ignoriermuster",
"Ignore Permissions": "Berechtigungen ignorieren",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Umkehrung der angegebenen Bedingung (z.B. schließe nicht aus)",
"Keep Versions": "Versionen erhalten",
"Last File Received": "Letzte Datei",
"Last File Synced": "Letzte Änderung",
"Last seen": "Zuletzt online",
"Later": "Später",
"Latest Release": "Letzte Veröffentlichung",
"Local Discovery": "Lokale Auffindung",
"Local State": "Lokaler Status",
"Maximum Age": "Höchstalter",
@@ -83,6 +84,8 @@
"Notice": "Hinweis",
"OK": "OK",
"Off": "Aus",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Nicht synchronisiert",
"Out of Sync Items": "Nicht synchronisierte Objekte",
"Outgoing Rate Limit (KiB/s)": "Ausgehendes Datenratelimit (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Browser starten",
"Stopped": "Gestoppt",
"Support": "Support",
"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 is restarting.": "Syncthing wird neu gestartet",
"Syncthing is upgrading.": "Syncthing wird aktualisiert",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing scheint nicht erreichbar zu sein oder es gibt ein Problem mit Deiner Internetverbindung. Versuche erneut...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Es scheint als ob Syncthing ein Problem mit der Verarbeitung ihrer Eingabe hat. Bitte laden sie die Seite neu oder führen sie einen Neustart von Syncthing durch, falls das Problem weiterhin besteht.",
"The aggregated statistics are publicly available at {%url%}.": "Die gesammelten Statistiken sind öffentlich verfügbar unter {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Die Konfiguration wurde gespeichert, aber nicht aktiviert. Syncthing muss neugestartet werden um die neue Konfiguration zu aktivieren.",
"The device ID cannot be blank.": "Die Geräte ID darf nicht leer sein.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Die hier einzutragende Geräte ID kann im \"Bearbeiten > Zeige ID\"-Dialog auf dem anderen Gerät gefunden werden. Leerzeichen und Bindestriche sind optional (werden ignoriert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Der verschlüsselte Nutzungsbericht wird täglich gesendet. Er wird benutzt um Statistiken über verwendete Betriebssysteme, Verzeichnis-Größen und Programm-Versionen zu erstellen. Sollte der Bericht in Zukunft weitere Daten erfassen, wird dieses Fenster erneut angezeigt.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Die eingegebene Geräte ID scheint nicht gültig zu sein. Es sollte eine 52 oder 56 stellige Zeichenkette aus Buchstaben und Nummern sein. Leerzeichen und Bindestriche sind optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "Die Verzeichnis ID darf nicht leer sein.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Die Verzeichnis ID muss eine kurze Kennung (64 Zeichen oder weniger) sein. Sie kann nur aus Buchstaben, Zahlen und dem Punkt- (.), Bindestrich- (-), und Unterstrich- (_) Zeichen bestehen.",
"The folder ID must be unique.": "Die Verzeichnis ID muss eindeutig sein.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen, 0 bedeutet, alte Versionen für immer zu behalten).",
"The number of old versions to keep, per file.": "Anzahl der alten Versionen, die von jeder Datei gespeichert werden sollen.",
"The number of versions must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Zahl und darf nicht leer sein.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "Das Suchintervall muss eine nicht negative Anzahl von Sekunden sein.",
"The rescan interval must be at least 5 seconds.": "Das Suchintervall muss mindestens 5 Sekunden betragen.",
"Unknown": "Unbekannt",
"Unshared": "Ungeteilt",
"Unused": "Ungenutzt",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Update auf {{version}}",
"Upgrading": "Wird aktualisiert",
"Upload Rate": "Upload",
"Use Compression": "Benutze Komprimierung",
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
"Version": "Version",
"Versions Path": "Versionierungspfad",

View File

@@ -9,7 +9,6 @@
"Addresses": "Διευθύνσεις",
"All Data": "Όλα τα δεδομένα",
"Allow Anonymous Usage Reporting?": "Να επιτρέπεται η αποστολή ανώνυμων στοιχείων χρήσης;",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Ανώνυμα στοιχεία χρήσης",
"Any devices configured on an introducer device will be added to this device as well.": "Αν δηλωθεί σαν «βασικός κόμβος», τότε όλες οι συσκευές που είναι δηλωμένες εκεί θα υπάρχουν και στον τοπικό κόμβο.",
"Automatic upgrades": "Αυτόματη αναβάθμιση",
@@ -17,13 +16,13 @@
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
"Changelog": "Πληροφορίες εκδόσεων",
"Close": "Τέλος",
"Command": "Command",
"Comment, when used at the start of a line": "Σχόλιο, όταν χρησιμοποιείται στην αρχή μιας γραμμής",
"Compression": "Συμπίεση",
"Compression is recommended in most setups.": "Η συμπίεση προτείνεται στις περισσότερες εγκαταστάσεις",
"Connection Error": "Σφάλμα σύνδεσης",
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 από τον Jakob Borg και τους παρακάτω συνεισφορείς:",
"Delete": "Διαγραφή",
"Device ID": "Ταυτότητα συσκευής",
"Device Identification": "Ταυτότητα συσκευής",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Γράψε τις διευθύνσεις IP με τη μορφή «ip:θύρα», διαχωρισμένες με κόμμα. Αλλιώς γράψε «dynamic» για να πραγματοποιηθεί η αυτόματη εύρεση διευθύνσεων.",
"Enter ignore patterns, one per line.": "Δώσε τα πρότυπα που θα αγνοηθούν, ένα σε κάθε γραμμή.",
"Error": "Σφάλμα",
"External File Versioning": "External File Versioning",
"File Versioning": "Τήρηση εκδόσεων",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Να αγνοούνται τα δικαιώματα των αρχείων όταν γίνεται αναζήτηση για αλλαγές. Χρησιμοποιείται σε συστήματα αρχείων τύπου FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Όταν τα αρχεία αντικατασταθούν ή διαγραφούν από το syncthing, τότε να μεταφέρονται χρονοσημασμένα σε φάκελο με το όνομα .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Τα αρχεία προστατεύονται από αλλαγές που γίνονται σε άλλες συσκευές, αλλά όποιες αλλαγές γίνουν σε αυτή τη συσκευή θα αποσταλούν σε όλη τη συστάδα συσκευών.",
"Folder ID": "Ταυτότητα φακέλου",
"Folder Master": "Να μην επιτρέπονται αλλαγές",
@@ -59,6 +57,7 @@
"Global Discovery": "Να μπορεί να ανευρεθεί καθολικά (από παντού)",
"Global Discovery Server": "Διακομιστής καθολικής ανεύρεσης κόμβου",
"Global State": "Καθολική κατάσταση",
"Idle": "Ανενεργό",
"Ignore": "Αγνόησε",
"Ignore Patterns": "Πρότυπο για αγνόηση",
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
"Keep Versions": "Διατήρηση εκδόσεων",
"Last File Received": "Πιο πρόσφατο αρχείο",
"Last File Synced": "Τελευταία αλλαγή",
"Last seen": "Τελευταία φορά συνδεδεμένος",
"Later": "Αργότερα",
"Latest Release": "Τελευταία έκδοση",
"Local Discovery": "Τοπική ανεύρεση",
"Local State": "Τοπική κατάσταση",
"Maximum Age": "Μέγιστη ηλικία",
@@ -83,6 +84,8 @@
"Notice": "Σημείωση",
"OK": "OK",
"Off": "Απενεργοποιημένο",
"Offline": "Εκτός σύνδεσης",
"Online": "Συνδεδεμένο",
"Out Of Sync": "Μη συγχρονισμένα",
"Out of Sync Items": "Μη συγχρονισμένα αντικείμενα",
"Outgoing Rate Limit (KiB/s)": "Ρυθμός αποστολής (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Εκκίνηση προγράμματος περιήγησης",
"Stopped": "Απενεργοποιημένο",
"Support": "Υποστήριξη",
"Support / Forum": "Υποστήριξη / Φόρουμ",
"Sync Protocol Listen Addresses": "Διευθύνσεις για το πρωτόκολλο συγχρονισμού",
"Synchronization": "Συγχρονισμός",
"Syncing": "Συγχρονίζω",
"Syncthing has been shut down.": "Το Syncthing έχει απενεργοποιηθεί.",
"Syncthing includes the following software or portions thereof:": "Το Syncthing περιλαμβάνει τα παρακάτω λογισμικά ή μέρη αυτών:",
"Syncthing is restarting.": "Το Syncthing επανεκκινείται.",
"Syncthing is upgrading.": "Το Syncthing αναβαθμίζεται.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Το Syncthing φαίνεται πως είναι απενεργοποιημένο ή υπάρχει πρόβλημα στη σύνδεσή σου στο διαδίκτυο. Προσπαθώ πάλι…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Φαίνεται πως το Syncthing έχει κάποιο πρόβλημα με αυτό που ζήτησες. Αν το πρόβλημα επιμείνει φόρτωσε ξανά τη σελίδα ή επανεκκίνησε το Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Τα στατιστικά που έχουν συλλεγεί είναι δημόσια διαθέσιμα στο {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Οι ρυθμίσεις έχουν αποθηκευτεί αλλά δεν έχουν ενεργοποιηθεί. Πρέπει να επανεκκινήσεις το Syncthing για να ισχύσουν οι νέες ρυθμίσεις.",
"The device ID cannot be blank.": "Η ταυτότητα της συσκευής δεν μπορεί να είναι κενή",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Η ταυτότητα της συσκευής που θα μπει εδώ βρίσκεται στο μενού «Επεξεργασία > Εμφάνιση ταυτότητας» στην άλλη συσκευή. Κενοί χαρακτήρες και παύλες είναι προαιρετικοί (απλά θα αγνοηθούν).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Η κρυπτογραφημένη αναφορά χρήσης στέλνεται καθημερινά. Χρησιμοποιείται για να παραχθούν στατιστικές για τα λειτουργικά συστήματα που χρησιμοποιούνται, τα μεγέθη των φακέλων και τις εκδόσεις των προγραμμάτων. Αν στο μέλλον συμπεριληφθούν και άλλα δεδομένα στην αναφορά χρήσης, τότε αυτό το παράθυρο θα εμφανιστεί ξανά.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Η ταυτότητα συσκευής που έδωσες δε φαίνεται έγκυρη. Θα πρέπει να είναι μια σειρά από 52 ή 56 χαρακτήρες (γράμματα και αριθμοί). Τα κενά και οι παύλες είναι προαιρετικά (αδιάφορα).",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "Η ταυτότητα του φακέλου δεν μπορεί να είναι κενή.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Η ταυτότητα του φακέλου πρέπει να είναι ένα σύντομο αναγνωριστικό (το πολύ 64 χαρακτήρες). Μπορεί να αποτελείται από γράμματα, αριθμούς, την τελεία (.), την παύλα (-) και την κάτω παύλα (_).",
"The folder ID must be unique.": "Η ταυτότητα του φακέλου πρέπει να είναι μοναδική.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Η μέγιστη ηλικία παλιότερων εκδόσεων (σε ημέρες, αν δώσεις 0 οι παλιότερες εκδόσεις θα διατηρούνται για πάντα).",
"The number of old versions to keep, per file.": "Πόσες παλιότερες εκδόσεις θα διατηρούνται, ανά αρχείο.",
"The number of versions must be a number and cannot be blank.": "Ο αριθμός εκδόσεων πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
"The path cannot be blank.": "Το μονοπάτι δεν μπορεί να είναι κενό.",
"The rescan interval must be a non-negative number of seconds.": "Ο χρόνος επανελέγχου για αλλαγές είναι σε δευτερόλεπτα (δηλ. θετικός αριθμός).",
"The rescan interval must be at least 5 seconds.": "Ο χρόνος επανελέγχου πρέπει να είναι πάνω από 5 δευτερόλεπτα.",
"Unknown": "Άγνωστο",
"Unshared": "Δε μοιράζεται",
"Unused": "Δε χρησιμοποιείται",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
"Upgrading": "Αναβάθμιση",
"Upload Rate": "Ταχύτητα ανεβάσματος",
"Use Compression": "Χρήση συμπίεσης",
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
"Version": "Έκδοση",
"Versions Path": "Φάκελος τήρησης εκδόσεων",

View File

@@ -9,7 +9,6 @@
"Addresses": "Addresses",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Automatic upgrades": "Automatic upgrades",
@@ -17,13 +16,13 @@
"CPU Utilization": "CPU Utilisation",
"Changelog": "Changelog",
"Close": "Close",
"Command": "Command",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression": "Compression",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Delete",
"Device ID": "Device ID",
"Device Identification": "Device Identification",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error",
"External File Versioning": "External File Versioning",
"File Versioning": "File Versioning",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Folder ID": "Folder ID",
"Folder Master": "Folder Master",
@@ -59,6 +57,7 @@
"Global Discovery": "Global Discovery",
"Global Discovery Server": "Global Discovery Server",
"Global State": "Global State",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
"Ignore Permissions": "Ignore Permissions",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last seen": "Last seen",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Maximum Age": "Maximum Age",
@@ -83,6 +84,8 @@
"Notice": "Notice",
"OK": "OK",
"Off": "Off",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out of Sync",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Synchronisation",
"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 is restarting.": "Syncthing is restarting.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"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…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The device ID cannot be blank.": "The device ID cannot be blank.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.",
"The folder ID must be unique.": "The folder ID must be unique.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
"The number of old versions to keep, per file.": "The number of old versions to keep, per file.",
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unused": "Unused",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Upgrade to {{version}}",
"Upgrading": "Upgrading",
"Upload Rate": "Upload Rate",
"Use Compression": "Use Compression",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Version",
"Versions Path": "Versions Path",

View File

@@ -9,7 +9,6 @@
"Addresses": "Addresses",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Automatic upgrades": "Automatic upgrades",
@@ -17,13 +16,13 @@
"CPU Utilization": "CPU Utilization",
"Changelog": "Changelog",
"Close": "Close",
"Command": "Command",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression": "Compression",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Delete",
"Device ID": "Device ID",
"Device Identification": "Device Identification",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error",
"External File Versioning": "External File Versioning",
"File Versioning": "File Versioning",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Folder ID": "Folder ID",
"Folder Master": "Folder Master",
@@ -59,6 +57,7 @@
"Global Discovery": "Global Discovery",
"Global Discovery Server": "Global Discovery Server",
"Global State": "Global State",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
"Ignore Permissions": "Ignore Permissions",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last seen": "Last seen",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Maximum Age": "Maximum Age",
@@ -83,6 +84,8 @@
"Notice": "Notice",
"OK": "OK",
"Off": "Off",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out Of Sync",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support": "Support",
"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 is restarting.": "Syncthing is restarting.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"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…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The device ID cannot be blank.": "The device ID cannot be blank.",
"The device ID to enter here can be found in the \"Edit \u003e Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Edit \u003e Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.",
"The folder ID must be unique.": "The folder ID must be unique.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
"The number of old versions to keep, per file.": "The number of old versions to keep, per file.",
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unused": "Unused",
@@ -158,7 +162,7 @@
"Upgrade To {%version%}": "Upgrade To {{version}}",
"Upgrading": "Upgrading",
"Upload Rate": "Upload Rate",
"Uptime": "Uptime",
"Use Compression": "Use Compression",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Version",
"Versions Path": "Versions Path",

View File

@@ -9,7 +9,6 @@
"Addresses": "Direcciones",
"All Data": "Todos los datos",
"Allow Anonymous Usage Reporting?": "Permitir reporte anónimo de uso?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando exterior maneja el control de versiones. Éste tiene que eliminar el archivo de la carpeta sincronizada.",
"Anonymous Usage Reporting": "Reporte anónimo de uso",
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo introductor será también agregado a este dispositivo.",
"Automatic upgrades": "Actualizaciones automáticas",
@@ -17,13 +16,13 @@
"CPU Utilization": "Uso de CPU",
"Changelog": "Registro de cambios",
"Close": "Cerrar",
"Command": "Comando",
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
"Compression": "Compresión",
"Compression is recommended in most setups.": "La compresión de datos es recomendada para la mayoría de las configuraciones.",
"Connection Error": "Error de conexión",
"Copied from elsewhere": "Copiado desde otra parte.",
"Copied from original": "Copiado del original",
"Copyright © 2015 the following Contributors:": "Derechos de autor © 2015 los siguientes colaboradores:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Derechos de autor © 2014 Jakob Borg y los siguientes colaboradores:",
"Delete": "Suprimir",
"Device ID": "ID del dispositivo",
"Device Identification": "Identificación del dispositivo",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Ingrese las direcciones \"ip:puerto\" separadas por coma, o \"dynamic\" para descubrir automáticamente las direcciones.",
"Enter ignore patterns, one per line.": "Añadir patrones de exclusión, uno por línea.",
"Error": "Error",
"External File Versioning": "Control de versiones externo",
"File Versioning": "Control de versiones",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Los permisos de archivo son ignorados al buscar cambios. Usar el sistemas de archivos FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Lo archivos son movidos al directorio .stversions y renombrados a versiones marcadas por fecha cuando son reemplazados o eliminados por Syncthing,",
"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 devices, but changes made on this device will be sent to the rest of the cluster.": "Los archivos están protegidos frente a los cambios realizados en otros dispositivos, peros los cambios realizados en este dispositivo serán envíados al resto del grupo",
"Folder ID": "ID del repositorio",
"Folder Master": "Repositorio maestro",
@@ -59,6 +57,7 @@
"Global Discovery": "Búsqueda en internet",
"Global Discovery Server": "Servidor global de identificación",
"Global State": "Estado global",
"Idle": "Inactivo",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrones de exclusión",
"Ignore Permissions": "Ignorar permisos",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (es decir, no excluir)",
"Keep Versions": "Conservar versiones",
"Last File Received": "Último archivo recibido",
"Last File Synced": "Último archivo sincronizado.",
"Last seen": "Visto por ultima vez",
"Later": "Más tarde",
"Latest Release": "Última versión",
"Local Discovery": "Búsqueda en red local",
"Local State": "Estado local",
"Maximum Age": "Edad máxima",
@@ -83,6 +84,8 @@
"Notice": "Aviso",
"OK": "OK",
"Off": "Apagado",
"Offline": "Fuera de linea",
"Online": "En linea",
"Out Of Sync": "Fuera de sincronización",
"Out of Sync Items": "Ítems no sincronizados",
"Outgoing Rate Limit (KiB/s)": "Tasa máxima de envío (KiB/s)",
@@ -125,7 +128,9 @@
"Start Browser": "Iniciar navegador",
"Stopped": "Parado",
"Support": "Soporte",
"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:",
@@ -139,7 +144,6 @@
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "La ID del dispositivo a introducir se puede encontrar en la opción de menú \"Edición > Mostrar ID\" en el otro dispositivo. Espacios y guiones son opcionales (ignorados).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "El informe de uso se envía encriptado diariamente. Se utiliza para hacer un seguimiento de plataformas comunes, tamaño de repositorios y versiones de la aplicación. Si el conjunto de datos cambia será notificado mediante este dialogo nuevamente.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "La ID del dispositivo introducida no es válida. Debe ser una cadena de 52 o 56 caracteres consistente en letras y números, con espacios y guiones opcionales.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "El primer argumento es la ruta de la carpeta y el segundo argumento es la ruta relativa de esta carpeta.",
"The folder ID cannot be blank.": "La ID del repositorio no puede estar en blanco.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "La ID del repositorio debe ser un identificador corto (64 caracteres o menos) consistente solamente en letras, números, punto (.), guion (-) y guion bajo (_).",
"The folder ID must be unique.": "La ID del repositorio debe ser única.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El tiempo máximo para mantener una versión (en días, establece en 0 para mantener versiones para siempre).",
"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 path cannot be blank.": "La ruta no puede estar vacía.",
"The rescan interval must be a non-negative number of seconds.": "El intervalo de reescaneo debe ser un número no negativo de segundos.",
"The rescan interval must be at least 5 seconds.": "El intervalo de reescaneo debe ser al menos de 5 segundos.",
"Unknown": "Desconocido",
"Unshared": "No compartido",
"Unused": "No utilizado",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Actualizar a {{version}}",
"Upgrading": "Actualizando",
"Upload Rate": "Tasa de subida",
"Use Compression": "Usar compresión",
"Use HTTPS for GUI": "Usar HTTPS para la GUI",
"Version": "Versión",
"Versions Path": "Ruta de versiones",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adresses",
"All Data": "Toutes les données",
"Allow Anonymous Usage Reporting?": "Autoriser le rapport anonyme de statistiques d'utilisation ?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Une commande externe gère les versions de fichiers. Elle supprime les fichiers dans le dossier synchronisé.",
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
"Any devices configured on an introducer device will be added to this device as well.": "Toute machine ajoutée depuis une machine initiatrice sera aussi ajoutée sur cette machine.",
"Automatic upgrades": "Mises à jour automatiques",
@@ -17,13 +16,13 @@
"CPU Utilization": "Utilisation du CPU",
"Changelog": "Nouveautés",
"Close": "Fermer",
"Command": "Commande",
"Comment, when used at the start of a line": "Commentaire, lorsque utilisé en début de ligne",
"Compression": "Compression",
"Compression is recommended in most setups.": "La compression est recommandée pour la plupart des configurations.",
"Connection Error": "Erreur de connexion",
"Copied from elsewhere": "Copié d'ailleurs",
"Copied from original": "Copié de l'original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 Les contributeurs suivants:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg et les contributeurs suivants :",
"Delete": "Supprimer",
"Device ID": "ID du périphérique",
"Device Identification": "Identification de l'appareil",
@@ -43,10 +42,9 @@
"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.",
"Enter ignore patterns, one per line.": "Entrer les masques de filtrage, un par ligne.",
"Error": "Erreur",
"External File Versioning": "Gestion externe des versions de fichiers",
"File Versioning": "Versions de fichier",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Les bits de permission de fichier sont ignorés lors de la recherche de changements. Utilisé sur les systèmes de fichiers FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés, avec horodatage, dans un dossier .stversions quand ils sont remplacés ou supprimés par Syncthing.",
"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 de type 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 leur remplacement ou suppression par syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Les fichiers sont protégés des changements réalisés sur les autres appareils, mais les changements réalisés sur cet appareil seront transférés au reste du groupe.",
"Folder ID": "ID du répertoire",
"Folder Master": "Répertoire maître",
@@ -59,6 +57,7 @@
"Global Discovery": "Recherche globale",
"Global Discovery Server": "Serveur global de recherche",
"Global State": "État global",
"Idle": "Au repos",
"Ignore": "Ignorer",
"Ignore Patterns": "Modèles à éviter",
"Ignore Permissions": "Ignorer les permissions",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
"Keep Versions": "Conserver les versions",
"Last File Received": "Dernier fichier reçu",
"Last File Synced": "Dernier fichier synchronisé",
"Last seen": "Dernière apparition",
"Later": "Plus tard",
"Latest Release": "Dernière version",
"Local Discovery": "Recherche locale",
"Local State": "État local",
"Maximum Age": "Ancienneté maximum",
@@ -83,6 +84,8 @@
"Notice": "Notification",
"OK": "OK",
"Off": "Éteint",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Non synchronisé",
"Out of Sync Items": "Objets non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Démarrer le navigateur web",
"Stopped": "Arrêté",
"Support": "Aide",
"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 intègre les logiciels suivants (ou des éléments provenant de ces logiciels) :",
"Syncthing is restarting.": "Syncthing est cours de redémarrage.",
"Syncthing is upgrading.": "Syncthing est cours de mise à jour.",
"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 ...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble avoir un problème pour traiter votre demande. S'il vous plaît, rafraîchissez la page ou redémarrer Syncthing si le problème persiste.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble éprouver des difficultés à appliquer votre requête. Si le problème persiste, veuillez rafraîchir votre navigateur ou redémarrer Syncthing.",
"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 device ID cannot be blank.": "L'ID de l'appareil ne peut être vide.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "L'ID de l'appareil à renseigner peut être trouvé dans le menu \"Éditer > Montrer l'ID\" des autres nœuds. Les espaces et les tirets sont optionnels (ils seront ignorés).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "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 les données rapportées sont modifiées cette boite de dialogue vous redemandera votre confirmation.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "L'ID de l'appareil inséré ne semble pas être valide. Il devrait ressembler à une chaîne de 52 ou 56 caractères comprenant des lettres, des chiffres et potentiellement des espaces et des traits d'union.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Le premier paramètre de ligne de commande est le chemin du dossier, et le second est le chemin relatif dans le dossier.",
"The folder ID cannot be blank.": "L'identifiant (ID) du dossier ne peut être vide.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "L'ID du dossier doit être un identifiant court (64 caractères ou moins) comprenant uniquement des lettres, nombres, points (.), traits d'union (-) et tirets bas (_).",
"The folder ID must be unique.": "L'ID du répertoire doit être unique.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Le temps maximum de conservation d'une version (en jours, mettre à 0 pour conserver les versions pour toujours)",
"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 versions doit être numérique, et ne peut pas être vide.",
"The path cannot be blank.": "Le chemin ne peut pas être vide.",
"The rescan interval must be a non-negative number of seconds.": "L'intervalle d'analyse ne doit pas être un nombre négatif de secondes.",
"The rescan interval must be at least 5 seconds.": "L'intervalle de scan doit être d'au minimum 5 secondes.",
"Unknown": "Inconnu",
"Unshared": "Non partagé",
"Unused": "Non utilisé",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Mettre à jour vers {{version}}",
"Upgrading": "Mise à jour de Syncthing",
"Upload Rate": "Débit d'envoi",
"Use Compression": "Utiliser la compression",
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
"Version": "Version",
"Versions Path": "Emplacement des versions",

View File

@@ -7,9 +7,8 @@
"Add new folder?": " ",
"Address": "Cím",
"Addresses": "Címek",
"All Data": "Minden adat",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Engedélyezed a névtelen felhasználási adatok küldését?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Névtelen felhasználási adatok küldése",
"Any devices configured on an introducer device will be added to this device as well.": "Minden eszköz ami a bevezető eszközön lett beállítva hozzá lesz adva ehhez az eszközhöz is.",
"Automatic upgrades": "Automatikus frissítés",
@@ -17,13 +16,13 @@
"CPU Utilization": "Processzor használat",
"Changelog": "Változások",
"Close": "Bezárás",
"Command": "Parancs",
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
"Compression": "Tömörítés",
"Compression": "Compression",
"Compression is recommended in most setups.": "A tömörítés a a legtöbb esetben ajánlott",
"Connection Error": "Kapcsolódási hiba",
"Copied from elsewhere": "Másolva máshonnan",
"Copied from original": "Másolva az eredetiről",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg és az alábbi Közreműködők",
"Delete": "Törlés",
"Device ID": "Eszköz azonosító",
"Device Identification": "Eszköz azonosító",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Add meg a címeket (ip:port formátumban) vesszővel elválasztva vagy add meg a \"dynamic\" szót az a cím automatikus észleléséhez.",
"Enter ignore patterns, one per line.": "Figyelmen kívül hagyáshoz ide írhatod a mintákat, soronként egyet",
"Error": "Hiba",
"External File Versioning": "Külső fájl verziózás",
"File Versioning": "Fájl verziózás",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "A fájl jogosultságok figyelmen kívül hagyása. FAT fájlrendszernél használatos.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "A fájlok időpecsételt verziói a .stversions mappában kerülnek áthelyezésre, amikor felülírásra vagy törlésre kerülnek a Syncthing által.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "A fájlok védve vannak a más eszközökön történt változásokkal szemben, de az ezen az eszközön történt változások érvényesek lesznek a többire.",
"Folder ID": "Mappa azonosító",
"Folder Master": "Központi mappa",
@@ -59,6 +57,7 @@
"Global Discovery": "Globális felfedezés",
"Global Discovery Server": "Globális felfedező szerver",
"Global State": "Globális állapot",
"Idle": "Tétlen",
"Ignore": "Visszautasítás",
"Ignore Patterns": "Figyelmen kívül hagyás",
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
@@ -67,12 +66,14 @@
"Inversion of the given condition (i.e. do not exclude)": "A feltétel ellentéte (pl. ki nem hagyás)",
"Keep Versions": "Megtartott verziók",
"Last File Received": "Utolsó beérkezett fájl",
"Last File Synced": "Utolsó szinkronizált fájl",
"Last seen": "Utoljára látva",
"Later": "Később",
"Latest Release": "Utolsó kiadás",
"Local Discovery": "Helyi felfedezés",
"Local State": "Helyi állapot",
"Maximum Age": "Maximális kor",
"Metadata Only": "Csak metaadatok",
"Metadata Only": "Metadata Only",
"Move to top of queue": "Sor elejére mozgatás",
"Multi level wildcard (matches multiple directory levels)": "Több szintű helyettesítő karakter (több könyvtár szintre érvényesül)",
"Never": "Soha",
@@ -82,7 +83,9 @@
"No File Versioning": "Nincs fájl verziózás",
"Notice": "Megjegyzés",
"OK": "Rendben",
"Off": "Kikapcsolva",
"Off": "Off",
"Offline": "Nincs kapcsolat",
"Online": "Kapcsolódva",
"Out Of Sync": "Nincs szinkronban",
"Out of Sync Items": "Nem szinkronizált elemek",
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
@@ -125,21 +128,22 @@
"Start Browser": "Böngésző indítása",
"Stopped": "Leállítva",
"Support": "Támogatás",
"Support / Forum": "Támogatás / Fórum",
"Sync Protocol Listen Addresses": "Szinkronizációs protokoll címe",
"Synchronization": "Szinkronizálás",
"Syncing": "Szinkronizálás",
"Syncthing has been shut down.": "Syncthing leállítva",
"Syncthing includes the following software or portions thereof:": "Syncthing a következő programokat, vagy komponenseket tartalmazza.",
"Syncthing is restarting.": "Syncthing újraindul",
"Syncthing is upgrading.": "Syncthing frissül",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Úgy tűnik, hogy a Syncthing nem működik, vagy valami probléma van az hálózati kapcsolattal. Újra próbálom...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "A Syncthing hibát észlelt a kérés teljesítése közben. Töltsd újra a böngészőt vagy indítsd újra a Syncthing-et amennyiben a hiba tartósan fennáll.",
"The aggregated statistics are publicly available at {%url%}.": "Az összevont statisztikák nyilvánosan elérhetők a {{url}} címen.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A beállítások elmentésre kerültek, de nem lettek aktiválva. Indítsd újra a Syncthing-et, hogy aktiváld őket.",
"The device ID cannot be blank.": "Az eszköz azonosító nem lehet üres.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Az eszköz azonosító az \"Azonosító mutatása\" ablakban található az eszközökön. A szóközök és kötőjelek opcionálisak (nem lesznek figyelembe véve)",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "A titkosított felhasználási adatok naponta kerülnek küldésre. Arra használjuk őket hogy kövessük a különböző platformokat, mappa méreteket és program verziókat. Amennyiben az elküldésre kerülő adat megváltozik, ez az ablak újra megjelenik.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "A beírt eszköz azonosító nem valódi. Az azonosító 52 vagy 56 karakter hosszú, számokból és betűkből áll, opcionálisan szóközöket és kötőjeleket tartalmaz.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Az első parancssori paraméter a mappa elérési útja, a második a relatív elérési út a mappában.",
"The folder ID cannot be blank.": "A mappa azonosító nem lehet üres.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "A mappa azonosító rövid (maximum 64 karakter), betűkből, számokból és a pont (.), kötőjel (-) és alulvonás (_) karakterekből állhat.",
"The folder ID must be unique.": "A mappa azonosító egyedi kell legyen",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "A verziók megtartásának maximális ideje (napokban, ha 0-t adsz meg örökre megmaradnak).",
"The number of old versions to keep, per file.": "A megtartott régi verziók száma, fájlonként.",
"The number of versions must be a number and cannot be blank.": "A megtartott verziók száma nem lehet üres",
"The path cannot be blank.": "Elérési út nem lehet üres.",
"The rescan interval must be a non-negative number of seconds.": "Az átnézési intervallum nullánál nagyobb másodperc érték kell legyen",
"The rescan interval must be at least 5 seconds.": "Az átnézési intervallumnak legalább 5 másodpercnek kell lennie.",
"Unknown": "Ismeretlen",
"Unshared": "Nincs megosztva",
"Unused": "Nincs használatban",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Frissítés a {{version}} verzióra",
"Upgrading": "Frissítés",
"Upload Rate": "Feltöltési sebesség",
"Use Compression": "Tömörítés használata",
"Use HTTPS for GUI": "HTTPS használata a GUI-hoz",
"Version": "Verzió",
"Versions Path": "Verziók útvonala",

View File

@@ -7,9 +7,8 @@
"Add new folder?": "Aggiungere una nuova cartella?",
"Address": "Indirizzo",
"Addresses": "Indirizzi",
"All Data": "Tutti i dati",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Abilitare Statistiche Anonime di Utilizzo?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
"Any devices configured on an introducer device will be added to this device as well.": "Qualsiasi dispositivo configurato in un introduttore verrà aggiunto anche a questo dispositivo.",
"Automatic upgrades": "Aggiornamenti automatici",
@@ -17,13 +16,13 @@
"CPU Utilization": "Utilizzo CPU",
"Changelog": "Changelog",
"Close": "Chiudi",
"Command": "Command",
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
"Compression": "Compressione",
"Compression": "Compression",
"Compression is recommended in most setups.": "La compressione è raccomandata nella maggior parte delle configurazioni.",
"Connection Error": "Errore di Connessione",
"Copied from elsewhere": "Copiato da qualche altra parte",
"Copied from original": "Copiato dall'originale",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg e i seguenti Collaboratori:",
"Delete": "Elimina",
"Device ID": "ID Dispositivo",
"Device Identification": "Identificazione Dispositivo",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Inserisci gli indirizzi \"ip:porta\" separati da una virgola, altrimenti inserisci \"dynamic\" per effettuare il rilevamento automatico dell'indirizzo.",
"Enter ignore patterns, one per line.": "Inserisci gli schemi di esclusione, uno per riga.",
"Error": "Errore",
"External File Versioning": "Controllo Versione Esterno",
"File Versioning": "Controllo Versione dei File",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Il software evita i bit dei permessi dei file durante il controllo delle modifiche. Utilizzato nei filesystem FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "I file sostituiti o eliminati da syncthing vengono datati e spostati in una cartella .stversions .",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "I file sono protetti dalle modifiche effettuate negli altri dispositivi, ma le modifiche effettuate in questo dispositivo verranno inviate anche al resto del cluster.",
"Folder ID": "ID Cartella",
"Folder Master": "Cartella Principale",
@@ -59,6 +57,7 @@
"Global Discovery": "Individuazione Globale",
"Global Discovery Server": "Server di Ricerca Globale",
"Global State": "Stato Globale",
"Idle": "Inattivo",
"Ignore": "Ignora",
"Ignore Patterns": "Schemi Esclusione File",
"Ignore Permissions": "Ignora Permessi",
@@ -67,13 +66,15 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversione della condizione indicata (ad es. non escludere)",
"Keep Versions": "Versioni Mantenute",
"Last File Received": "Ultimo File Ricevuto",
"Last File Synced": "Ultimo File Sincronizzato",
"Last seen": "Ultima connessione",
"Later": "Più Tardi",
"Latest Release": "Ultima Versione",
"Local Discovery": "Individuazione Locale",
"Local State": "Stato Locale",
"Maximum Age": "Durata Massima",
"Metadata Only": "Solo i metadati",
"Move to top of queue": "Posiziona in cima alla coda",
"Metadata Only": "Metadata Only",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Metacarattere multi-livello (corrisponde alle cartelle e alle sotto-cartelle)",
"Never": "Mai",
"New Device": "Nuovo Dispositivo",
@@ -83,6 +84,8 @@
"Notice": "Avviso",
"OK": "OK",
"Off": "Off",
"Offline": "Non in linea",
"Online": "In linea",
"Out Of Sync": "Non Sincronizzati",
"Out of Sync Items": "Elementi Non Sincronizzati",
"Outgoing Rate Limit (KiB/s)": "Limite Velocità in Uscita (KiB/s)",
@@ -95,7 +98,7 @@
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
"RAM Utilization": "Utilizzo RAM",
"Rescan": "Riscansiona",
"Rescan All": "Riscansiona Tutto",
"Rescan All": "Rescan All",
"Rescan Interval": "Intervallo Scansione",
"Restart": "Riavvia",
"Restart Needed": "Riavvio Necessario",
@@ -125,21 +128,22 @@
"Start Browser": "Avvia Browser",
"Stopped": "Fermato",
"Support": "Supporto",
"Support / Forum": "Supporto / Forum",
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
"Synchronization": "Sincronizzazione",
"Syncing": "Sincronizzazione in corso",
"Syncthing has been shut down.": "Syncthing è stato arrestato.",
"Syncthing includes the following software or portions thereof:": "Syncthing utilizza i seguenti software o porzioni di questi:",
"Syncthing includes the following software or portions thereof:": "Syncthing include i seguenti software o porzioni di questi:",
"Syncthing is restarting.": "Riavvio di Syncthing in corso.",
"Syncthing is upgrading.": "Aggiornamento di Syncthing in corso.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing sembra inattivo, oppure c'è un problema con la tua connessione a Internet. Nuovo tentativo…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Sembra che Syncthing non sia stato in grado di processare il tuo comando. Se il problema persiste, prova a ricaricare la pagina nel tuo navigatore, oppure a rilanciare Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Le statistiche aggregate sono disponibili pubblicamente su {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configurazione è stata salvata ma non attivata. Devi riavviare Syncthing per attivare la nuova configurazione.",
"The device ID cannot be blank.": "L'ID del dispositivo non può essere vuoto.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Trova l'ID nella finestra di dialogo \"Modifica > Mostra ID\" dell'altro dispositivo, poi inseriscilo qui. Gli spazi e i trattini sono opzionali (ignorati).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Quotidianamente il software invia le statistiche di utilizzo in forma criptata. Questi dati riguardano i sistemi operativi utilizzati, le dimensioni delle cartelle e le versioni del software. Se i dati riportati sono cambiati, verrà mostrata di nuovo questa finestra di dialogo.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "L'ID del dispositivo inserito non sembra valido. Dovrebbe essere una stringa di 52 o 56 caratteri costituita da lettere e numeri, con spazi e trattini opzionali.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "L'ID della cartella non può essere vuoto.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "L'ID della cartella dev'essere un identificatore breve (64 caratteri o meno) costituito solamente da lettere, numeri, punti (.), trattini (-) e trattini bassi (_).",
"The folder ID must be unique.": "L'ID della cartella dev'essere unico.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La durata massima di una versione (in giorni, imposta a 0 per mantenere le versioni per sempre).",
"The number of old versions to keep, per file.": "Il numero di vecchie versioni da mantenere, per file.",
"The number of versions must be a number and cannot be blank.": "Il numero di versioni dev'essere un numero e non può essere vuoto.",
"The path cannot be blank.": "Il percorso non può essere vuoto.",
"The rescan interval must be a non-negative number of seconds.": "L'intervallo di scansione deve essere un numero superiore a zero secondi.",
"The rescan interval must be at least 5 seconds.": "L'intervallo di scansione non può essere inferiore a 5 secondi.",
"Unknown": "Sconosciuto",
"Unshared": "Non Condiviso",
"Unused": "Non Utilizzato",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Aggiorna alla {{version}}",
"Upgrading": "Aggiornamento",
"Upload Rate": "Velocità Upload",
"Use Compression": "Utilizza Compressione",
"Use HTTPS for GUI": "Utilizza HTTPS per l'interfaccia grafica",
"Version": "Versione",
"Versions Path": "Percorso Cartella Versioni",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adresai",
"All Data": "Visiems duomenims",
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anoniminė vartojimo ataskaita",
"Any devices configured on an introducer device will be added to this device as well.": "Visi supažindintojo įrenginiai bus pridėti prie jūsų įrenginių sąrašo.",
"Automatic upgrades": "Automatiniai atnaujinimai",
@@ -17,13 +16,13 @@
"CPU Utilization": "Procesoriaus panaudojimas",
"Changelog": "Pasikeitimai",
"Close": "Uždaryti",
"Command": "Command",
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
"Compression": "Kompresija",
"Compression is recommended in most setups.": "Daugumoje atvejų spaudimas rekomenduojamas.",
"Connection Error": "Susijungimo klaida",
"Copied from elsewhere": "Nukopijuota iš betkur",
"Copied from original": "Nukopijuota iš originalo",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Visos teisės saugomos © 2014 Jakob Borg ir šių bendraautor:",
"Delete": "Trinti",
"Device ID": "Įrenginio ID",
"Device Identification": "Įrenginio identifikacija",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Įveskite dvitaškiu atskirtą \"ip:port\" adresą arba žodį \"dynamic\" norėdami gauti adresą automatiškai",
"Enter ignore patterns, one per line.": "Suveskite nepaisomus šablonus, kiekvieną naujoje eilutėje.",
"Error": "Klaida",
"External File Versioning": "External File Versioning",
"File Versioning": "Versijų valdymas",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Nekreipti dėmesio į failų naudojimosi leidimus.\nTaikyti FAT sistemose.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Syncthing programa talpina senesnes versijas .stversions aplanke.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Failai apsaugoti nuo pakeitimų atliktų kituose įrenginiuose, bet pakeitimai šiame įrenginyje bus nusiųsti kitiems.",
"Folder ID": "Aplanko ID",
"Folder Master": "Aplanko vadovas",
@@ -59,6 +57,7 @@
"Global Discovery": "Visuotinis matomumas",
"Global Discovery Server": "Visuotinio matomumo serveris",
"Global State": "Visuotinė būsena",
"Idle": "Laisvas",
"Ignore": "Ignoruoti",
"Ignore Patterns": "Nepaisyti šablonų",
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Apversti sąlygas (pvz.: nenustoti naudoti)",
"Keep Versions": "Saugojamų versijų kiekis",
"Last File Received": "Paskutinis priimtas failas",
"Last File Synced": "Paskutinis gautas failas",
"Last seen": "Paskutinį kartą matytas",
"Later": "Vėliau",
"Latest Release": "Paskutinė versija",
"Local Discovery": "Vietinis matomumas",
"Local State": "Vietinė būsena",
"Maximum Age": "Maksimalus amžius",
@@ -83,6 +84,8 @@
"Notice": "Įspėjimas",
"OK": "Gerai",
"Off": "Netaikoma",
"Offline": "Atsijungęs",
"Online": "Prisijungęs",
"Out Of Sync": "Nesutikrinta",
"Out of Sync Items": "Nesutikrinta",
"Outgoing Rate Limit (KiB/s)": "Išeinančio srauto maksimalus greitis (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Paleisti naršyklę",
"Stopped": "Sustabdyta",
"Support": "Pagalba",
"Support / Forum": "Palaikymas / Diskusijos",
"Sync Protocol Listen Addresses": "Sutapatinimo taisyklių adresas",
"Synchronization": "Sutapatinimas",
"Syncing": "Sutapatinama",
"Syncthing has been shut down.": "Syncthing išjungtas",
"Syncthing includes the following software or portions thereof:": "Syncthing naudoja šias programas ar jų dalis:",
"Syncthing is restarting.": "Syncthing perleidžiamas",
"Syncthing is upgrading.": "Syncthing atsinaujina.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing išjungta arba problemos su Interneto ryšių. Bandoma iš naujo...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing turi problemų su jūsų užklausa. Perkraukite puslapį arba perkraukite Syncthing jei problema nedings.",
"The aggregated statistics are publicly available at {%url%}.": "Naudojimosi ataskaitą galite peržiūrėti adresu: {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Nauji nustatymai išsaugoti, bet neaktyvuoti. Perleiskite Syncthing programą iš naujo norėdami įgalinti naujus nustatymus.",
"The device ID cannot be blank.": "Įrenginio ID negali būti tuščias.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Įrenginio ID, kurį čia reikia įvesti, gali būti rastas „Redaguoti > Rodyti ID“ dialoge kitame įrenginyje. Tarpai ir brūkšneliai nebūtini (ignoruojami).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Kas dieną siunčiama šifruota naudojimo ataskaita. Ji naudojama sekti, kokios platformos naudojamos, aplankų dydžius ir programų versijas. Jei siunčiamų duomenų turinys pasikeis, šis dialogas bus parodytas iš naujo.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Įvestas neteisingas įrenginio ID. Turi būti 52 ar 56 simbolių eilutė su raidėmis ir skaičiais kuriuos galima atskirti tarpu arba brūkšneliu.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "Aplanko ID negali būti tuščias.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Aplanko vardas negali būti ilgesnis nei 64 simboliai. Galima naudoti tik raides ir skaičius bet tašką (.), brūkšnelį (-) ir pabraukimą (_).",
"The folder ID must be unique.": "Aplanko ID turi būti unikalus.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimalus laikas kurį bus saugojama versija (dienomis, nustatykite 0 norėdami saugoti amžinai).",
"The number of old versions to keep, per file.": "Kiek failo versijų saugoti.",
"The number of versions must be a number and cannot be blank.": "Versijų skaičius turi būti skaitmuo ir negali būti tuščias laukelis.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "Nuskaitymo dažnis negali būti neigiamas skaičius.",
"The rescan interval must be at least 5 seconds.": "Nuskaityti galima nedažniau nei kas 5 sekundes.",
"Unknown": "Nežinoma",
"Unshared": "Nesidalinama",
"Unused": "Nenaudojamas",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Atnaujinti į {{version}}",
"Upgrading": "Atnaujinama",
"Upload Rate": "Išsiuntimo greitis",
"Use Compression": "Naudoti suspaudimą",
"Use HTTPS for GUI": "Valdymo skydeliui naudoti saugų ryšį ",
"Version": "Versija",
"Versions Path": "Kelias iki versijos",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adresser",
"All Data": "Alle data",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonym Innsamling Av Brukerdata",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurert på en introduksjonsenhet vil også bli lagt til denne enheten.",
"Automatic upgrades": "Automatiske oppdateringer",
@@ -17,13 +16,13 @@
"CPU Utilization": "CPU-utnyttelse",
"Changelog": "Endringslog",
"Close": "Lukk",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
"Compression": "Komprimering",
"Compression is recommended in most setups.": "Komprimering er anbefalt i de fleste tilfeller.",
"Connection Error": "Tilkoblingsfeil",
"Copied from elsewhere": "Kopiert fra et annet sted",
"Copied from original": "Kopiert fra original",
"Copyright © 2015 the following Contributors:": "Kopirett © 2015 de følgende bidragsytere:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg og følgende Bidragsytere:",
"Delete": "Slett",
"Device ID": "Enhet ID",
"Device Identification": "Enhetskjennemerke",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": " Skriv inn kommaseparerte \"ip:port\"-adresser, eller \"dynamic\" for å automatisk finne adressen.",
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelates, ett per linje.",
"Error": "Feilmelding",
"External File Versioning": "Ekstern versjonskontroll",
"File Versioning": "Versjonskontroll",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Ignorer bit som styrer filtilgang når en ser etter endringer. Bruk på FAT-filsystem.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Filer blir flyttet til tidsstemplede versjoner i en .stversions-mappe når de blir erstattet eller slettet av syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskyttet mot endringer som er gjort på andre enheter, men endringer som er gjort på denne enheten blir sendt til resten av gruppen.",
"Folder ID": "Mappe ID",
"Folder Master": "Styrende Mappe",
@@ -59,6 +57,7 @@
"Global Discovery": "Global Søking",
"Global Discovery Server": "Global Søkemotor",
"Global State": "Global Tilstand",
"Idle": "Pause",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatelsesmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikke ekskluder)",
"Keep Versions": "Behold Versjoner",
"Last File Received": "Sist Mottatte Fil",
"Last File Synced": "Sist Synkroniserte Fil",
"Last seen": "Sist sett",
"Later": "Senere",
"Latest Release": "Nyeste Versjon",
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
@@ -83,6 +84,8 @@
"Notice": "Merknad",
"OK": "OK",
"Off": "Av",
"Offline": "Frakobla",
"Online": "Tilkobla",
"Out Of Sync": "Ikke Synkronisert",
"Out of Sync Items": "Ikke Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgående Hastighetsbegrensning (KiB/s)",
@@ -125,7 +128,9 @@
"Start Browser": "Start Nettleser",
"Stopped": "Stoppa",
"Support": "Brukerstøtte",
"Support / Forum": "Brukerstøtte / Forum",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Synchronization": "Synkronisering",
"Syncing": "Synkroniserer",
"Syncthing has been shut down.": "Syncthing har blitt slått av.",
"Syncthing includes the following software or portions thereof:": "Syncthing inkluderer helt eller delvis følgende programvare:",
@@ -139,7 +144,6 @@
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhet-IDen lagt til her kan hentes fram via \"Rediger > Vis ID\"-dialogboksen på den andre enheten. Mellomrom og bindestrek er valgfritt (blir ignorert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Kryptert informasjon om bruken av programmet blir gjort daglig. Dette blir brukt til å følge med på vanlig brukte systemoppsett, størrelser på mapper, og versjoner av programmet. Om datasettet endrer seg vil denne dialogboksen dukke opp og du vil bli bedt om å godkjenne dette.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "IDen for denne enheten er ikke godkjent. Det bør være 52 eller 56 tegn bestående av bokstaver og tall, valgfritt med mellomrom og bindestrek.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "Mappe-ID kan ikke være tom.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Mappe-IDen må være et kort kjennemerke (64 tegn eller mindre) bestående kun av bokstaver, tall og tegnene punktum (.), mellomrom (-) og strek (_).",
"The folder ID must be unique.": "Mappe-ID må være unik.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimal tid å beholde en versjon (i dager, sett til 0 for å beholde versjoner på ubegrenset tid).",
"The number of old versions to keep, per file.": "Antall gamle versjoner å beholde, per fil.",
"The number of versions must be a number and cannot be blank.": "Antall versjoner må være et tall og kan ikke være tomt.",
"The path cannot be blank.": "Plasseringen kan ikke være tom.",
"The rescan interval must be a non-negative number of seconds.": "Antall sekund i skanneintervallet kan ikke være negativt.",
"The rescan interval must be at least 5 seconds.": "Skanneintervallet må være minst 5 sekund.",
"Unknown": "Ukjent",
"Unshared": "Ikke delt",
"Unused": "Ikke i bruk",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrading": "Oppgraderer",
"Upload Rate": "Opplastingsrate",
"Use Compression": "Bruk Komprimering",
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
"Version": "Versjon",
"Versions Path": "Plassering Av Versjoner",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adressen",
"All Data": "Alle Gegevens",
"Allow Anonymous Usage Reporting?": "Bijhouden van anonieme gebruikers statistieken toestaan?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Bijhouden anonieme gebruikers statistieken",
"Any devices configured on an introducer device will be added to this device as well.": "Toestellen geconfigureerd op een introductie toestel zullen ook aan dit toestel worden toegevoegd.",
"Automatic upgrades": "Automatisch bijwerken",
@@ -17,13 +16,13 @@
"CPU Utilization": "CPU Gebruik",
"Changelog": "Logboek",
"Close": "Sluiten",
"Command": "Commando",
"Comment, when used at the start of a line": "Commentaar, indien gebruikt aan het begin van de lijn",
"Compression": "Compressie",
"Compression is recommended in most setups.": "Gegevenscompressie is aan te raden in de meeste situaties.",
"Connection Error": "Verbindingsfout",
"Copied from elsewhere": "Van elders gekopieerd",
"Copied from original": "Gekopieerd van het origineel",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg en de onderstaande bijdragers:",
"Delete": "Verwijderen",
"Device ID": "Apparaat ID",
"Device Identification": "Apparaat identificatie",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Geef, gescheiden door komma's, \"ip:port\" adressen of \"dynamic\" voor het automatische vinden van de addressen.",
"Enter ignore patterns, one per line.": "Geef te negeren patronen, één per regel.",
"Error": "Fout",
"External File Versioning": "External File Versioning",
"File Versioning": "Versiebeheer",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Bestands permissiebits worden genegeerd wanneer naar veranderingen wordt gekeken. Gebruik dit op FAT bestandsystemen",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Bestanden worden naar de .stversions map verplaatst met een tijdsaanduiding, wanneer ze aangepast of verwijderd worden door syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Bestanden zijn beschermt tegen aanpassingen gemaakt door andere apparaten maar aanpassingen op dit apparaat worden doorgestuurd naar de rest van de cluster.",
"Folder ID": "Folder ID",
"Folder Master": "Hoofdfolder",
@@ -59,6 +57,7 @@
"Global Discovery": "Globaal zoeken",
"Global Discovery Server": "Globale zoekserver",
"Global State": "Globale status",
"Idle": "Inactief",
"Ignore": "Negeren",
"Ignore Patterns": "Te negeren patronen",
"Ignore Permissions": "Rechten negeren",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversie van de gegeven voorwaarde (bv. niet uitsluiten)",
"Keep Versions": "Versies behouden",
"Last File Received": "Laatste ontvangen bestand",
"Last File Synced": "Laatste Gesynchroniseerde Bestand",
"Last seen": "Laatst gezien op",
"Later": "Later",
"Latest Release": "Laatste uitgave",
"Local Discovery": "Lokaal zoeken",
"Local State": "Lokale status",
"Maximum Age": "Maximum leeftijd",
@@ -83,6 +84,8 @@
"Notice": "Notificatie",
"OK": "OK",
"Off": "Uit",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Niet gesynchroniseerd",
"Out of Sync Items": "Niet gesynchroniseerde objecten",
"Outgoing Rate Limit (KiB/s)": "Uitgaande snelheidslimiet (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Start browser",
"Stopped": "Gestopt",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Synchronisatie protocol luister adres",
"Synchronization": "Synchronisatie",
"Syncing": "Aan het synchroniseren",
"Syncthing has been shut down.": "Syncthing is afgesloten",
"Syncthing includes the following software or portions thereof:": "De volgende software of delen daarvan zijn onderdeel van syncthing:",
"Syncthing is restarting.": "Syncthing is aan het herstarten.",
"Syncthing is upgrading.": "Syncthing is aan het upgraden.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing lijkt afgesloten te zijn, of er is een verbindingsprobleem met het internet. Nieuwe poging....",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing heeft problemen bij het verwerken van je vraag. Gelieve de pagina opnieuw te laden of Syncthing te herstarten wanneer het probleem blijft opduiken.",
"The aggregated statistics are publicly available at {%url%}.": "The verzamelde statistieken zijn publiek beschikbaar op {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "De configuratie is opslagen maar nog niet actief. Syncthing moet opnieuw opgestart worden om de nieuwe configuratie te activeren.",
"The device ID cannot be blank.": "Het toestel ID mag niet leeg zijn.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Het verwachte toestel ID kan teruggevonden worden in het \"Aanpassen > Toon ID\" scherm op het andere toestel. Spaties en streepjes zijn facultatief (worden genegeerd).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Het versleutelde gebruiksrapport wordt dagelijks opgestuurd en wordt gebruikt om de verschillende platformen, folder groottes en versies op te volgen. Als de reeks gegevens wijzigt zal opnieuw toestemming gevraagd worden.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Dit toestel ID lijkt ongeldig. Het toestel ID bestaat uit 52 of 56 letters en nummers met facultatieve spaties en streepjes.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "De folder ID mag niet leeg zijn.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "De folder ID mag maximaal 64 tekens lang zijn en bestaat enkel uit letters, nummers, punten (.), streepjes (-) en onderstrepingstekens (_).",
"The folder ID must be unique.": "De folder ID moet uniek zijn.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "De maximale tijdsduur om een versie te bewaren (in dagen, gebruik 0 om versies voor altijd te bewaren).",
"The number of old versions to keep, per file.": "Het aantal versies dat bewaard moet worden per file.",
"The number of versions must be a number and cannot be blank.": "Het aantal nummers moet een getal zijn en mag niet leeg blijven.",
"The path cannot be blank.": "U dient een locatie in te voeren.",
"The rescan interval must be a non-negative number of seconds.": "De scanfrequentie moet een positief getal in seconden zijn.",
"The rescan interval must be at least 5 seconds.": "De scanfrequentie moet minimaal 5 seconden zijn.",
"Unknown": "Onbekend",
"Unshared": "Niet gedeeld",
"Unused": "Ongebruikt",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Upgrade naar {{version}}",
"Upgrading": "Bezig met upgrade",
"Upload Rate": "Upload snelheid",
"Use Compression": "Compressie gebruiken",
"Use HTTPS for GUI": "Gebruik HTTPS voor de GUI",
"Version": "Versie",
"Versions Path": "Locatie versies",

View File

@@ -4,35 +4,34 @@
"Add": "Legg til",
"Add Device": "Legg Til Eining",
"Add Folder": "Legg Til Mappe",
"Add new folder?": "Leggja til ny mappe?",
"Add new folder?": "Legg til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"All Data": "Alle dataa",
"Allow Anonymous Usage Reporting?": "Tillata anonymisert bruksrapportering?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymisert bruksrapportering",
"Any devices configured on an introducer device will be added to this device as well.": "Einingar konfigurert på ei introduksjonseining vil òg verta lagt til denne eininga.",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukardata?",
"Anonymous Usage Reporting": "Tillat Anonym Innsamling Av Brukardata",
"Any devices configured on an introducer device will be added to this device as well.": "Einingar konfigurert på ein introduksjonseining vil òg verte lagt til denne eininga.",
"Automatic upgrades": "Automatiske oppdateringar",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnytting",
"Changelog": "Endringslogg",
"Changelog": "Endringslog",
"Close": "Lukk",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentar, når brukt i starten av linja",
"Compression": "Komprimering",
"Comment, when used at the start of a line": "Kommentar, når det vert brukt i starten av ei linje.",
"Compression": "Compression",
"Compression is recommended in most setups.": "Komprimering er tilrådd i dei fleste høve.",
"Connection Error": "Tilkoplingsfeil",
"Copied from elsewhere": "Kopiert frå ein annan stad",
"Copied from original": "Kopiert frå originalen",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copied from original": "Kopiert frå original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg og følgjande Bidragsytarar:",
"Delete": "Slett",
"Device ID": "Eining ID",
"Device Identification": "Einingskjennemerke",
"Device Name": "Namn På Eining",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Eininga {{device}} ({{address}}) vil kopla seg til. Vil du leggja ho til?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Eining {{device}} ({{address}}) vil kople seg til. Legg til eining?",
"Devices": "Einingar",
"Disconnected": "Fråkopla",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsfart",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Lasta ned",
"Downloading": "Lastar ned",
"Edit": "Rediger",
@@ -43,11 +42,10 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn \"ip:port\"-adresser med komma mellom kvar adresse, eller \"dynamic\" for å automatisk søkja opp adressa.",
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelatast, eitt per linje.",
"Error": "Feilmelding",
"External File Versioning": "Ekstern filutgåvehandtering",
"File Versioning": "Filutgåvekontroll",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskytta mot endringar gjort på andre einingar, men endringar gjort på denne eininga vert sende til resten av klyngja.",
"File Versioning": "Versjonskontroll",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Ignorer bit som styrer filtilgang når ein ser etter endringar. Bruk på FAT-filsystem.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Filer vert flytta til tidsstempla versjonar i ei .stversions-mappe når dei vert erstatta eller sletta av syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer er beskytta mot endringar gjort på andre einingar, men endringar gjort på denne eininga vert sendt til resten av gruppa.",
"Folder ID": "Mappe ID",
"Folder Master": "Styrande Mappe",
"Folder Path": "Mappeplassering",
@@ -56,46 +54,51 @@
"GUI Authentication User": "GUI Brukar",
"GUI Listen Addresses": "GUI Lytteadresse",
"Generate": "Generer",
"Global Discovery": "Global søking",
"Global Discovery Server": "Global søkjetenar",
"Global Discovery": "Global Søking",
"Global Discovery Server": "Global Søkjemotor",
"Global State": "Global Tilstand",
"Idle": "Pause",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatingsmønster",
"Ignore Permissions": "Ignorer tilgangar",
"Incoming Rate Limit (KiB/s)": "Innkomande hastigheitsgrense (KiB/s)",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkomande Hastigheitsgrense (KiB/s)",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Det motsette av den gitte tilstanden (dvs. ekskluder ikkje)",
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikkje ekskluder)",
"Keep Versions": "Behald Versjonar",
"Last File Received": "Siste mottatte fila",
"Last File Received": "Sist Mottatte Fil",
"Last File Synced": "Sist Synkroniserte Fil",
"Last seen": "Sist sett",
"Later": "Seinare",
"Local Discovery": "Lokal oppdaging",
"Latest Release": "Nyaste Versjon",
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Metadata Only": "Berre metadata",
"Move to top of queue": "Flytt øvst i køen",
"Multi level wildcard (matches multiple directory levels)": "Fleirnivå-jokerteikn (søkjer på fleire mappenivå)",
"Metadata Only": "Metadata Only",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søkjer på fleire mappenivå)",
"Never": "Aldri",
"New Device": "Ny eining",
"New Folder": "Ny mappe",
"New Device": "Ny Eining",
"New Folder": "Ny Mappe",
"No": "Nei",
"No File Versioning": "Ingen versjonskontroll",
"No File Versioning": "Ingen Versjonskontroll",
"Notice": "Merknad",
"OK": "OK",
"Off": "Av",
"Out Of Sync": "Ikkje synkronisert",
"Out of Sync Items": "Ikkje-synkroniserte element",
"Outgoing Rate Limit (KiB/s)": "Utgåande hastigheitsgrense (KiB/s)",
"Override Changes": "Overstyr endringar",
"Off": "Off",
"Offline": "Fråkopla",
"Online": "Tilkopla",
"Out Of Sync": "Ikkje Synkronisert",
"Out of Sync Items": "Ikkje Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgåande Hastigheitsgrense (KiB/s)",
"Override Changes": "Overstyr Endringar",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Plasseringa av mappa på datamaskinen. Vert oppretta om ho ikkje finst. Krøllstrekteiknet (~) kan brukast som forkorting for",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Plasseringa for lagra versjonar (la denne vera tom for å bruka standard .stversions-mappa i mappa).",
"Please wait": "Gjer vel og vent",
"Please wait": "Ver venleg og vent",
"Preview": "Førehandsvisning",
"Preview Usage Report": "Førehandsvis bruksrapporten",
"Preview Usage Report": "Førehandsvis Datainnsamling",
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "Minnebruk",
"RAM Utilization": "RAM-utnytting",
"Rescan": "Skann På Ny",
"Rescan All": "Skann alle på nytt",
"Rescan All": "Rescan All",
"Rescan Interval": "Skanneintervall",
"Restart": "Omstart",
"Restart Needed": "Omstart Trengs",
@@ -107,66 +110,68 @@
"Select the folders to share with this device.": "Vel mappene du vil dela med denne eininga.",
"Settings": "Innstillingar",
"Share": "Del",
"Share Folder": "Del mappe",
"Share Folders With Device": "Del mapper med eininga",
"Share Folder": "Del Mappe",
"Share Folders With Device": "Del Mapper Med Eining",
"Share With Devices": "Del Med Einingar",
"Share this folder?": "Dela denne mappa?",
"Share this folder?": "Del denne mappa?",
"Shared With": "Delt Med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappa. Må vera det same på alle einingane i klyngja.",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappa. Må vera det same på alle einingar i ei gruppe.",
"Show ID": "Vis ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vist i staden for einings-ID-en i klyngjestatusen. Vil verta kringkasta til dei andre einingane som eit valfritt standardnamn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i staden for mappe-ID-en i klyngjestatuses. Vil verta oppdatert til namnet eininga kringkastar dersom tomt.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vis i staden for Eining ID i gruppestatus. Vil verta kringkasta til andre einingar som eit valfritt standardnamn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist i staden for Mappe-ID i gruppestatus. Vil verta oppdatert til namnet eininga kringkastar dersom tomt.",
"Shutdown": "Slå Av",
"Shutdown Complete": "Slått av",
"Simple File Versioning": "Enkel versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivå-jokerteikn (søkjer berre i éi mappe)",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Enkel Versjonskontroll",
"Single level wildcard (matches within a directory only)": "Enkeltnivåsøk (søkjer kun i ei mappe)",
"Source Code": "Kildekode",
"Staggered File Versioning": "Forskuva versjonskontroll",
"Staggered File Versioning": "Forskyvd Versjonskontroll",
"Start Browser": "Start Nettlesar",
"Stopped": "Stoppa",
"Support": "Brukarstøtte",
"Support / Forum": "Brukarstøtte / Forum",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Synchronization": "Synkronisering",
"Syncing": "Synkroniserer",
"Syncthing has been shut down.": "Syncthing har blitt slått av.",
"Syncthing includes the following software or portions thereof:": "Syncthing inkluderer føljande programvare heilt eller delvis:",
"Syncthing is restarting.": "Syncthing startar på ny.",
"Syncthing is upgrading.": "Syncthing oppgraderer.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ut til å vera nede, eller så er det eit problem med nettilkoplinga di. Prøvar på ny …",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Samla statistikk er opent tilgjengeleg på {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Instillingane har blitt lagra men ikkje aktivert. Syncthing må starta på ny for å aktivera dei nye instillingane.",
"The device ID cannot be blank.": "Eining ID kan ikkje vera tom.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Einings-ID-en du skal nytta her finn du i meldingsvindauget \"Rediger > Vis ID\" på den andre eininga. Mellomrom og bindestrek er valfrie (vert ignorert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterte bruksrapporten vert send dagleg. Han vert nytta til å spora vanlege plattformer, mappestorleikar og programutgåvene. Om datasettet endrar seg, vil dette meldingsvindauget dukka opp att og du vil verta beden om å godkjenna det.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Einings-ID-en er ikkje gyldig. Han må vera 52 eller 56 teikn og vera samansett av bokstavar og tal med valfrie mellomrom og bindestrekar.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den første kommandolinjeparameteren er mappebana og den andre syner den relative bana i mappa.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Eining-IDen lagt til her kan hentast fram via \"Rediger > Vis ID\"-dialogboksen på den andre eininga. Mellomrom og bindestrek er valfritt (vert ignorert).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Kryptert informasjon om bruken av programmet vert gjort dagleg. Dette vert brukt til å følgja med på vanleg brukte systemoppsett, storleikar på mapper, og versjonar av programmet. Om datasettet endrar seg vil denne dialogboksen dukka opp og du vil bli bedt om å godkjenna dette.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "IDen for denne eininga er ikkje godkjend. Det bør vera 52 eller 56 teikn samansett av bokstavar og tal, valfritt med mellomrom og bindestrek.",
"The folder ID cannot be blank.": "Mappe ID kan ikkje vera tom.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Mappe-IDen må vera eit kort kjennemerke (64 teikn eller mindre) samansett kun av bokstavar, tal og teikna punktum (.), mellomrom (-) og strek (_).",
"The folder ID must be unique.": "Mappe ID må vera unik.",
"The folder path cannot be blank.": "Mappeplasseringa kan ikkje vera tom.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Desse intervalla vert nytta: den fyrste timen vert ei utgåve lagra kvart 30. sekund, den fyrste dagen vert ei utgåve lagra kvar time, dei fyrste 30 dagane vert ei utgåve lagra kvar dag, og inntil høgaste alderen vert ei utgåve lagra kvar veke.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Fylgjande intervall vert brukt: den fyrste timen vert ein versjon lagra kvart 30. sekund, den fyrste dagen vert ein versjon lagra kvar time, dei fyrste 30 dagane vert ein versjon lagra kvar dag, og inntil høgaste levetid vert ein versjon lagra kvar uke.",
"The maximum age must be a number and cannot be blank.": "Maksimal levetid må vera eit tal og kan ikkje vera tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Høgaste tidsrom å behalda ei utgåve (i dagar, set til 0 for å behalda versjonane for alltid).",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksimalt tidsrom å behalda ein versjon (i dagar, set til 0 for å behalda versjonar for ubegrensa tid.)",
"The number of old versions to keep, per file.": "Tal på gamle versjonar ein skal behalda, per fil.",
"The number of versions must be a number and cannot be blank.": "Tal på versjonar må vera eit tal og kan ikkje vera tomt.",
"The path cannot be blank.": "Bana kan ikkje vera tom.",
"The rescan interval must be a non-negative number of seconds.": "Talet på sekund i skanneintervallet kan ikkje vera negativt.",
"The rescan interval must be at least 5 seconds.": "Skanneintervallet må vera minst 5 sekund.",
"Unknown": "Ukjent",
"Unshared": "Ikkje delt",
"Unused": "Ubrukt",
"Up to Date": "Oppdatert",
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrading": "Oppgraderer",
"Upload Rate": "Opplastingsfart",
"Use HTTPS for GUI": "Bruk HTTPS ved grafisk grensesnitt",
"Upload Rate": "Opplastingsrate",
"Use Compression": "Bruk Komprimering",
"Use HTTPS for GUI": "Bruk HTTPS for GUI",
"Version": "Versjon",
"Versions Path": "Utgåvebane",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Utgåver vert automatisk sletta når maksimal levetid er nådd eller når det høgaste tillate talet på filer innan eit intervall vert overskride.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Hugs at når ei ny eining vert lagt til må ho òg leggjast til på andre sida.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Hugs at når ei ny mappe vert lagt til, vert mappe-ID-en brukt til å binda saman mappene mellom einingane. Det er skilnad på store og små bokstavar, så ID-ane må vera identiske på alle einingane.",
"Versions Path": "Plassering Av Versjonar",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versjonar vert automatisk sletta når maksimal levetid er nådd eller når det tillatte talet på filer er overstige.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Merk at når ein ny eining vert lagt til må denne òg leggast til på andre sida.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Når ei ny mappe vert lagt til, hugs at Mappe-ID vert brukt til å binda saman mapper mellom einingar. Det er skilnad på store og små bokstavar, så IDane må vera identiske på alle einingane.",
"Yes": "Ja",
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønskjer å dela mappa \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vil dela mappa \"{{folder}}\"."
}

View File

@@ -7,9 +7,8 @@
"Add new folder?": "Dodać nowy folder?",
"Address": "Adres",
"Addresses": "Adresy",
"All Data": "Wszystkie dane",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Zezwalaj na anonimowe statystyki użycia",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonimowe statystyki użycia",
"Any devices configured on an introducer device will be added to this device as well.": "Wszystkie urządzenia skonfigurowane na urządzeniu wprowadzającym zostaną dodane także do tego urządzenia.",
"Automatic upgrades": "Automatyczne aktualizacje",
@@ -17,18 +16,18 @@
"CPU Utilization": "Użycie CPU",
"Changelog": "Historia zmian",
"Close": "Zamknij",
"Command": "Command",
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
"Compression": "Kompresja",
"Compression": "Compression",
"Compression is recommended in most setups.": "Kompresja jest zalecana w większości przypadków",
"Connection Error": "Błąd połączenia",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Skopiowane z oryginału",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copied from original": "Copied from original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg i następujący współautorzy:",
"Delete": "Usuń",
"Device ID": "ID urządzenia",
"Device Identification": "Identyfikator urządzenia",
"Device Name": "Nazwa urządzenia",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Urządzenie {{device}} ({{address}}) chce się połączyć. Zezwolić?",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Urządzenia",
"Disconnected": "Rozłączony",
"Documentation": "Dokumentacja",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Wprowadź adresy oddzielone przecinkiem \"ip:port\", lub wpisz \"dynamic\" w celu wykrycia adresu. ",
"Enter ignore patterns, one per line.": "Wprowadz wzorce ignorowania, jeden w każdej linii.",
"Error": "Błąd",
"External File Versioning": "External File Versioning",
"File Versioning": "Wersjonowanie plików",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Uprawnienia plików są ignorowane przy poszukiwaniu zmian. Używaj w systemie plików FAT",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "W momencie zmiany lub usuwania pliki są przenoszone do oznaczonych datą wersji wewnątrz folderu .stversions",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Pliki są zabezpieczone przed zmianami na innym urządzeniu, jednak zmiany w tym urządzeniu będą wysłane do reszty.",
"Folder ID": "ID folderu",
"Folder Master": "Główny folder",
@@ -59,7 +57,8 @@
"Global Discovery": "Globalne odnajdywanie",
"Global Discovery Server": "Globalny serwer rozgłoszeniowy",
"Global State": "Status globalny",
"Ignore": "Ignoruj",
"Idle": "Bezczynny",
"Ignore": "Ignore",
"Ignore Patterns": "Wzorce ignorowania",
"Ignore Permissions": "Ignoruj uprawnienia",
"Incoming Rate Limit (KiB/s)": "Ograniczenie prędkości odbierania (KiB/s)",
@@ -67,13 +66,15 @@
"Inversion of the given condition (i.e. do not exclude)": "Odwrócenie podanego wzorca (np. nie wykluczaj)",
"Keep Versions": "Zachowuj wersje",
"Last File Received": "Ostatni otrzymany plik",
"Last File Synced": "Ostatni zsynchronizowany plik",
"Last seen": "Ostatnio widziany",
"Later": "Później",
"Later": "Later",
"Latest Release": "Najnowsza wersja",
"Local Discovery": "Lokalne odnajdywanie",
"Local State": "Status lokalny",
"Maximum Age": "Maksymalny wiek",
"Metadata Only": "Tylko metadane",
"Move to top of queue": "Przenieś na początek kolejki",
"Metadata Only": "Metadata Only",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Wieloznaczność na poziomie katalogów i plików (uwzględnia nazwy folderów i plików)",
"Never": "Nigdy",
"New Device": "Nowe urządzenie",
@@ -82,9 +83,11 @@
"No File Versioning": "Bez wersjonowania pliku",
"Notice": "Wskazówka",
"OK": "OK",
"Off": "Wyłącz",
"Off": "Off",
"Offline": "Rozłączony",
"Online": "Połączony",
"Out Of Sync": "Niezsynchronizowane",
"Out of Sync Items": "Niezsynchronizowane pliki",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Ograniczenie prędkości wysyłania (KiB/s)",
"Override Changes": "Nadpisz zmiany",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do lokalnego folderu. Zostanie utworzona jeżeli nie istnieje.\nZnak tyldy (~) może zostać użyty jako skrót do",
@@ -95,29 +98,29 @@
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"RAM Utilization": "Użycie pamięci RAM",
"Rescan": "Skanuj ponownie",
"Rescan All": "Skanuj wszystko ponownie",
"Rescan All": "Rescan All",
"Rescan Interval": "Interwał skanowania",
"Restart": "Uruchom ponownie",
"Restart Needed": "Wymagane ponowne uruchomienie",
"Restarting": "Uruchamianie ponowne",
"Reused": "Ponownie użyte",
"Reused": "Reused",
"Save": "Zapisz",
"Scanning": "Skanowanie",
"Select the devices to share this folder with.": "Wybierz urządzenie, któremu udostępnić folder.",
"Select the folders to share with this device.": "Wybierz foldery do współdzielenia z tym urządzeniem.",
"Settings": "Ustawienia",
"Share": "Udostępnij",
"Share Folder": "Udostępnij folder",
"Share Folders With Device": "Udostępnij foldery między urządzeniami",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share With Devices": "Udostępnij dla urządzenia",
"Share this folder?": "Udostępnić ten folder?",
"Share this folder?": "Share this folder?",
"Shared With": "Współdzielony z",
"Short identifier for the folder. Must be the same on all cluster devices.": "Krótki identyfikator folderu. Musi być taki sam na wszystkich urządzeniach.",
"Show ID": "Pokaż ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Pokazane w statusie zamiast ID urządzenia.Zostanie wysłane do innych urządzeń jako opcjonalna domyślna nazwa.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Pokazane w statusie zamiast ID urządzenia. Zostanie zaktualizowane do nazwy urządzenia jeżeli pozostanie puste.",
"Shutdown": "Wyłącz",
"Shutdown Complete": "Wyłączanie ukończone",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Proste wersjonowanie pliku",
"Single level wildcard (matches within a directory only)": "Wieloznaczność na poziomie plików (uwzględnia nazwy plików)",
"Source Code": "Kod źródłowy",
@@ -125,21 +128,22 @@
"Start Browser": "Uruchom przeglądarkę",
"Stopped": "Zatrzymany",
"Support": "Wsparcie",
"Support / Forum": "Wsparcie / Forum",
"Sync Protocol Listen Addresses": "Adres nasłuchu protokołu synchronizacji",
"Synchronization": "Synchronizacja",
"Syncing": "Synchronizowanie",
"Syncthing has been shut down.": "Syncthing został wyłączony",
"Syncthing includes the following software or portions thereof:": "Syncthing zawiera następujące oprogramowanie lub ich częśći:",
"Syncthing is restarting.": "Restart Syncthing",
"Syncthing is upgrading.": "Aktualizowanie Syncthing",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing wydaje się być wyłączony lub jest problem z twoim połączeniem internetowym. Próbuje ponownie...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Zebrane statystyki są publicznie dostępna pod adresem {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfiguracja została zapisana lecz nie jest aktywna. Syncthing musi zostać zrestartowany aby aktywować nową konfiguracje.",
"The device ID cannot be blank.": "ID urządzenia nie może być puste.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID urządzenia można znaleźć w \"Edytuj -> Pokaż ID\" na zdalnym urządzeniu.\nOdstępy i myślniki są opcjonalne (ignorowane)",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Zaszyfrowane raporty użycia są wysyłane codziennie. Są one używane w celach statystycznych platform, rozmiarów katalogów i wersji programu. Jeżeli zgłaszane dane ulegną zmianie, ponownie wyświetli się ta informacja.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Wprowadzone ID urządzenia wygląda na niepoprawne. Musi zawierać 52 lub 56 znaków składających się z liter i cyfr. Odstępy i myślniki są opcjonalne.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "ID folderu nie może być puste.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID folderu musi być krótkim identyfikatorem (64 lub mniej znaków) zawierać litery, cyfry, znaki kropki (.), myślnika (-) i podkreślenia (_).",
"The folder ID must be unique.": "ID folderu musi być unikalne.",
@@ -149,15 +153,16 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
"The number of old versions to keep, per file.": "Liczba wersji pliku do zachowania.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być liczbą i nie może być pusta.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
"The rescan interval must be at least 5 seconds.": "Interwał skanowania musi wynosić co najmniej 5 sekund.",
"Unknown": "Nieznany",
"Unshared": "Nieudostępnione",
"Unshared": "Unshared",
"Unused": "Nieużywane",
"Up to Date": "Aktualny",
"Upgrade To {%version%}": "Aktualizuj do {{version}}",
"Upgrading": "Aktualizowanie",
"Upload Rate": "Prędkość wysyłania",
"Use Compression": "Używaj kompresji",
"Use HTTPS for GUI": "Używaj HTTPS",
"Version": "Wersja",
"Versions Path": "Ścieżka wersji",
@@ -168,5 +173,5 @@
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
"full documentation": "pełna dokumentacja",
"items": "pozycji",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\""
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
}

View File

@@ -9,7 +9,6 @@
"Addresses": "Endereços",
"All Data": "Todos os dados",
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anônimos de uso?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Um comando externo controla o versionamento. Ele tem que remover o arquivo da pasta sincronizada.",
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados em um apresentador também serão adicionados a este dispositivo.",
"Automatic upgrades": "Atualizações automáticas",
@@ -17,13 +16,13 @@
"CPU Utilization": "Uso de CPU",
"Changelog": "Registro de alterações",
"Close": "Fechar",
"Command": "Comando",
"Comment, when used at the start of a line": "Comentário, se usado no início de uma linha",
"Compression": "Compressão",
"Compression is recommended in most setups.": "A compressão é recomendada na maior parte dos casos.",
"Connection Error": "Erro de conexão",
"Copied from elsewhere": "Copiado de outro lugar",
"Copied from original": "Copiado do original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015. Direitos reservados aos seguintes colaboradores:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Direitos reservados © 2014 Jakob Borg e os seguintes contribuidores:",
"Delete": "Apagar",
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços \"ip:porta\" separados por vírgulas ou \"dynamic\" para descobrir automaticamente o endereço.\n",
"Enter ignore patterns, one per line.": "Insira os padrões de exclusão, um por linha.",
"Error": "Erro",
"External File Versioning": "Versionamento Externo de Arquivo",
"File Versioning": "Versionamento de arquivos",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Os bits de permissão de um arquivo são ignorados durante as verificações. Use em sistemas de arquivo FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando são substituídos ou removidos pelo Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "As permissões de arquivo são ignoradas ao procurar por alterações. Use nos sistemas de arquivos FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Os arquivos são movidos para versões marcadas com o tempo numa pasta .stversions, quando substituídos ou apagados pelo syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
"Folder ID": "ID da pasta",
"Folder Master": "Pasta mestre",
@@ -59,6 +57,7 @@
"Global Discovery": "Descoberta global",
"Global Discovery Server": "Servidor de descoberta global",
"Global State": "Estado global",
"Idle": "Em espera",
"Ignore": "Ignorar",
"Ignore Patterns": "Padrões de exclusão",
"Ignore Permissions": "Ignorar permissões",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversão de uma condição (ou seja, não excluir)",
"Keep Versions": "Manter versões",
"Last File Received": "Último arquivo recebido",
"Last File Synced": "Último arquivo sincronizado",
"Last seen": "Visto por último em",
"Later": "Depois",
"Latest Release": "Versão mais recente",
"Local Discovery": "Descoberta local",
"Local State": "Estado local",
"Maximum Age": "Idade máxima",
@@ -83,6 +84,8 @@
"Notice": "Aviso",
"OK": "OK",
"Off": "Desligada",
"Offline": "Desconectado",
"Online": "Conectado",
"Out Of Sync": "Não sincronizado",
"Out of Sync Items": "Itens não sincronizados",
"Outgoing Rate Limit (KiB/s)": "Limite de velocidade de envio (KiB/s)",
@@ -125,32 +128,33 @@
"Start Browser": "Iniciar navegador",
"Stopped": "Parado",
"Support": "Suporte",
"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.": "O Syncthing foi desligado.",
"Syncthing includes the following software or portions thereof:": "O Syncthing inclui os seguintes programas ou partes deles:",
"Syncthing is restarting.": "O Syncthing está sendo reiniciado.",
"Syncthing is upgrading.": "O Syncthing está sendo atualizado.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Parece que o Syncthing está desligado ou há um problema com a sua conexão de internet. Tentando novamente...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Parece que o Syncthing está tendo problemas no processamento da requisição. Por favor, atualize a página ou reinicie o Syncthing caso o problema persista.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Parece que o Syncthing está tendo problemas ao processar sua requisição. Recarregue a página ou reinicie o Syncthing caso o problema persista.",
"The aggregated statistics are publicly available at {%url%}.": "As estatísticas agregadas 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 salva mas ainda não foi ativada. O Syncthing precisa ser reiniciado para a ativação da nova configuração.",
"The device ID cannot be blank.": "O ID de dispositivo não pode ficar vazio.",
"The device ID cannot be blank.": "O ID de dispositivo não pode estar em branco.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a ser inserido aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífens são opcionais (ignorados).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas comuns, tamanhos de pastas e versões da aplicação. Se o tipo de dados do relatório for alterado, esta janela te notificará novamente.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "O ID de dispositivo inserido não parece ser válido. Ele deve ter entre 52 e 56 caracteres e ser composto de letras e números, com espaços e hífens opcionais.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "O primeiro argumento da linha de comando é o caminho da pasta e o segundo é o caminho relativo à pasta.",
"The folder ID cannot be blank.": "O ID da pasta não pode ficar vazio.",
"The folder ID cannot be blank.": "O ID da pasta não pode estar vazio.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos), composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
"The folder ID must be unique.": "O ID da pasta deve ser único.",
"The folder path cannot be blank.": "O caminho da pasta não pode ficar vazio.",
"The folder path cannot be blank.": "O caminho da pasta não pode estar vazio.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico e não pode ficar vazio.",
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico e não pode estar vazio.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "O número máximo de dias em que uma versão é guardada. (Use 0 para manter para sempre).",
"The number of old versions to keep, per file.": "O número de versões antigas a serem mantidas, por arquivo.",
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numério e não pode ficar vazio.",
"The path cannot be blank.": "O caminho não pode ficar vazio.",
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numério e não pode estar em branco.",
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações deve ser um número positivo de segundos.",
"The rescan interval must be at least 5 seconds.": "O intervalo entre verificações deve ser de pelo menos 5 segundos.",
"Unknown": "Desconhecida",
"Unshared": "Não compartilhada",
"Unused": "Não utilizado",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Atualizar para {{version}}",
"Upgrading": "Atualizando",
"Upload Rate": "Velocidade de envio",
"Use Compression": "Usar compressão",
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
"Version": "Versão",
"Versions Path": "Caminho das versões",

View File

@@ -9,7 +9,6 @@
"Addresses": "Endereços",
"All Data": "Todos os dados",
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anónimos de utilização?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Um comando externo trata do controle de versões. Esse comando tem que remover o ficheiro da pasta sincronizada.",
"Anonymous Usage Reporting": "Enviar relatórios anónimos de utilização",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados num dispositivo apresentador serão também adicionados a este dispositivo.",
"Automatic upgrades": "Actualizações automáticas",
@@ -17,13 +16,13 @@
"CPU Utilization": "Utilização da CPU",
"Changelog": "Registo de alterações",
"Close": "Fechar",
"Command": "Comando",
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
"Compression": "Compressão",
"Compression is recommended in most setups.": "A compressão é recomendada na maior parte dos casos.",
"Connection Error": "Erro de ligação",
"Copied from elsewhere": "Copiado doutro sítio",
"Copied from original": "Copiado do original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 os seguintes contribuidores:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Direitos reservados © 2014 Jakob Borg e os seguintes contribuidores:",
"Delete": "Eliminar",
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza endereços \"ip:porto\" separados por vírgulas ou \"dynamic\" para descobrir automaticamente o endereço.",
"Enter ignore patterns, one per line.": "Escreva os padrões de exclusão, um por linha.",
"Error": "Erro",
"External File Versioning": "Controle de versões de ficheiros externo",
"File Versioning": "Gestão de versões",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "As permissões do ficheiro são ignoradas ao procurar alterações. 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, ao serem substituídos ou apagados pelo Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "As permissões do ficheiro são ignoradas ao procurar alterações. 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 marcadas com o tempo numa pasta .stversions, quando substituídos ou apagados pelo syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os ficheiros estão protegidos contra alterações feitas noutros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
"Folder ID": "ID da pasta",
"Folder Master": "Pasta mestre",
@@ -59,6 +57,7 @@
"Global Discovery": "Busca global",
"Global Discovery Server": "Servidor da busca global",
"Global State": "Estado global",
"Idle": "Em espera",
"Ignore": "Ignorar",
"Ignore Patterns": "Padrões de exclusão",
"Ignore Permissions": "Ignorar permissões",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Inversão de uma dada condição (ou seja, não excluir)",
"Keep Versions": "Manter versões",
"Last File Received": "Último ficheiro recebido",
"Last File Synced": "Último ficheiro sincronizado",
"Last seen": "Última vez que foi verificado",
"Later": "Mais tarde",
"Latest Release": "Última versão",
"Local Discovery": "Busca local",
"Local State": "Estado local",
"Maximum Age": "Idade máxima",
@@ -83,6 +84,8 @@
"Notice": "Avisos",
"OK": "OK",
"Off": "Desligado",
"Offline": "Desconectado",
"Online": "Conectado",
"Out Of Sync": "Não sincronizado",
"Out of Sync Items": "Itens não sincronizados",
"Outgoing Rate Limit (KiB/s)": "Limite da velocidade de envio (KiB/s)",
@@ -125,7 +128,9 @@
"Start Browser": "Iniciar navegador",
"Stopped": "Parado",
"Support": "Suporte",
"Support / Forum": "Suporte / Fórum",
"Sync Protocol Listen Addresses": "Endereços de escuta do protocolo de sincronização",
"Synchronization": "Sincronização",
"Syncing": "A Sincronizar",
"Syncthing has been shut down.": "O Syncthing foi desligado.",
"Syncthing includes the following software or portions thereof:": "O Syncthing inclui as seguintes aplicações ou partes delas:",
@@ -139,7 +144,6 @@
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a introduzir aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífenes são opcionais (ignorados).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "O relatório de utilização cifrado é enviado diariamente. É utilizado para rastrear plataformas comuns, tamanhos de pastas e versões da aplicação. Se o tipo de dados do relatório for alterado, será notificado novamente através desta janela.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "O ID do dispositivo fornecido não parece ser válido. Deveria ter 52 ou 56 caracteres constituídos por letras e números, com espaços e hífenes opcionais.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "O primeiro parâmetro na linha de comandos é o caminho para a pasta e o segundo parâmetro é o caminho relativo dentro da pasta.",
"The folder ID cannot be blank.": "O ID da pasta não pode estar vazio.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta tem que ser um identificador curto (64 caracteres ou menos) e ser constituído somente por letras, números e os caracteres ponto (.), hífen (-) e sublinhado (_).",
"The folder ID must be unique.": "O ID da pasta tem que ser único.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Tempo máximo para manter uma versão (em dias, use 0 para manter a versão para sempre).",
"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 estar vazio.",
"The path cannot be blank.": "O caminho não pode estar vazio.",
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações tem que ser um valor não negativo de segundos.",
"The rescan interval must be at least 5 seconds.": "O intervalo entre verificações tem que ser pelo menos de 5 segundos.",
"Unknown": "Desconhecido",
"Unshared": "Não partilhada",
"Unused": "Não utilizado",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Actualizar para {{version}}",
"Upgrading": "Actualizando",
"Upload Rate": "Velocidade de envio",
"Use Compression": "Usar compressão",
"Use HTTPS for GUI": "Utilizar HTTPS na interface gráfica",
"Version": "Versão",
"Versions Path": "Caminho das versões",

View File

@@ -9,7 +9,6 @@
"Addresses": "Адреса",
"All Data": "Все данные",
"Allow Anonymous Usage Reporting?": "Разрешить сбор анонимной статистики использования?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Анонимная статистика использования",
"Any devices configured on an introducer device will be added to this device as well.": "Все устройства, подключённые к устройству-рекомендателю, будут добавлены к текущему устройству.",
"Automatic upgrades": "Автообновление",
@@ -17,13 +16,13 @@
"CPU Utilization": "Загрузка ЦПУ",
"Changelog": "Журнал изменений",
"Close": "Закрыть",
"Command": "Command",
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
"Compression": "Сжатие",
"Compression is recommended in most setups.": "Сжатие рекомендуется в большинстве случаев.",
"Connection Error": "Ошибка подключения",
"Copied from elsewhere": "Скопировано из другого места",
"Copied from original": "Скопировано с оригинала",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Все права защищены © 2014 Jakob Borg и следующие участники:",
"Delete": "Удалить",
"Device ID": "ID устройства",
"Device Identification": "Идентификация устройства",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": " Введите пары \"IP:PORT\" разделённые запятыми, или слово \"dynamic\" для автоматического обнаружения адреса.",
"Enter ignore patterns, one per line.": "Введите шаблоны игнорирования, по-одному на строку.",
"Error": "Ошибка",
"External File Versioning": "External File Versioning",
"File Versioning": "Управление версиями",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Права доступа к файлам будут игнорироваться. Используйте на файловых системах типа FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Версии файлов с временнОй меткой перемещаются в директорию .stversions, если они удалены или перемещены в процессе синхронизации.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файлы защищены от изменений сделанных на других устройствах, но изменения сделанные на этом устройстве будут отправлены всему кластеру.",
"Folder ID": "ID папки",
"Folder Master": "Папка-оригинал",
@@ -59,6 +57,7 @@
"Global Discovery": "Глобальное обнаружение",
"Global Discovery Server": "Сервер глобального обнаружения",
"Global State": "Глобальное состояние",
"Idle": "Бездействует",
"Ignore": "Игнорировать",
"Ignore Patterns": "Шаблоны игнорирования",
"Ignore Permissions": "Игнорировать файловые права доступа",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Инвертировать текущее условие (например, исключить)",
"Keep Versions": "Количество хранимых версий",
"Last File Received": "Последний полученный файл",
"Last File Synced": "Последний синхронизированный файл",
"Last seen": "Был доступен",
"Later": "Потом",
"Latest Release": "Последняя версия",
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
"Maximum Age": "Максимальный срок",
@@ -83,6 +84,8 @@
"Notice": "Внимание",
"OK": "ОК",
"Off": "Отключить",
"Offline": "Оффлайн",
"Online": "Онлайн",
"Out Of Sync": "Не синхронизировано",
"Out of Sync Items": "Не синхронизированные пункты",
"Outgoing Rate Limit (KiB/s)": "Предел скорости отдачи (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Открыть браузер",
"Stopped": "Остановлено",
"Support": "Поддержка",
"Support / Forum": "Поддержка / Форум",
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
"Synchronization": "Синхронизация",
"Syncing": "Синхронизация",
"Syncthing has been shut down.": "Syncthing выключен.",
"Syncthing includes the following software or portions thereof:": "Syncthing включает в себя следующее ПО или его части:",
"Syncthing is restarting.": "Перезапуск Syncthing",
"Syncthing is upgrading.": "Обновление Syncthing ",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Кажется, Syncthing не запущен или есть проблемы с подключением к Интернету. Переподключаюсь...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Похоже, у Syncthing возникла проблема с обработкой запроса. Обновите страницу в обозревателе или перезапустите Syncthing, если это не поможет.",
"The aggregated statistics are publicly available at {%url%}.": "Суммарная статистика общедоступна на {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурация была сохранена но не активирована. Для активации новой конфигурации необходимо рестартовать Syncthing.",
"The device ID cannot be blank.": "ID устройства не может быть пустым.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор устройства для ввода здесь, может быть найден в диалоге \"Редактирование > Показать ID\" на другом устройстве. Пробелы и тире не обязательны (игнорируются).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Зашифрованный отчет об использовании отправляется ежедневно. Это используется для отслеживания общих платформ, размеров папок и версий приложения. Если отчетные данные изменятся, вам будет снова показано это диалоговое окно.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Введённое ID устройства не валидное. Оно должно состоять из букв и цифр, может включать пробелы и дефисы, его длина должна быть от 52 до 56 символов, ",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "ID папки не может быть пустым.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID папки должен быть коротким (не более 64 символов), должен состоять только из букв, цифр, точек (.), дефисов (-) или подчёркиваний (_).",
"The folder ID must be unique.": "ID папки должен быть уникальным.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальный срок хранения версии (в днях, 0 значит вечное хранение).",
"The number of old versions to keep, per file.": "Количество хранимых версий файла.",
"The number of versions must be a number and cannot be blank.": "Количество версий должно быть числом и не может быть пустым.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "Интервал пересканирования должен быть неотрицательным количеством секунд.",
"The rescan interval must be at least 5 seconds.": "Интервал пересканирования должен быть хотя бы 5 секунд.",
"Unknown": "Неизвестно",
"Unshared": "Необщедоступно",
"Unused": "Не используется",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Обновить до {{version}}",
"Upgrading": "Обновление",
"Upload Rate": "Скорость отдачи",
"Use Compression": "Использовать сжатие",
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
"Version": "Версия",
"Versions Path": "Путь к версиям",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adresser",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonym användarstatistik",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurerade på en introduktörsenhet kommer också att läggas till den här enheten.",
"Automatic upgrades": "Automatisk uppgradering",
@@ -17,13 +16,13 @@
"CPU Utilization": "CPU-användning",
"Changelog": "Changelog",
"Close": "Stäng",
"Command": "Command",
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
"Compression": "Compression",
"Compression is recommended in most setups.": "Komprimering är rekommenderat för de flesta.",
"Connection Error": "Anslutningsproblem",
"Copied from elsewhere": "Kopierat utifrån",
"Copied from original": "Oförändrat",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg och följande medarbetare:",
"Delete": "Radera",
"Device ID": "Enhets-ID",
"Device Identification": "Enhetsidentifikation",
@@ -43,10 +42,9 @@
"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.",
"Enter ignore patterns, one per line.": "Ange filmönster, ett per rad.",
"Error": "Fel",
"External File Versioning": "External File Versioning",
"File Versioning": "Versionshantering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT 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 devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
"Folder ID": "Katalog-ID",
"Folder Master": "Huvudlagring",
@@ -59,6 +57,7 @@
"Global Discovery": "Global uppslagning",
"Global Discovery Server": "Global uppslagningsserver",
"Global State": "Global status",
"Idle": "Vilande",
"Ignore": "Ignorera",
"Ignore Patterns": "Ignorerade filmönster",
"Ignore Permissions": "Ignorera filrättigheter",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Vänder på villkoret, d.v.s. exkluderar inte.",
"Keep Versions": "Behåll versioner",
"Last File Received": "Senast Mottagna Fil",
"Last File Synced": "Senast uppdaterade fil",
"Last seen": "Senast online",
"Later": "Senare",
"Latest Release": "Senaste version",
"Local Discovery": "Lokal uppslagning",
"Local State": "Lokal status",
"Maximum Age": "Högsta åldersgräns",
@@ -83,6 +84,8 @@
"Notice": "Observera",
"OK": "OK",
"Off": "Off",
"Offline": "Ej tillgänglig",
"Online": "Tillgänglig",
"Out Of Sync": "Osynkad",
"Out of Sync Items": "Osynkade poster",
"Outgoing Rate Limit (KiB/s)": "Max uppladdningshastighet (KiB/s)",
@@ -125,21 +128,22 @@
"Start Browser": "Starta browser",
"Stopped": "Stoppad",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Address för inkommande anslutningar",
"Synchronization": "Synkronisering",
"Syncing": "Synkroniserar",
"Syncthing has been shut down.": "Syncthing har stängts ner.",
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:",
"Syncthing is restarting.": "Syncthing startar om.",
"Syncthing is upgrading.": "Syncthing uppgraderas.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller finns det problem med din Internetanslutning. Försöker igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing verkar ha problem med att genomföra din förfrågan. Ladda om sidan i din webbläsare eller starta om Syncthing ifall problemet kvarstår.",
"The aggregated statistics are publicly available at {%url%}.": "Sammanställd 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 device ID cannot be blank.": "Enhets-ID kan inte vara tomt.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhets-ID som behövs här kan du hitta i \"Redigera > Visa ID\"-dialogen på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets-ID:t verkar inte korrekt. Det ska vara en 52 eller 56 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "Ange ett enhets-ID.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Katalog-ID:t måste vara en kort sträng (64 tecken eller mindre), bestående av endast bokstäver, siffror, punkt (.), bindestreck (-) och understreck (_).",
"The folder ID must be unique.": "Katalog-ID:t måste vara unikt.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"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 tomt.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
"The rescan interval must be at least 5 seconds.": "Uppdateringsintervallet måste vara minst 5 sekunder.",
"Unknown": "Okänt",
"Unshared": "Inte delad",
"Unused": "Oanvänd",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Uppgradera till {{version}}",
"Upgrading": "Uppgraderar",
"Upload Rate": "Uppladdningshastighet",
"Use Compression": "Använd komprimering",
"Use HTTPS for GUI": "Använd HTTPS för GUI",
"Version": "Version",
"Versions Path": "Katalog för versioner",

View File

@@ -9,7 +9,6 @@
"Addresses": "Adresler",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Anonim kullanım raporlarına izin ver ?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonim Kullanım Raporlama",
"Any devices configured on an introducer device will be added to this device as well.": "Tanıtıcı bir cihazda yapılandırılan cihazlar bu cihaza da eklenecektir.",
"Automatic upgrades": "Otomatik güncellemeler",
@@ -17,13 +16,13 @@
"CPU Utilization": "İşlemci Kullanımı",
"Changelog": "Değişim Günlüğü",
"Close": "Kapat",
"Command": "Command",
"Comment, when used at the start of a line": "Satır başında kullanıldığında açıklama özelliği taşır",
"Compression": "Sıkıştırma",
"Compression is recommended in most setups.": "Sıkıştırma işlemi çoğu kurulum için önerilmektedir.",
"Connection Error": "Bağlantı hatası",
"Copied from elsewhere": "Başka bir yerden kopyalanmış",
"Copied from original": "Aslından kopyalanmış",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Telif hakkı © 2014 Jakob Borg ve aşağıdaki katkıda bulunanlar",
"Delete": "Sil",
"Device ID": "Cihaz ID",
"Device Identification": "Cihaz Kimliği",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "IP adresleri eklemek için virgül ile ayırarak \"ip:port\" yazın, ya da \"dynamic\" yazarak otomatik bulma işlemini seçin.",
"Enter ignore patterns, one per line.": "Yoksayılacak kalıp dizilerini her satıra bir tane olacak şekilde girin.",
"Error": "Hata",
"External File Versioning": "External File Versioning",
"File Versioning": "Dosya Sürümlendirme",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Değişim yoklarken dosya izin bilgilerini ihmal et. FAT dosya sisteminde kullanın.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Dosyalar syncthing tarafından değiştirildiğinde ya da silindiğinde, tarih damgalı sürümleri .stversions dizinine taşınır.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosyalar diğer cihazlarda yapılan değişikliklerden korunur, ancak bu cihazdaki değişiklikler kümedeki diğer cihazlara gönderilir.",
"Folder ID": "Klasör ID",
"Folder Master": "Ana Klasör",
@@ -59,6 +57,7 @@
"Global Discovery": "Küresel Keşif",
"Global Discovery Server": "Küresel Keşif Sunucusu",
"Global State": "Küresel Durum",
"Idle": "Boşta",
"Ignore": "Yoksay",
"Ignore Patterns": "Kalıpları Yoksay",
"Ignore Permissions": "İzinleri yoksay",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Verilen koşulun ters çevirilmesi (örneğin: yok sayma)",
"Keep Versions": "Sürüm tut",
"Last File Received": "Alınan Son Dosya",
"Last File Synced": "Eşzamanlanan Son Dosya",
"Last seen": "Son Görülen",
"Later": "Sonra",
"Latest Release": "Son sürüm",
"Local Discovery": "Yerel bulma",
"Local State": "Yerel Durum",
"Maximum Age": "Azami Süre",
@@ -83,6 +84,8 @@
"Notice": "Uyarı",
"OK": "Tamam",
"Off": "Off",
"Offline": "Çevrim dışı",
"Online": "Çevrim içi",
"Out Of Sync": "Senkronize edilmemiş",
"Out of Sync Items": "Eşzamanlanmayan Öğeler",
"Outgoing Rate Limit (KiB/s)": "Yükleme hız sınırı (KB/sn)",
@@ -125,21 +128,22 @@
"Start Browser": "Tarayıcıyı Başlat",
"Stopped": "Durduruldu",
"Support": "Destek",
"Support / Forum": "Destek / Forum",
"Sync Protocol Listen Addresses": "Sync Protokol Dinleme Adresleri",
"Synchronization": "Senkronizasyon",
"Syncing": "Senkronize ediliyor",
"Syncthing has been shut down.": "Syncthing durduruldu",
"Syncthing includes the following software or portions thereof:": "Syncthing aşağıdaki yazılımları veya bunların bölümlerini içermektedir:",
"Syncthing is restarting.": "Syncthing yeniden başlatılıyor.",
"Syncthing is upgrading.": "Syncthing yükseltiliyor.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing görünüşe durdu veya internetin bağlantınızda problem var. Tekrar deniyor....",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing isteminizi işleme alırken bir sorunla karşılaştı. Lütfen tarayıcınızı yeniden yükleyin veya sorun devam ediyorsa Syncthing'i yeniden başlatın.",
"The aggregated statistics are publicly available at {%url%}.": "Toplanan halka açık istatistiklere ulaşabileceğiniz adres {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Ayarlar kaydedildi ancak aktifleştirilmedi. Aktifleştirmek için Syncthing yeniden başlatılmalı.",
"The device ID cannot be blank.": "Cihaz ID boş olamaz.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Buraya girilecek cihaz ID'si diğer düğümde \"Düzenle > ID Göster\" menüsünden bulunabilir. Boşluk ve kısa çizginin olup olmaması önemli değildir. (İhmal edilir)",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Şifrelenmiş kullanım bilgisi günlük olarak gönderilir. Platform, klasör büyüklüğü ve uygulama sürümü hakkında bilgi toplanır. Toplanan bilgi çeşidi değişecek olursa, sizden tekrar onay istenecek.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Girilen cihaz ID'si geçerli gibi gözükmüyor. 52 ya da 56 karakter uzunluğunda, harf ve rakamlardan oluşmalı. Boşlukların ve kısa çizgilerin olup olmaması önemli değildir.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "Klasör ID boş olamaz.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Klasör ID uzun olmamalı (64 karakter ya da daha az). Sadece harf, rakam, nokta (.), kısa çizgi (-) ve alt çizgi (_) kullanabilirsiniz.",
"The folder ID must be unique.": "Klasör ID benzersiz olmalıdır.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Bir sürümün tutulması için belirlenen azami süre (sürümleri daimi olarak tutabilmek için 0 değeri atayın)",
"The number of old versions to keep, per file.": "Dosya başına saklanacak eski sürüm.",
"The number of versions must be a number and cannot be blank.": "Sürümlerin sayısı sayı olmalı ve boş bırakılamaz.",
"The path cannot be blank.": "The path cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "Tarama zaman aralığı, saniye cinsinden negatif olmayan bir sayı olmalıdır.",
"The rescan interval must be at least 5 seconds.": "Tarama aralığı en az 5 saniye olmalıdır.",
"Unknown": "Bilinmiyor",
"Unshared": "Paylaşılmayan",
"Unused": "Kullanılmayan",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "{{version}} sürümüne yükselt",
"Upgrading": "Yükseltiliyor",
"Upload Rate": "Yükleme hızı",
"Use Compression": "Sıkıştırma kullan",
"Use HTTPS for GUI": "GUI için HTTPS kullan",
"Version": "Sürüm",
"Versions Path": "Sürüm Dizini",

View File

@@ -9,7 +9,6 @@
"Addresses": "Адреси",
"All Data": "Усі дані",
"Allow Anonymous Usage Reporting?": "Дозволити програмі збирати анонімну статистику використання?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Анонімна статистика використання",
"Any devices configured on an introducer device will be added to this device as well.": "Усі пристрої, налаштовані на пристрої-рекомендувачі, будуть додані до поточного пристрою.",
"Automatic upgrades": "Автоматичні оновлення",
@@ -17,13 +16,13 @@
"CPU Utilization": "Навантаження CPU",
"Changelog": "Перелік змін",
"Close": "Закрити",
"Command": "Команда",
"Comment, when used at the start of a line": "Коментар, якщо використовується на початку рядка",
"Compression": "Стиснення",
"Compression is recommended in most setups.": "Стиснення рекомендується у більшості випадків.",
"Connection Error": "Помилка з’єднання",
"Copied from elsewhere": "Скопійовано з іншого місця",
"Copied from original": "Скопійовано з оригіналу",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg та наступні контриб’ютори:",
"Delete": "Видалити",
"Device ID": "ID пристрою",
"Device Identification": "Ідентифікатор пристрою",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Уведіть адреси \"ip:port\" розділені комою, або слово \"dynamic\" для здійснення автоматичного виявлення адреси.",
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
"Error": "Помилка",
"External File Versioning": "Зовнішне керування версіями",
"File Versioning": "Керування версіями",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Біти прав доступу до файлів будуть проігноровані під час визначення змін. Використовуйте на файлових системах FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Файли будуть поміщатися у директорію .stversions із відповідною позначкою часу, коли вони будуть замінятися або видалятися програмою.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Файли захищено від змін зроблених на інших пристроях, але зміни зроблені на цьому пристрої будуть надіслані решті кластеру.",
"Folder ID": "ID директорії",
"Folder Master": "Центральна директорія",
@@ -59,6 +57,7 @@
"Global Discovery": "Глобальне виявлення",
"Global Discovery Server": "Сервер для глобального виявлення",
"Global State": "Глобальний статус",
"Idle": "Очікування",
"Ignore": "Ігнорувати",
"Ignore Patterns": "Ігнорувати шаблони",
"Ignore Permissions": "Ігнорувати права доступу до файлів",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "Інверсія поточної умови (тобто не виключає)",
"Keep Versions": "Зберігати версії",
"Last File Received": "Останній завантажений файл",
"Last File Synced": "Останній синхронізований файл",
"Last seen": "З’являвся останній раз",
"Later": "Пізніше",
"Latest Release": "Останній реліз",
"Local Discovery": "Локальне виявлення",
"Local State": "Локальний статус",
"Maximum Age": "Максимальний вік",
@@ -81,8 +82,10 @@
"No": "Ні",
"No File Versioning": "Версіонування вимкнено",
"Notice": "Повідомлення",
"OK": "Гаразд",
"OK": "OK",
"Off": "Вимкнути",
"Offline": "Офлайн",
"Online": "Онлайн",
"Out Of Sync": "Не синхронізовано",
"Out of Sync Items": "Не синхронізовані елементи",
"Outgoing Rate Limit (KiB/s)": "Ліміт швидкості віддачі (КіБ/с)",
@@ -103,8 +106,8 @@
"Reused": "Використано вдруге",
"Save": "Зберегти",
"Scanning": "Сканування",
"Select the devices to share this folder with.": "Оберіть пристрої, які матимуть доступ до цієї директорії.",
"Select the folders to share with this device.": "Оберіть директорії до яких матиме доступ цей пристрій.",
"Select the devices to share this folder with.": "Оберіть пристрої із якими обміняти дану директорію.",
"Select the folders to share with this device.": "Оберіть директорії які обмінювати із цим пристроєм.",
"Settings": "Налаштування",
"Share": "Розповсюдити ",
"Share Folder": "Розповсюдити каталог",
@@ -125,21 +128,22 @@
"Start Browser": "Запустити браузер",
"Stopped": "Зупинено",
"Support": "Підтримка",
"Support / Forum": "Підтримка / Форум",
"Sync Protocol Listen Addresses": "Адреса панелі управління",
"Synchronization": "Синхронізація",
"Syncing": "Синхронізація",
"Syncthing has been shut down.": "Syncthing вимкнено (закрито).",
"Syncthing includes the following software or portions thereof:": "Syncthing містить наступне програмне забезпечення (або його частини):",
"Syncthing is restarting.": "Syncthing перезавантажується.",
"Syncthing is upgrading.": "Syncthing оновлюється.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Схоже на те, що Syncthing закритий, або виникла проблема із Інтернет-з’єднанням. Проводиться повторна спроба з’єднання…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Схоже на те, що Syncthing стикнувся з проблемою оброблюючи ваш запит. Будь ласка перезавантажте сторінку в браузері або перезапустіть Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Зібрана статистика публічно доступна за посиланням {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфігурацію збережено, але не активовано. Необхідно перезапустити Syncthing для того, щоби активувати нову конфігурацію.",
"The device ID cannot be blank.": "ID пристрою не може бути порожнім.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID пристрою, який необхідно додати. Може бути знайдений у вікні \"Редагувати > Показати ID\" на іншому пристрої. Пробіли та тире опціональні (вони ігноруються програмою).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Зашифрована статистика використання відсилається щоденно. Вона використовується для того, щоб розробники розуміли, на яких платформах працює програма, розміри директорій та версії програми. Якщо набір даних, що збирається зазнає змін, ви обов’язково будете повідомлені через це діалогове вікно.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Введений ID пристрою невалідний. Ідентифікатор має вигляд строки довжиною 52 або 56 символів, що містить цифри та літери, із опціональними пробілами та тире.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Перший параметр командного рядка це шлях до директорії, другий - відносний шлях у директорії.",
"The folder ID cannot be blank.": "ID директорії не може бути порожнім.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID директорії повинен бути коротким ідентифікатором (64 символи або менше), що містить лише цифри та літери, знак крапки (.), тире (-) та нижнього підкреслення (_).",
"The folder ID must be unique.": "ID директорії повинен бути унікальним.",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальний термін, щоб зберігати версію (у днях, вствновіть в 0, щоби зберігати версії назавжди).",
"The number of old versions to keep, per file.": "Кількість старих версій, яку необхідно зберігати для кожного файлу.",
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
"The path cannot be blank.": "Шлях не може бути порожнім.",
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
"The rescan interval must be at least 5 seconds.": "Інтервал повторного сканування повинен бути принаймні 5 секунд.",
"Unknown": "Невідомо",
"Unshared": "Не розповсюджується",
"Unused": "Не використовується",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "Оновити до {{version}}",
"Upgrading": "Оновлення",
"Upload Rate": "Швидкість віддачі",
"Use Compression": "Використовувати компресію",
"Use HTTPS for GUI": "Використовувати HTTPS для доступу до панелі управління",
"Version": "Версія",
"Versions Path": "Шлях до версій",

View File

@@ -9,7 +9,6 @@
"Addresses": "地址列表",
"All Data": "所有数据",
"Allow Anonymous Usage Reporting?": "允许匿名使用报告?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "匿名使用报告",
"Any devices configured on an introducer device will be added to this device as well.": "在介绍人设备上被添加的其它设备,也将会被添加到本机。",
"Automatic upgrades": "自动升级",
@@ -17,13 +16,13 @@
"CPU Utilization": "CPU使用率",
"Changelog": "更新日志",
"Close": "关闭",
"Command": "命令",
"Comment, when used at the start of a line": "注释,在行首使用",
"Compression": "压缩",
"Compression is recommended in most setups.": "在大多数场合,建议开启压缩",
"Connection Error": "连接出错",
"Copied from elsewhere": "从其他地点复制",
"Copied from original": "从源复制",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "版权© 2014 Jakob Borg 及以下贡献者:",
"Delete": "删除",
"Device ID": "设备标识",
"Device Identification": "设备标识",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "输入以半角逗号分隔的\"ip:端口\"设置可用地址列表,或者输入\"dynamic\"表示自动寻找地址。",
"Enter ignore patterns, one per line.": "请输入忽略表达式,每行一条",
"Error": "错误",
"External File Versioning": "外部版本控制",
"File Versioning": "版本控制",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "监控文件改变时忽略文件权限位。用于FAT文件系统。",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "当文件被syncthing修改或者删除时将会被移动到.stversions文件夹已保留历史版本。",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "在其它设备中对该文件夹内文件的修改并不会被同步到本机,但是在本机上对其的修改,则会被同步到其它设备中。",
"Folder ID": "文件夹标识",
"Folder Master": "主文件夹",
@@ -59,6 +57,7 @@
"Global Discovery": "在互联网上寻找节点",
"Global Discovery Server": "用以在互联网上寻找节点的Announce服务器地址",
"Global State": "全局状态",
"Idle": "空闲",
"Ignore": "忽略",
"Ignore Patterns": "忽略列表",
"Ignore Permissions": "忽略文件权限",
@@ -67,8 +66,10 @@
"Inversion of the given condition (i.e. do not exclude)": "对本条件取反(例如:不要排除某项)",
"Keep Versions": "保留历史版本数量",
"Last File Received": "最后接收的文件",
"Last File Synced": "最近同步的文件",
"Last seen": "最后可见",
"Later": "稍后",
"Latest Release": "最新版本",
"Local Discovery": "在局域网上寻找节点",
"Local State": "本地状态",
"Maximum Age": "历史版本最长保留时间",
@@ -83,6 +84,8 @@
"Notice": "提示",
"OK": "确定",
"Off": "关闭",
"Offline": "离线",
"Online": "在线",
"Out Of Sync": "未同步",
"Out of Sync Items": "未同步的项目",
"Outgoing Rate Limit (KiB/s)": "上传速度限制(千字节/秒)",
@@ -125,21 +128,22 @@
"Start Browser": "启动浏览器",
"Stopped": "已停止",
"Support": "支持",
"Support / Forum": "支持/论坛",
"Sync Protocol Listen Addresses": "协议监听地址",
"Synchronization": "同步完成度",
"Syncing": "同步中",
"Syncthing has been shut down.": "Syncthing已关闭",
"Syncthing includes the following software or portions thereof:": "Syncthing使用了下列软件或其中的一部分",
"Syncthing is restarting.": "Syncthing正在重启",
"Syncthing is upgrading.": "Syncthing正在升级",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing似乎关闭了或者您的网络连接存在故障。重试中...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing 在处理您的请求时似乎碰到了问题。如果问题持续,请刷新您的浏览器或重启 Syncthing 。",
"The aggregated statistics are publicly available at {%url%}.": "全局统计公布于 {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "设置已经保存但是还未生效。Syncthing需要重启以启用新的设置。",
"The device ID cannot be blank.": "设备标识不能为空",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "在这里所需要输入的设备标识,可以在目标设备的“选项->显示设备标识”中看到。空格和横线可选(将会被忽略)。",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "经过加密的使用报告会每天发送。它用来跟踪统计使用本软件的平台,文件夹大小,以及本软件的版本。如果报告的内容有任何变化,本对话框会再次弹出提示您。",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "输入的设备标识似乎无效。设备标识长度必须为52或56的字母和数字空格和横线不算在内。",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "命令行的第一个参数是文件夹的路径,第二个参数是文件夹内的相对路径。",
"The folder ID cannot be blank.": "文件夹标识不能为空。",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "文件夹标识必须由不长于 64 位字符,且仅能包含字母、数字、半角句号(.)、横线(-和下划线_。",
"The folder ID must be unique.": "文件夹标识不得重复",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "历史版本保留的最长天数0为永久保存",
"The number of old versions to keep, per file.": "每个文件保留的版本数量上限。",
"The number of versions must be a number and cannot be blank.": "保留版本数量必须为数字,且不能为空。",
"The path cannot be blank.": "路径不能为空",
"The rescan interval must be a non-negative number of seconds.": "扫描间隔单位为秒,且不能为负数。",
"The rescan interval must be at least 5 seconds.": "扫描间隔必须至少为5秒。",
"Unknown": "未知",
"Unshared": "未共享",
"Unused": "已共享",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "升级至版本{{version}}",
"Upgrading": "升级中",
"Upload Rate": "上传速度",
"Use Compression": "使用压缩",
"Use HTTPS for GUI": "使用HTTPS连接web管理页面",
"Version": "版本",
"Versions Path": "历史版本路径",

View File

@@ -7,23 +7,22 @@
"Add new folder?": "新增資料夾?",
"Address": "位址",
"Addresses": "位址",
"All Data": "全部資料",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "允許匿名的使用資訊回報?",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "匿名的使用資訊回報",
"Any devices configured on an introducer device will be added to this device as well.": "任何在引入者裝置所設置的裝置將會一併新增至此裝置",
"Automatic upgrades": "自動升級",
"Bugs": "程式錯誤",
"CPU Utilization": "CPU 使用",
"Changelog": "更新日誌",
"CPU Utilization": "CPU 使用",
"Changelog": "Changelog",
"Close": "關閉",
"Command": "指令",
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
"Compression": "壓縮",
"Compression": "Compression",
"Compression is recommended in most setups.": "建議在大多數的設置中使用壓縮。",
"Connection Error": "連線錯誤",
"Copied from elsewhere": "從別處複製",
"Copied from original": "Copied from original",
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "版權所有 © 2014 Jakob Borg 及以下貢獻者:",
"Delete": "刪除",
"Device ID": "裝置識別碼",
"Device Identification": "裝置識別",
@@ -43,10 +42,9 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "輸入以半形逗號區隔的 \"ip:連接埠\" 位址,或著輸入 \"dynamic\" 以進行位址的自動探索",
"Enter ignore patterns, one per line.": "輸入忽略樣式,每行一種。",
"Error": "錯誤",
"External File Versioning": "外部檔案版本控制",
"File Versioning": "檔案版本控制",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "File permission bits are ignored when looking for changes. Use on FAT file systems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "當在尋找變化時,檔案權限位元會被忽略。用於 FAT 檔案系統。",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "當檔案被 syncthing 取代或刪除時,它們將被移至 .stversions 資料夾並添加日期戳記。",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "其他裝置做的改變不會影響到此裝置的檔案,但在此裝置上的變化將被發送到叢集中的其他部分。",
"Folder ID": "資料夾識別碼",
"Folder Master": "主資料夾",
@@ -59,6 +57,7 @@
"Global Discovery": "全域探索",
"Global Discovery Server": "全域探索伺服器",
"Global State": "全域狀態",
"Idle": "閒置",
"Ignore": "忽略",
"Ignore Patterns": "忽略樣式",
"Ignore Permissions": "忽略權限",
@@ -66,14 +65,16 @@
"Introducer": "引入者",
"Inversion of the given condition (i.e. do not exclude)": "反轉給定條件 (即:不要排除)",
"Keep Versions": "保留歷史版本數",
"Last File Received": "最後接收的檔案",
"Last File Received": "Last File Received",
"Last File Synced": "最後同步檔案",
"Last seen": "最後發現時間",
"Later": "稍後",
"Latest Release": "最新發佈",
"Local Discovery": "本地探索",
"Local State": "本地狀態",
"Maximum Age": "最長保留時間",
"Metadata Only": "Metadata Only",
"Move to top of queue": "移到隊列頂端",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
"Never": "從未",
"New Device": "新裝置",
@@ -82,7 +83,9 @@
"No File Versioning": "無檔案版本控制",
"Notice": "注意",
"OK": "確定",
"Off": "關閉",
"Off": "Off",
"Offline": "離線",
"Online": "上線",
"Out Of Sync": "不同步",
"Out of Sync Items": "不同步物件",
"Outgoing Rate Limit (KiB/s)": "連出速率限制 (KiB/s)",
@@ -93,9 +96,9 @@
"Preview": "預覽",
"Preview Usage Report": "預覽使用資訊報告",
"Quick guide to supported patterns": "可支援樣式的快速指南",
"RAM Utilization": "記憶體使用",
"RAM Utilization": "記憶體使用",
"Rescan": "重新掃描",
"Rescan All": "全部重新掃描",
"Rescan All": "Rescan All",
"Rescan Interval": "重新掃描間隔",
"Restart": "重新啟動",
"Restart Needed": "需要重新啟動",
@@ -117,7 +120,7 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "代替裝置識別碼顯示在叢集狀態中。這段文字將會廣播到其他的裝置作為一個可選的預設名稱。",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "代替裝置識別碼顯示在叢集狀態中。本欄若未填寫則將被更新為此裝置所廣播的名稱。",
"Shutdown": "關閉",
"Shutdown Complete": "關閉完成",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "簡單檔案版本控制",
"Single level wildcard (matches within a directory only)": "單階層萬用字元 (只在單個資料夾階層內比對)",
"Source Code": "原始碼",
@@ -125,21 +128,22 @@
"Start Browser": "啟動瀏覽器",
"Stopped": "已停止",
"Support": "支援",
"Support / Forum": "支援 / 論壇",
"Sync Protocol Listen Addresses": "同步通訊協定監聽位址",
"Synchronization": "同步作業",
"Syncing": "正在同步",
"Syncthing has been shut down.": "Syncthing 已經關閉。",
"Syncthing includes the following software or portions thereof:": "Syncthing 包括以下軟體或其中的一部分:",
"Syncthing is restarting.": "Syncthing 正在重新啟動。",
"Syncthing is upgrading.": "Syncthing 正在進行升級。",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing 似乎下線了,或者您的網際網路連線出現問題。正在重試...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "匯總統計資訊公佈於 {{url}}。",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "組態已經儲存但尚未啟用。Syncthing 必須重新啟動以便啟用新的組態。",
"The device ID cannot be blank.": "裝置識別碼不能為空白。",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "要輸入在這裡的裝置識別碼可以在其他裝置的 \"編輯 > 顯示識別碼\" 對話框找到。空白以及連接符號可不輸入 (省略)",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "經過加密的使用資訊報告會每天傳送。報告是用來追蹤常用的平台、資料夾的大小以及應用程式的版本。若傳送的資料集有異動,您會再次看到這個對話框。",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "輸入的裝置識別碼似乎無效。它應該為一串包含半形英文字母及數字,並可能會含有空白或連接符號的字串,且長度為 52 或 56 個字元。",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
"The folder ID cannot be blank.": "資料夾識別碼不能為空白。",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "資料夾識別碼必須為一段只包含半形英文字母、數字、點 (.)、連接符號 (-) 以及底線 (_) 的簡短識別碼 (不多於 64 個字元)",
"The folder ID must be unique.": "資料夾識別碼必須為獨一無二的。",
@@ -149,8 +153,8 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "一個版本被保留的最長時間 (單位為天,若設定為 0 則表示永遠保留)。",
"The number of old versions to keep, per file.": "每個檔案要保留的舊版本數量。",
"The number of versions must be a number and cannot be blank.": "每個檔案要保留的舊版本數量必須是數字且不能為空白。",
"The path cannot be blank.": "路徑不能空白。",
"The rescan interval must be a non-negative number of seconds.": "重新掃描間隔必須為一個非負數的秒數。",
"The rescan interval must be at least 5 seconds.": "重新掃描間隔至少須為 5 秒。",
"Unknown": "未知",
"Unshared": "未共享",
"Unused": "未使用",
@@ -158,6 +162,7 @@
"Upgrade To {%version%}": "升級至 {{version}}",
"Upgrading": "正在升級",
"Upload Rate": "上載速率",
"Use Compression": "使用壓縮",
"Use HTTPS for GUI": "為 GUI 使用 HTTPS",
"Version": "版本",
"Versions Path": "歷史版本路徑",

View File

@@ -30,7 +30,7 @@
<nav class="navbar navbar-top navbar-default" role="navigation">
<div class="container">
<span class="navbar-brand"><img class="logo" src="assets/img/logo-horizontal.svg" height="32" width="117"/></span>
<p class="navbar-text hidden-xs" ng-class="{'hidden-sm':upgradeInfo && upgradeInfo.newer}">{{thisDeviceName()}}</p>
<p class="navbar-text hidden-xs">{{thisDeviceName()}}</p>
<ul class="nav navbar-nav navbar-right">
<li ng-if="upgradeInfo && upgradeInfo.newer">
<button type="button" class="btn navbar-btn btn-primary btn-sm" href="" ng-click="upgrade()">
@@ -38,7 +38,6 @@
<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
</button>
</li>
<li class="dropdown" language-select></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-cog" aria-label="Edit"></span></a>
<ul class="dropdown-menu">
@@ -148,7 +147,7 @@
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.Error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
@@ -169,9 +168,9 @@
<div class="visible-xs"><h3><span translate>Folders</span></h3><hr></div>
<div class="panel panel-default" ng-repeat="folder in folderList()">
<div class="panel-heading" data-toggle="collapse" data-parent="#folders" href="#folder-{{$index}}" style="cursor: pointer">
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div>
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.ID)}}%"></div>
<h3 class="panel-title">
<span class="glyphicon glyphicon-hdd hidden-xs"></span>{{folder.id}}
<span class="glyphicon glyphicon-hdd hidden-xs"></span>{{folder.ID}}
<span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
@@ -180,7 +179,7 @@
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span>
({{syncPercentage(folder.id)}}%)
({{syncPercentage(folder.ID)}}%)
</span>
</span>
</h3>
@@ -191,65 +190,64 @@
<tbody>
<tr>
<th><span class="glyphicon glyphicon-folder-open"></span>&emsp;<span translate>Folder Path</span></th>
<td class="text-right">{{folder.path}}</td>
<td class="text-right">{{folder.Path}}</td>
</tr>
<tr ng-if="model[folder.id].invalid">
<tr ng-if="model[folder.ID].invalid">
<th><span class="glyphicon glyphicon-warning-sign"></span>&emsp;<span translate>Error</span></th>
<td class="text-right">{{model[folder.id].invalid}}</td>
<td class="text-right">{{model[folder.ID].invalid}}</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-globe"></span>&emsp;<span translate>Global State</span></th>
<td class="text-right">{{model[folder.id].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].globalBytes | binary}}B</td>
<td class="text-right">{{model[folder.ID].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.ID].globalBytes | binary}}B</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-home"></span>&emsp;<span translate>Local State</span></th>
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
<td class="text-right">{{model[folder.ID].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.ID].localBytes | binary}}B</td>
</tr>
<tr ng-if="model[folder.id].needFiles > 0">
<tr ng-if="model[folder.ID].needFiles > 0">
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Out Of Sync</span></th>
<td class="text-right">
<a ng-click="showNeed(folder.id)" href="">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
<a ng-click="showNeed(folder.ID)" href="">{{model[folder.ID].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.ID].needBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="folder.readOnly">
<tr ng-if="folder.ReadOnly">
<th><span class="glyphicon glyphicon-lock"></span>&emsp;<span translate>Folder Master</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="model[folder.id].ignorePatterns">
<tr ng-if="model[folder.ID].ignorePatterns">
<th><span class="glyphicon glyphicon-eye-close"></span>&emsp;<span translate>Ignore Patterns</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.ignorePerms">
<tr ng-if="folder.IgnorePerms">
<th><span class="glyphicon glyphicon-unchecked"></span>&emsp;<span translate>Ignore Permissions</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.rescanIntervalS != 60">
<tr ng-if="folder.RescanIntervalS != 60">
<th><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan Interval</span></th>
<td class="text-right">{{folder.rescanIntervalS}} s</td>
<td class="text-right">{{folder.RescanIntervalS}} s</td>
</tr>
<tr ng-if="folder.versioning.type">
<tr ng-if="folder.Versioning.Type">
<th><span class="glyphicon glyphicon-tags"></span>&emsp;<span translate>File Versioning</span></th>
<td class="text-right" ng-switch="folder.versioning.type">
<td class="text-right" ng-switch="folder.Versioning.Type">
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
<span ng-switch-when="simple" translate>Simple File Versioning</span>
<span ng-switch-when="external" translate>External File Versioning</span>
</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-share-alt"></span>&emsp;<span translate>Shared With</span></th>
<td class="text-right">{{sharesFolder(folder)}}</td>
</tr>
<tr ng-if="!folder.readOnly && folderStats[folder.id].lastFile">
<tr ng-if="!folder.readOnly && folderStats[folder.ID].LastFile">
<th><span class="glyphicon glyphicon-transfer"></span>&emsp;<span translate>Last File Received</span></th>
<td class="text-right">
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm'}}">
{{folderStats[folder.id].lastFile.filename | basename}}
<span title="{{folderStats[folder.ID].LastFile.Filename}} @ {{folderStats[folder.ID].LastFile.At | date:'yyyy-MM-dd HH:mm'}}">
{{folderStats[folder.ID].LastFile.Filename | basename}}
</span>
</td>
</tr>
@@ -257,9 +255,9 @@
</table>
</div>
<div class="panel-footer">
<button class="btn btn-sm btn-danger pull-left" ng-if="folderStatus(folder) == 'idle' && folder.readOnly && model[folder.id].needFiles > 0" ng-click="override(folder.id)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<button class="btn btn-sm btn-danger pull-left" ng-if="folder.ReadOnly && model[folder.ID].needFiles > 0" ng-click="override(folder.ID)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<span class="pull-right">
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.id)"><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan</span></button>
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.ID)"><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan</span></button>
<button class="btn btn-sm btn-default" href="" ng-click="editFolder(folder)"><span class="glyphicon glyphicon-pencil"></span>&emsp;<span translate>Edit</span></button>
</span>
<div class="clearfix"></div>
@@ -284,7 +282,7 @@
<div class="panel panel-default" ng-repeat="deviceCfg in [thisDevice()]">
<div class="panel-heading" data-toggle="collapse" href="#device-this" style="cursor: pointer">
<h3 class="panel-title">
<identicon data-value="deviceCfg.deviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<identicon data-value="deviceCfg.DeviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
</h3>
</div>
<div id="device-this" class="panel-collapse collapse in">
@@ -293,11 +291,11 @@
<tbody>
<tr>
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Download Rate</span></th>
<td class="text-right">{{connections['total'].inbps | binary}}B/s ({{connections['total'].inBytesTotal | binary}}B)</td>
<td class="text-right">{{connections['total'].inbps | binary}}B/s ({{connections['total'].InBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-cloud-upload"></span>&emsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections['total'].outbps | binary}}B/s ({{connections['total'].outBytesTotal | binary}}B)</td>
<td class="text-right">{{connections['total'].outbps | binary}}B/s ({{connections['total'].OutBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-th"></span>&emsp;<span translate>RAM Utilization</span></th>
@@ -320,10 +318,6 @@
</span>
</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Uptime</span></th>
<td class="text-right">{{system.uptime | duration:"m"}}</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-tag"></span>&emsp;<span translate>Version</span></th>
<td class="text-right">{{version}}</td>
@@ -339,13 +333,13 @@
<div class="panel-group" id="devices">
<div class="panel panel-default" ng-repeat="deviceCfg in otherDevices()">
<div class="panel-heading" data-toggle="collapse" data-parent="#devices" href="#device-{{$index}}" style="cursor: pointer">
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | number:0}}%"></div>
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.DeviceID]._total | number:0}}%"></div>
<h3 class="panel-title">
<identicon data-value="deviceCfg.deviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<identicon data-value="deviceCfg.DeviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right text-{{deviceClass(deviceCfg)}}">
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.deviceID]._total | number:0}}%)
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.DeviceID]._total | number:0}}%)
</span>
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs">&#9724;</span></span>
@@ -356,37 +350,37 @@
<div class="panel-body">
<table class="table table-condensed table-striped">
<tbody>
<tr ng-if="connections[deviceCfg.deviceID]">
<tr ng-if="connections[deviceCfg.DeviceID]">
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Download Rate</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].inbps | binary}}B/s ({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)</td>
<td class="text-right">{{connections[deviceCfg.DeviceID].inbps | binary}}B/s ({{connections[deviceCfg.DeviceID].InBytesTotal | binary}}B)</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID]">
<tr ng-if="connections[deviceCfg.DeviceID]">
<th><span class="glyphicon glyphicon-cloud-upload"></span>&emsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td>
<td class="text-right">{{connections[deviceCfg.DeviceID].outbps | binary}}B/s ({{connections[deviceCfg.DeviceID].OutBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-link"></span>&emsp;<span translate>Address</span></th>
<td class="text-right">{{deviceAddr(deviceCfg)}}</td>
</tr>
<tr ng-if="deviceCfg.compression != 'metadata'">
<tr ng-if="deviceCfg.Compression != 'metadata'">
<th><span class="glyphicon glyphicon-compressed"></span>&emsp;<span translate>Compression</span></th>
<td class="text-right">
<span ng-if="deviceCfg.compression == 'always'" translate>All Data</span>
<span ng-if="deviceCfg.compression == 'never'" translate>Off</span>
<span ng-if="deviceCfg.Compression == 'always'" translate>All Data</span>
<span ng-if="deviceCfg.Compression == 'never'" translate>Off</span>
</td>
</tr>
<tr ng-if="deviceCfg.introducer">
<tr ng-if="deviceCfg.Introducer">
<th><span class="glyphicon glyphicon-thumbs-up"></span>&emsp;<span translate>Introducer</span></th>
<td translate class="text-right">Yes</td>
</tr>
<tr ng-if="connections[deviceCfg.deviceID]">
<tr ng-if="connections[deviceCfg.DeviceID]">
<th><span class="glyphicon glyphicon-tag"></span>&emsp;<span translate>Version</span></th>
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
<td class="text-right">{{connections[deviceCfg.DeviceID].ClientVersion}}</td>
</tr>
<tr ng-if="!connections[deviceCfg.deviceID]">
<tr ng-if="!connections[deviceCfg.DeviceID]">
<th><span class="glyphicon glyphicon-eye-open"></span>&emsp;<span translate>Last seen</span></th>
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
<td translate ng-if="!deviceStats[deviceCfg.DeviceID].LastSeenDays || deviceStats[deviceCfg.DeviceID].LastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.DeviceID].LastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.DeviceID].LastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
</tr>
<tr ng-if="deviceFolders(deviceCfg).length > 0">
<th><span class="glyphicon glyphicon-hdd"></span>&emsp;<span translate>Folders</span></th>
@@ -479,11 +473,11 @@
<form role="form" name="deviceEditor">
<div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}">
<label translate for="deviceID">Device ID</label>
<input ng-if="!editingExisting" name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required valid-deviceid list="discovery-list" />
<input ng-if="!editingExisting" name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.DeviceID" required valid-deviceid list="discovery-list" />
<datalist id="discovery-list" ng-if="!editingExisting">
<option ng-repeat="(id,address) in discovery" value="{{ id }}" />
</datalist>
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.deviceID}}</div>
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.DeviceID}}</div>
<p class="help-block">
<span translate ng-if="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">The device ID to enter here can be found in the "Edit > Show ID" dialog on the other device. Spaces and dashes are optional (ignored).</span>
<span translate ng-show="!editingExisting && (deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine)">When adding a new device, keep in mind that this device must be added on the other side too.</span>
@@ -493,18 +487,18 @@
</div>
<div class="form-group">
<label translate for="name">Device Name</label>
<input id="name" class="form-control" type="text" ng-model="currentDevice.name"></input>
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
<input id="name" class="form-control" type="text" ng-model="currentDevice.Name"></input>
<p translate ng-if="currentDevice.DeviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.DeviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
</div>
<div class="form-group">
<label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice.addressesStr"></input>
<input ng-disabled="currentDevice.DeviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice.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 ng-if="!editingSelf" class="form-group">
<label translate>Compression</label>
<select class="form-control" ng-model="currentDevice.compression">
<select class="form-control" ng-model="currentDevice.Compression">
<option value="always" translate>All Data</option>
<option value="metadata" translate>Metadata Only</option>
<option value="never" translate>Off</option>
@@ -513,7 +507,7 @@
<div ng-if="!editingSelf" class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.introducer"> <span translate>Introducer</span>
<input type="checkbox" ng-model="currentDevice.Introducer"> <span translate>Introducer</span>
</label>
<p translate class="help-block">Any devices configured on an introducer device will be added to this device as well.</p>
</div>
@@ -527,7 +521,7 @@
<div class="three-columns">
<div class="checkbox" ng-repeat="folder in folderList()">
<label>
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]"> {{folder.id}}
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.ID]"> {{folder.ID}}
</label>
</div>
</div>
@@ -561,7 +555,7 @@
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.ID" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
<p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
@@ -571,7 +565,7 @@
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
<label translate for="folderPath">Folder Path</label>
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required />
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.Path" list="directory-list" required />
<datalist id="directory-list">
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
</datalist>
@@ -582,7 +576,7 @@
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.rescanIntervalS.$invalid && folderEditor.rescanIntervalS.$dirty}">
<label for="rescanIntervalS"><span translate>Rescan Interval</span> (s)</label>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.rescanIntervalS" required min="0"></input>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.RescanIntervalS" required min="0"></input>
<p class="help-block">
<span translate ng-if="!folderEditor.rescanIntervalS.$valid && folderEditor.rescanIntervalS.$dirty">The rescan interval must be a non-negative number of seconds.</span>
</p>
@@ -594,7 +588,7 @@
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.readOnly"> <span translate>Folder Master</span>
<input type="checkbox" ng-model="currentFolder.ReadOnly"> <span translate>Folder Master</span>
</label>
</div>
<p translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
@@ -602,10 +596,10 @@
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.ignorePerms"> <span translate>Ignore Permissions</span>
<input type="checkbox" ng-model="currentFolder.IgnorePerms"> <span translate>Ignore Permissions</span>
</label>
</div>
<p translate class="help-block">File permission bits are ignored when looking for changes. Use on FAT file systems.</p>
<p translate class="help-block">File permission bits are ignored when looking for changes. Use on FAT filesystems.</p>
</div>
</div>
<div class="col-md-6">
@@ -613,27 +607,22 @@
<label translate>File Versioning</label>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="none"> <span translate>No File Versioning</span>
<input type="radio" ng-model="currentFolder.FileVersioningSelector" value="none"> <span translate>No File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="simple"> <span translate>Simple File Versioning</span>
<input type="radio" ng-model="currentFolder.FileVersioningSelector" value="simple"> <span translate>Simple File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="external"> <span translate>External File Versioning</span>
<input type="radio" ng-model="currentFolder.FileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
</label>
</div>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</p>
<div class="form-group" ng-if="currentFolder.FileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required min="1"></input>
<p class="help-block">
@@ -642,8 +631,8 @@
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<div class="form-group" ng-if="currentFolder.FileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder.staggeredMaxAge" required></input>
@@ -652,20 +641,11 @@
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
<div class="form-group" ng-if="currentFolder.FileVersioningSelector == 'staggered'">
<label translate for="staggeredVersionsPath">Versions Path</label>
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath"></input>
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the folder).</p>
</div>
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the synced folder.</p>
<label translate for="externalCommand">Command</label>
<input name="externalCommand" id="externalCommand" class="form-control" type="text" ng-model="currentFolder.externalCommand" required></input>
<p class="help-block">
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">The first command line parameter is the folder path and the second parameter is the relative path in the folder.</span>
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
</div>
</div>
@@ -677,7 +657,7 @@
<div class="three-columns">
<div class="checkbox" ng-repeat="device in otherDevices()">
<label>
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.deviceID]"> {{deviceName(device)}}
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.DeviceID]"> {{deviceName(device)}}
</label>
</div>
</div>
@@ -721,7 +701,7 @@
</dl>
</div>
<div class="modal-footer">
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.path}}{{system.pathSeparator}}.stignore</code></div>
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.Path}}{{system.pathSeparator}}.stignore</code></div>
<button type="button" class="btn btn-primary btn-sm" data-dismiss="modal" ng-click="saveIgnores()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Save</span></button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Close</span></button>
</div>
@@ -744,32 +724,32 @@
<div class="col-md-6">
<div class="form-group">
<label translate for="DeviceName">Device Name</label>
<input id="DeviceName" class="form-control" type="text" ng-model="tmpOptions.deviceName">
<input id="DeviceName" class="form-control" type="text" ng-model="tmpOptions.DeviceName">
</div>
<div class="form-group">
<label translate for="ListenAddressStr">Sync Protocol Listen Addresses</label>
<input id="ListenAddressStr" class="form-control" type="text" ng-model="tmpOptions.listenAddressStr">
<input id="ListenAddressStr" class="form-control" type="text" ng-model="tmpOptions.ListenAddressStr">
</div>
<div class="form-group">
<label translate for="MaxRecvKbps">Incoming Rate Limit (KiB/s)</label>
<input id="MaxRecvKbps" class="form-control" type="number" ng-model="tmpOptions.maxRecvKbps">
<input id="MaxRecvKbps" class="form-control" type="number" ng-model="tmpOptions.MaxRecvKbps">
</div>
<div class="form-group">
<label translate for="MaxSendKbps">Outgoing Rate Limit (KiB/s)</label>
<input id="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.maxSendKbps">
<input id="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.MaxSendKbps">
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input id="UPnPEnabled" type="checkbox" ng-model="tmpOptions.upnpEnabled"> <span translate>Enable UPnP</span>
<input id="UPnPEnabled" type="checkbox" ng-model="tmpOptions.UPnPEnabled"> <span translate>Enable UPnP</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="GlobalAnnEnabled" type="checkbox" ng-model="tmpOptions.globalAnnounceEnabled"> <span translate>Global Discovery</span>
<input id="GlobalAnnEnabled" type="checkbox" ng-model="tmpOptions.GlobalAnnEnabled"> <span translate>Global Discovery</span>
</label>
</div>
</div>
@@ -778,55 +758,55 @@
<div class="form-group">
<div class="checkbox">
<label ng-if="upgradeInfo">
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.autoUpgradeEnabled"> <span translate>Automatic upgrades</span>
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.AutoUpgradeEnabled"> <span translate>Automatic upgrades</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="LocalAnnEnabled" type="checkbox" ng-model="tmpOptions.localAnnounceEnabled"> <span translate>Local Discovery</span>
<input id="LocalAnnEnabled" type="checkbox" ng-model="tmpOptions.LocalAnnEnabled"> <span translate>Local Discovery</span>
</label>
</div>
</div>
</div>
<div class="form-group">
<label translate for="GlobalAnnServersStr">Global Discovery Server</label>
<input ng-disabled="!tmpOptions.globalAnnounceEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions.globalAnnounceServersStr">
<input ng-disabled="!tmpOptions.GlobalAnnEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions.GlobalAnnServersStr">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate for="Address">GUI Listen Addresses</label>
<input id="Address" class="form-control" type="text" ng-model="tmpGUI.address">
<input id="Address" class="form-control" type="text" ng-model="tmpGUI.Address">
</div>
<div class="form-group">
<label translate for="User">GUI Authentication User</label>
<input id="User" class="form-control" type="text" ng-model="tmpGUI.user">
<input id="User" class="form-control" type="text" ng-model="tmpGUI.User">
</div>
<div class="form-group">
<label translate for="Password">GUI Authentication Password</label>
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.password">
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.Password">
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="UseTLS" type="checkbox" ng-model="tmpGUI.useTLS"> <span translate>Use HTTPS for GUI</span>
<input id="UseTLS" type="checkbox" ng-model="tmpGUI.UseTLS"> <span translate>Use HTTPS for GUI</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="StartBrowser" type="checkbox" ng-model="tmpOptions.startBrowser"> <span translate>Start Browser</span>
<input id="StartBrowser" type="checkbox" ng-model="tmpOptions.StartBrowser"> <span translate>Start Browser</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="UREnabled" type="checkbox" ng-model="tmpOptions.urEnabled"> <span translate>Anonymous Usage Reporting</span> (<a translate ng-click="showURPreview()" href="#">Preview</a>)
<input id="UREnabled" type="checkbox" ng-model="tmpOptions.UREnabled"> <span translate>Anonymous Usage Reporting</span> (<a translate ng-click="showURPreview()" href="#">Preview</a>)
</label>
</div>
</div>
@@ -835,7 +815,7 @@
<div class="form-group">
<label><span translate>API Key</span></label>
<div class="well well-sm text-monospace">{{tmpGUI.apiKey || "-"}}</div>
<div class="well well-sm text-monospace">{{tmpGUI.APIKey || "-"}}</div>
<button translate type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(tmpGUI)">Generate</button>
</div>
</div>
@@ -909,34 +889,34 @@
<table class="table table-striped table-condensed">
<tr ng-repeat="f in needed.progress" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td title="{{f.name}}">{{f.name | basename}}</td>
<td ng-if="a == 'sync' && progress[neededFolder] && progress[neededFolder][f.name]">
<td title="{{f.Name}}">{{f.Name | basename}}</td>
<td ng-if="a == 'sync' && progress[neededFolder] && progress[neededFolder][f.Name]">
<div class="progress">
<div class="progress-bar progress-bar-success" style="width: {{progress[neededFolder][f.name].reused}}%"></div>
<div class="progress-bar" style="width: {{progress[neededFolder][f.name].copiedFromOrigin}}%"></div>
<div class="progress-bar progress-bar-info" style="width: {{progress[neededFolder][f.name].copiedFromElsewhere}}%"></div>
<div class="progress-bar progress-bar-warning" style="width: {{progress[neededFolder][f.name].pulled}}%"></div>
<div class="progress-bar progress-bar-danger progress-bar-striped active" style="width: {{progress[neededFolder][f.name].pulling}}%"></div>
<div class="progress-bar progress-bar-success" style="width: {{progress[neededFolder][f.Name].Reused}}%"></div>
<div class="progress-bar" style="width: {{progress[neededFolder][f.Name].CopiedFromOrigin}}%"></div>
<div class="progress-bar progress-bar-info" style="width: {{progress[neededFolder][f.Name].CopiedFromElsewhere}}%"></div>
<div class="progress-bar progress-bar-warning" style="width: {{progress[neededFolder][f.Name].Pulled}}%"></div>
<div class="progress-bar progress-bar-danger progress-bar-striped active" style="width: {{progress[neededFolder][f.Name].Pulling}}%"></div>
<span class="show frontal">
{{progress[neededFolder][f.name].bytesDone | binary}}B / {{progress[neededFolder][f.name].bytesTotal | binary}}B
{{progress[neededFolder][f.Name].BytesDone | binary}}B / {{progress[neededFolder][f.Name].BytesTotal | binary}}B
</span>
</div>
</td>
<td class="text-right small-data" ng-if="a != 'sync' || !progress[neededFolder] || !progress[neededFolder][f.name]">
<span ng-if="f.size > 0">{{f.size | binary}}B</span>
<td class="text-right small-data" ng-if="a != 'sync' || !progress[neededFolder] || !progress[neededFolder][f.Name]">
<span ng-if="f.Size > 0">{{f.Size | binary}}B</span>
</td>
</tr>
<tr ng-repeat="f in needed.queued" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td><a href="" ng-if="$index != 0" ng-click="bumpFile(neededFolder, f.name)" title="{{'Move to top of queue' | translate}}"><span class="glyphicon glyphicon-eject"></span></a><span ng-if="$index != 0">&ensp;</span><span title="{{f.name}}">{{f.name | basename}}</span></td>
<td><a href="" ng-if="$index != 0" ng-click="bumpFile(neededFolder, f.Name)" title="{{'Move to top of queue' | translate}}"><span class="glyphicon glyphicon-eject"></span></a><span ng-if="$index != 0">&ensp;</span><span title="{{f.Name}}">{{f.Name | basename}}</span></td>
<td class="text-right small-data">
<span ng-if="f.size > 0">{{f.size | binary}}B</span>
<span ng-if="f.Size > 0">{{f.Size | binary}}B</span>
</td>
</tr>
<tr ng-repeat="f in needed.rest" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td title="{{f.name}}">{{f.name | basename}}</td>
<td class="text-right small-data"><span ng-if="f.size > 0">{{f.size | binary}}B</span></td>
<td title="{{f.Name}}">{{f.Name | basename}}</td>
<td class="text-right small-data"><span ng-if="f.Size > 0">{{f.Size | binary}}B</span></td>
</tr>
</table>
@@ -948,60 +928,57 @@
<h1 class="text-center"><img alt="Syncthing" title="Syncthing" src="assets/img/logo-horizontal.svg" style="vertical-align: -16px" height="100" width="366"/><br/><small>{{version}}</small></h1>
<hr/>
<p translate>Copyright &copy; 2015 the following Contributors:</p>
<p translate>Copyright &copy; 2014 Jakob Borg and the following Contributors:</p>
<div class="row">
<div class="col-md-12">
<ul class="list-unstyled three-columns" id="contributor-list">
<li class="auto-generated">Aaron Bieber</li>
<li class="auto-generated">Alexander Graf</li>
<li class="auto-generated">Andrew Dunham</li>
<li class="auto-generated">Arthur Axel fREW Schmidt</li>
<li class="auto-generated">Audrius Butkevicius</li>
<li class="auto-generated">Ben Curthoys</li>
<li class="auto-generated">Ben Schulz</li>
<li class="auto-generated">Ben Sidhom</li>
<li class="auto-generated">Brandon Philips</li>
<li class="auto-generated">Brendan Long</li>
<li class="auto-generated">Caleb Callaway</li>
<li class="auto-generated">Cathryne Linenweaver</li>
<li class="auto-generated">Chris Joel</li>
<li class="auto-generated">Colin Kennedy</li>
<li class="auto-generated">Daniel Martí</li>
<li class="auto-generated">Dennis Wilson</li>
<li class="auto-generated">Dominik Heidler</li>
<li class="auto-generated">Emil Hessman</li>
<li class="auto-generated">Federico Castagnini</li>
<li class="auto-generated">Felix Ableitner</li>
<li class="auto-generated">Felix Unterpaintner</li>
<li class="auto-generated">Gilli Sigurdsson</li>
<li class="auto-generated">Jakob Borg</li>
<li class="auto-generated">James Patterson</li>
<li class="auto-generated">Jaroslav Malec</li>
<li class="auto-generated">Jens Diemer</li>
<li class="auto-generated">Jochen Voss</li>
<li class="auto-generated">Johan Vromans</li>
<li class="auto-generated">Karol Różycki</li>
<li class="auto-generated">Ken'ichi Kamada</li>
<li class="auto-generated">Lode Hoste</li>
<li class="auto-generated">Marc Laporte</li>
<li class="auto-generated">Marc Pujol</li>
<li class="auto-generated">Marcin Dziadus</li>
<li class="auto-generated">Michael Jephcote</li>
<li class="auto-generated">Michael Tilli</li>
<li class="auto-generated">Pascal Jungblut</li>
<li class="auto-generated">Peter Hoeg</li>
<li class="auto-generated">Philippe Schommers</li>
<li class="auto-generated">Phill Luby</li>
<li class="auto-generated">Piotr Bejda</li>
<li class="auto-generated">Ryan Sullivan</li>
<li class="auto-generated">Sergey Mishin</li>
<li class="auto-generated">Stefan Tatschner</li>
<li class="auto-generated">Tim Abell</li>
<li class="auto-generated">Tobias Nygren</li>
<li class="auto-generated">Tomas Cerveny</li>
<li class="auto-generated">Tully Robinson</li>
<li class="auto-generated">Veeti Paananen</li>
<li class="auto-generated">Vil Brekin</li>
<ul class="list-unstyled three-columns">
<li>Aaron Bieber</li>
<li>Andrew Dunham</li>
<li>Alexander Graf</li>
<li>Arthur Axel fREW Schmidt</li>
<li>Audrius Butkevicius</li>
<li>Ben Curthoys</li>
<li>Ben Schulz</li>
<li>Ben Sidhom</li>
<li>Brandon Philips</li>
<li>Brendan Long</li>
<li>Caleb Callaway</li>
<li>Cathryne Linenweaver</li>
<li>Colin Kennedy</li>
<li>Chris Joel</li>
<li>Daniel Martí</li>
<li>Dennis Wilson</li>
<li>Dominik Heidler</li>
<li>Emil Hessman</li>
<li>Federico Castagnini</li>
<li>Felix Ableitner</li>
<li>Felix Unterpaintner</li>
<li>Gilli Sigurdsson</li>
<li>James Patterson</li>
<li>Jens Diemer</li>
<li>Jochen Voss</li>
<li>Johan Vromans</li>
<li>Kamada Ken'ichi</li>
<li>Karol Różycki</li>
<li>Lode Hoste</li>
<li>Marc Laporte</li>
<li>Marc Pujol</li>
<li>Marcin Dziadus</li>
<li>Michael Jephcote</li>
<li>Michael Tilli</li>
<li>Pascal Jungblut</li>
<li>Peter Hoeg</li>
<li>Philippe Schommers</li>
<li>Phill Luby</li>
<li>Piotr Bejda</li>
<li>Ryan Sullivan</li>
<li>Stefan Tatschner</li>
<li>Tim Abell</li>
<li>Tobias Nygren</li>
<li>Tomas Cerveny</li>
<li>Tully Robinson</li>
<li>Veeti Paananen</li>
<li>Vil Brekin</li>
</ul>
</div>
</div>
@@ -1034,7 +1011,6 @@
<script src="scripts/syncthing/core/controllers/eventController.js"></script>
<script src="scripts/syncthing/core/controllers/syncthingController.js"></script>
<script src="scripts/syncthing/core/directives/identiconDirective.js"></script>
<script src="scripts/syncthing/core/directives/languageSelectDirective.js"></script>
<script src="scripts/syncthing/core/directives/modalDirective.js"></script>
<script src="scripts/syncthing/core/directives/uniqueFolderDirective.js"></script>
<script src="scripts/syncthing/core/directives/validDeviceidDirective.js"></script>
@@ -1042,7 +1018,6 @@
<script src="scripts/syncthing/core/filters/alwaysNumberFilter.js"></script>
<script src="scripts/syncthing/core/filters/basenameFilter.js"></script>
<script src="scripts/syncthing/core/filters/binaryFilter.js"></script>
<script src="scripts/syncthing/core/filters/durationFilter.js"></script>
<script src="scripts/syncthing/core/filters/naturalFilter.js"></script>
<script src="scripts/syncthing/core/services/localeService.js"></script>

View File

@@ -70,7 +70,7 @@ function folderCompare(a, b) {
function folderMap(l) {
var m = {};
l.forEach(function (r) {
m[r.id] = r;
m[r.ID] = r;
});
return m;
}

View File

@@ -1,5 +1,3 @@
var debugEvents = false;
angular.module('syncthing.core')
.controller('EventController', function ($scope, $http) {
'use strict';
@@ -22,9 +20,7 @@ angular.module('syncthing.core')
if (lastID > 0) {
data.forEach(function (event) {
if (debugEvents) {
console.log("event", event.id, event.type, event.data);
}
console.log("event", event.id, event.type, event.data);
$scope.$emit(event.type, event);
});
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,45 +0,0 @@
angular.module('syncthing.core')
.directive('languageSelect', function (LocaleService) {
'use strict';
return {
restrict: 'EA',
template:
'<a ng-if="visible" href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="true">{{localesNames[currentLocale] || "English"}} <span class="caret"></span></a>'+
'<ul ng-if="visible" class="dropdown-menu">'+
'<li ng-repeat="(i,name) in localesNames" ng-class="{active: i==currentLocale}">'+
'<a href="#" data-ng-click="changeLanguage(i)">{{name}}</a>'+
'</li>'+
'</ul>',
link: function ($scope) {
var availableLocales = LocaleService.getAvailableLocales();
var localeNames = LocaleService.getLocalesDisplayNames();
var availableLocaleNames = {};
// get only locale names that present in available locales
for (var i = 0; i < availableLocales.length; i++) {
var a = availableLocales[i];
if (localeNames[a]) {
availableLocaleNames[a] = localeNames[a];
}
}
$scope.localesNames = availableLocaleNames;
$scope.visible = $scope.localesNames && $scope.localesNames['en'];
// using $watch cause LocaleService.currentLocale will be change after recive async query accpeted-languages
// in LocaleService.readBrowserLocales
var remove_watch = $scope.$watch(LocaleService.getCurrentLocale, function (newValue) {
if (newValue) {
$scope.currentLocale = newValue;
remove_watch();
}
});
$scope.changeLanguage = function (locale) {
LocaleService.useLocale(locale, true);
$scope.currentLocale = locale;
};
}
};
});

View File

@@ -1,35 +0,0 @@
/** convert amount of seconds to string format "d h m s" without zero values
* precision must be one of 'd', 'h', 'm', 's'(default)
* Example:
* {{121020003|duration}} --> 1400d 16h 40m 3s
* {{121020003|duration:"m"}} --> 1400d 16h 40m
* {{121020003|duration:"h"}} --> 1400d 16h
* {{1|duration:"h"}} --> <1h
**/
angular.module('syncthing.core')
.filter('duration', function () {
'use strict';
var SECONDS_IN = {"d": 86400, "h": 3600, "m": 60, "s": 1};
return function (input, precision) {
var result = "";
if (!precision) {
precision = "s";
}
input = parseInt(input, 10);
for (var k in SECONDS_IN) {
var t = (input/SECONDS_IN[k] | 0); // Math.floor
if (t > 0) {
result += " " + t + k;
}
if (precision == k) {
return result ? result : "<1" + k;
} else {
input %= SECONDS_IN[k];
}
}
return "[Error: incorrect usage, precision must be one of " + Object.keys(SECONDS_IN) + "]";
};
});

View File

@@ -1,15 +1,9 @@
angular.module('syncthing.core')
.provider('LocaleService', function () {
'use strict';
var _defaultLocale,
_availableLocales;
var _SYNLANG = "SYN_LANG"; // const key for localStorage
// native names of locales javascript escaped
var _LOCALES_NAMES = { "af": "Afrikaans", "am": "\u12A0\u121B\u122D\u129B", "ar": "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", "as": "\u0985\u09B8\u09AE\u09C0\u09AF\u09BC\u09BE", "ast": "Asturianu", "be": "\u0411\u0435\u043B\u0430\u0440\u0443\u0441\u043A\u0430\u044F", "bg": "\u0411\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438", "bn": "\u09AC\u09BE\u0982\u09B2\u09BE", "bn-IN": "\u09AC\u09BE\u0982\u09B2\u09BE (\u09AD\u09BE\u09B0\u09A4)", "bo": "\u0F56\u0F7C\u0F51\u0F0B\u0F61\u0F72\u0F42", "br": "Brezhoneg", "brx": "\u092C\u094B\u0921\u094B", "bs": "Bosanski", "ca": "Catal\u00E0", "ca-valencia": "Catal\u00E0 (valenci\xE0)", "cs": "\u010De\u0161tina", "cy": "Welsh/Cymraeg", "da": "Dansk", "de": "Deutsch", "dgo": "\u0921\u094B\u0917\u0930\u0940", "dz": "\u0F62\u0FAB\u0F7C\u0F44\u0F0B\u0F41", "el": "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC", "en-GB": "English (GB)", "en": "English", "en-ZA": "English (ZA)", "eo": "Esperanto", "es_ES": "Espa\u00F1ol (Espa\u00F1a)", "et": "Eesti keel", "eu": "Euskara", "fa": "\u0641\u0627\u0631\u0633\u0649", "fi": "Suomi", "fr": "Fran\xE7ais", "ga": "Gaeilge", "gd": "G\xE0idhlig", "gl": "Galego", "gu": "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0", "he": "\u05E2\u05D1\u05E8\u05D9\u05EA", "hi": "\u0939\u093F\u0928\u094D\u0926\u0940", "hr": "Hrvatski", "hu": "Magyar", "id": "Bahasa Indonesia", "is": "\xCDslenska", "it": "Italiano", "ja": "\u65E5\u672C\u8A9E", "ka": "\u10E5\u10D0\u10E0\u10D7\u10E3\u10DA\u10D8", "kk": "\u049A\u0430\u0437\u0430\u049B\u0448\u0430", "km": "\u1781\u17D2\u1798\u17C2\u179A", "kmr-Latn": "Kurdish (latin script)", "kn": "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1", "ko_KR": "\uD55C\uAD6D\uC5B4", "kok": "\u0915\u094B\u0902\u0915\u0923\u0940", "ks": "\uFEDA\uFEB8\uFEE4\uFEF3\uFEAE\uFEF3", "lb": "L\xEBtzebuergesch", "lo": "\u0E9E\u0EB2\u0EAA\u0EB2\u0EA5\u0EB2\u0EA7", "lt": "Lietuvi\u0173 kalba", "lv": "Latvie\u0161u", "mai": "\u092E\u0948\u0925\u093F\u0932\u0940", "mk": "\u043C\u0430\u043A\u0435\u0434\u043E\u043D\u0441\u043A\u0438", "ml": "\u0D2E\u0D32\u0D2F\u0D3E\u0D33\u0D02", "mn": "\u043C\u043E\u043D\u0433\u043E\u043B", "mni": "\u09AE\u09C8\u0987\u09A4\u09C8\u0987\u09B2\u09CB\u09A8", "mr": "\u092E\u0930\u093E\u0920\u0940", "my": "\u1019\u1014\u1039\u1019\u102C\u1005\u102C", "nb": "Bokm\u00E5l", "ne": "\u0928\u0947\u092A\u093E\u0932\u0940", "nl": "Nederlands", "nn": "Nynorsk", "nr": "Nd\xE9b\xE9l\xE9", "nso": "Sesotho sa Leboa", "oc": "Occitan", "om": "Afaan Oromo", "or": "\u0B13\u0B21\u0B3C\u0B3F\u0B06", "pa-IN": "\u0A2A\u0A70\u0A1C\u0A3E\u0A2C\u0A40", "pl": "Polski", "pt": "Portugu\xEAs", "pt-BR": "Portugu\xEAs (Brasil)", "pt-PT": "Portugu\xEAs (Portugal)", "ro_RO": "Rom\u00E2n\u0103 (Rom\u00E2nia)", "ru": "\u0420\u0443\u0441\u0441\u043A\u0438\u0439", "rw": "KinyaRwanda", "sa-IN": "\u0938\u0902\u0938\u094D\u0915\u0943\u0924\u092E\u094D", "sat": "\u0938\u0902\u0925\u093E\u0932\u0940", "sd": "\uFEB2\uFEE7\uFEA9\u06BE\u06CC", "si": "\u0DC3\u0DD2\u0D82\u0DC4\u0DBD", "sid": "Sidama", "sk": "Sloven\u010Dina", "sl": "Sloven\u0161\u010Dina", "sq": "Shqip", "sr": "\u0441\u0440\u043F\u0441\u043A\u0438", "sr-Latn": "Srpski latinicom", "ss": "SiSwati", "st": "Sesotho", "sv": "Svenska", "sw-TZ": "Kiswahili", "ta": "\u0BA4\u0BAE\u0BBF\u0BB4\u0BCD", "te": "\u0C24\u0C46\u0C32\u0C41\u0C17\u0C41", "tg": "\u0442\u043E\u04B7\u0438\u043A\u04E3", "th": "\u0E20\u0E32\u0E29\u0E32\u0E44\u0E17\u0E22", "tn": "Setswana", "tr": "T\xFCrk\xE7e", "ts": "Xitsonga", "tt": "\u0442\u0430\u0442\u0430\u0440 \u0442\u0435\u043B\u0435", "ug": "\uFE89\u06C7\uFEF2\uFECF\u06C7\uFEAD\u0686\u06D5", "uk": "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", "uz": "\u045E\u0437\u0431\u0435\u043A", "ve": "Tshiven\u1E13a", "vi": "Ti\u1EBFng vi\u1EC7t", "xh": "IsiXhosa", "zh-CN": "\u4E2D\u6587 (\u7B80\u4F53)", "zh-TW": "\u4E2D\u6587 (\u6B63\u9AD4)", "zu": "IsiZulu" };
this.setDefaultLocale = function (locale) {
_defaultLocale = locale;
};
@@ -33,12 +27,9 @@ angular.module('syncthing.core')
function autoConfigLocale() {
var params = $location.search();
var savedLang = typeof(localStorage) != 'undefined' && localStorage[_SYNLANG];
if(params.lang) {
useLocale(params.lang, true);
} else if (savedLang) {
useLocale(savedLang);
$translate.use(params.lang);
} else {
readBrowserLocales().success(function (langs) {
// Find the first language in the list provided by the user's browser
@@ -76,26 +67,21 @@ angular.module('syncthing.core')
}
}
// Fallback if nothing matched
useLocale(locale);
$translate.use(locale);
});
}
}
function useLocale(language, save2Storage) {
function useLocale(language) {
// @TODO: eventually check for valid locale format
if (language) {
$translate.use(language).then(function () {
if (save2Storage && typeof(localStorage) != 'undefined')
localStorage[_SYNLANG] = language;
});
$translate.use(language);
}
}
return {
autoConfigLocale: autoConfigLocale,
useLocale: useLocale,
getCurrentLocale: function() { return $translate.use() },
getAvailableLocales: function() { return _availableLocales },
getLocalesDisplayNames: function() { return _LOCALES_NAMES }
useLocale: useLocale
}
}];

View File

@@ -8,8 +8,6 @@ package auto_test
import (
"bytes"
"compress/gzip"
"io/ioutil"
"testing"
"github.com/syncthing/syncthing/internal/auto"
@@ -21,11 +19,6 @@ func TestAssets(t *testing.T) {
if !ok {
t.Fatal("No index.html in compiled in assets")
}
var gr *gzip.Reader
gr, _ = gzip.NewReader(bytes.NewReader(idx))
idx, _ = ioutil.ReadAll(gr)
if !bytes.Contains(idx, []byte("<html")) {
t.Fatal("No html in index.html")
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,7 @@ type Broadcast struct {
}
func NewBroadcast(port int) (*Broadcast, error) {
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: port})
if err != nil {
return nil, err
}

View File

@@ -11,28 +11,22 @@ import "net"
type Multicast struct {
conn *net.UDPConn
addr *net.UDPAddr
intf *net.Interface
inbox chan []byte
outbox chan recv
}
func NewMulticast(addr, ifname string) (*Multicast, error) {
gaddr, err := net.ResolveUDPAddr("udp6", addr)
func NewMulticast(addr string) (*Multicast, error) {
gaddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
intf, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
conn, err := net.ListenMulticastUDP("udp6", intf, gaddr)
conn, err := net.ListenMulticastUDP("udp", nil, gaddr)
if err != nil {
return nil, err
}
b := &Multicast{
conn: conn,
addr: gaddr,
intf: intf,
inbox: make(chan []byte),
outbox: make(chan recv, 16),
}
@@ -53,14 +47,27 @@ func (b *Multicast) Recv() ([]byte, net.Addr) {
}
func (b *Multicast) writer() {
addr := *b.addr
addr.Zone = b.intf.Name
for bs := range b.inbox {
_, err := b.conn.WriteTo(bs, &addr)
if err != nil && debug {
l.Debugln(err, "on write to", addr)
} else if debug {
l.Debugf("sent %d bytes to %s", len(bs), addr.String())
intfs, err := net.Interfaces()
if err != nil {
if debug {
l.Debugln("multicast interfaces:", err)
}
continue
}
for _, intf := range intfs {
if intf.Flags&net.FlagUp != 0 && intf.Flags&net.FlagMulticast != 0 {
addr := *b.addr
addr.Zone = intf.Name
_, err = b.conn.WriteTo(bs, &addr)
if err != nil {
if debug {
l.Debugln(err, "on write to", addr)
}
} else if debug {
l.Debugf("sent %d bytes to %s", len(bs), addr.String())
}
}
}
}
}

View File

@@ -27,71 +27,42 @@ import (
var l = logger.DefaultLogger
const (
OldestHandledVersion = 5
CurrentVersion = 10
)
const CurrentVersion = 10
type Configuration struct {
Version int `xml:"version,attr" json:"version"`
Folders []FolderConfiguration `xml:"folder" json:"folders"`
Devices []DeviceConfiguration `xml:"device" json:"devices"`
GUI GUIConfiguration `xml:"gui" json:"gui"`
Options OptionsConfiguration `xml:"options" json:"options"`
IgnoredDevices []protocol.DeviceID `xml:"ignoredDevice" json:"ignoredDevices"`
Version int `xml:"version,attr"`
Folders []FolderConfiguration `xml:"folder"`
Devices []DeviceConfiguration `xml:"device"`
GUI GUIConfiguration `xml:"gui"`
Options OptionsConfiguration `xml:"options"`
IgnoredDevices []protocol.DeviceID `xml:"ignoredDevice"`
XMLName xml.Name `xml:"configuration" json:"-"`
OriginalVersion int `xml:"-" json:"-"` // The version we read from disk, before any conversion
}
func (orig Configuration) Copy() Configuration {
c := orig
// Deep copy FolderConfigurations
c.Folders = make([]FolderConfiguration, len(orig.Folders))
for i := range c.Folders {
c.Folders[i] = orig.Folders[i].Copy()
}
// Deep copy DeviceConfigurations
c.Devices = make([]DeviceConfiguration, len(orig.Devices))
for i := range c.Devices {
c.Devices[i] = orig.Devices[i].Copy()
}
c.Options = orig.Options.Copy()
// DeviceIDs are values
c.IgnoredDevices = make([]protocol.DeviceID, len(orig.IgnoredDevices))
copy(c.IgnoredDevices, orig.IgnoredDevices)
return c
OriginalVersion int `xml:"-" json:"-"` // The version we read from disk, before any conversion
Deprecated_Repositories []FolderConfiguration `xml:"repository" json:"-"`
Deprecated_Nodes []DeviceConfiguration `xml:"node" json:"-"`
}
type FolderConfiguration struct {
ID string `xml:"id,attr" json:"id"`
Path string `xml:"path,attr" json:"path"`
Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
ReadOnly bool `xml:"ro,attr" json:"readOnly"`
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
LenientMtimes bool `xml:"lenientMtimes" json:"lenientMTimes"`
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
ID string `xml:"id,attr"`
Path string `xml:"path,attr"`
Devices []FolderDeviceConfiguration `xml:"device"`
ReadOnly bool `xml:"ro,attr"`
RescanIntervalS int `xml:"rescanIntervalS,attr"`
IgnorePerms bool `xml:"ignorePerms,attr"`
AutoNormalize bool `xml:"autoNormalize,attr"`
Versioning VersioningConfiguration `xml:"versioning"`
LenientMtimes bool `xml:"lenientMtimes"`
Copiers int `xml:"copiers"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int `xml:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
deviceIDs []protocol.DeviceID
}
func (orig FolderConfiguration) Copy() FolderConfiguration {
c := orig
c.Devices = make([]FolderDeviceConfiguration, len(orig.Devices))
copy(c.Devices, orig.Devices)
return c
Deprecated_Directory string `xml:"directory,omitempty,attr" json:"-"`
Deprecated_Nodes []FolderDeviceConfiguration `xml:"node" json:"-"`
}
func (f *FolderConfiguration) CreateMarker() error {
@@ -126,8 +97,8 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
}
type VersioningConfiguration struct {
Type string `xml:"type,attr" json:"type"`
Params map[string]string `json:"params"`
Type string `xml:"type,attr"`
Params map[string]string
}
type InternalVersioningConfiguration struct {
@@ -167,66 +138,60 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl
}
type DeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
Name string `xml:"name,attr,omitempty" json:"name"`
Addresses []string `xml:"address,omitempty" json:"addresses"`
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
CertName string `xml:"certName,attr,omitempty" json:"certName"`
Introducer bool `xml:"introducer,attr" json:"introducer"`
}
func (orig DeviceConfiguration) Copy() DeviceConfiguration {
c := orig
c.Addresses = make([]string, len(orig.Addresses))
copy(c.Addresses, orig.Addresses)
return c
DeviceID protocol.DeviceID `xml:"id,attr"`
Name string `xml:"name,attr,omitempty"`
Addresses []string `xml:"address,omitempty"`
Compression protocol.Compression `xml:"compression,attr"`
CertName string `xml:"certName,attr,omitempty"`
Introducer bool `xml:"introducer,attr"`
}
type FolderDeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
DeviceID protocol.DeviceID `xml:"id,attr"`
Deprecated_Name string `xml:"name,attr,omitempty" json:"-"`
Deprecated_Addresses []string `xml:"address,omitempty" json:"-"`
}
type OptionsConfiguration struct {
ListenAddress []string `xml:"listenAddress" json:"listenAddress" default:"0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
UPnPEnabled bool `xml:"upnpEnabled" json:"upnpEnabled" default:"true"`
UPnPLease int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"0"`
UPnPRenewal int `xml:"upnpRenewalMinutes" json:"upnpRenewalMinutes" default:"30"`
URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"true"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
SymlinksEnabled bool `xml:"symlinksEnabled" json:"symlinksEnabled" default:"true"`
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
}
ListenAddress []string `xml:"listenAddress" default:"0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" default:"21025"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
MaxSendKbps int `xml:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60"`
StartBrowser bool `xml:"startBrowser" default:"true"`
UPnPEnabled bool `xml:"upnpEnabled" default:"true"`
UPnPLease int `xml:"upnpLeaseMinutes" default:"0"`
UPnPRenewal int `xml:"upnpRenewalMinutes" default:"30"`
URAccepted int `xml:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
URUniqueID string `xml:"urUniqueID"` // Unique ID for reporting purposes, regenerated when UR is turned on.
RestartOnWakeup bool `xml:"restartOnWakeup" default:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" default:"12"` // 0 for off
KeepTemporariesH int `xml:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" default:"true"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" default:"5"`
SymlinksEnabled bool `xml:"symlinksEnabled" default:"true"`
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" default:"false"`
func (orig OptionsConfiguration) Copy() OptionsConfiguration {
c := orig
c.ListenAddress = make([]string, len(orig.ListenAddress))
copy(c.ListenAddress, orig.ListenAddress)
c.GlobalAnnServers = make([]string, len(orig.GlobalAnnServers))
copy(c.GlobalAnnServers, orig.GlobalAnnServers)
return c
Deprecated_RescanIntervalS int `xml:"rescanIntervalS,omitempty" json:"-"`
Deprecated_UREnabled bool `xml:"urEnabled,omitempty" json:"-"`
Deprecated_URDeclined bool `xml:"urDeclined,omitempty" json:"-"`
Deprecated_ReadOnly bool `xml:"readOnly,omitempty" json:"-"`
Deprecated_GUIEnabled bool `xml:"guiEnabled,omitempty" json:"-"`
Deprecated_GUIAddress string `xml:"guiAddress,omitempty" json:"-"`
}
type GUIConfiguration struct {
Enabled bool `xml:"enabled,attr" json:"enabled" default:"true"`
Address string `xml:"address" json:"address" default:"127.0.0.1:8384"`
User string `xml:"user,omitempty" json:"user"`
Password string `xml:"password,omitempty" json:"password"`
UseTLS bool `xml:"tls,attr" json:"useTLS"`
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
Enabled bool `xml:"enabled,attr" default:"true"`
Address string `xml:"address" default:"127.0.0.1:8080"`
User string `xml:"user,omitempty"`
Password string `xml:"password,omitempty"`
UseTLS bool `xml:"tls,attr"`
APIKey string `xml:"apikey,omitempty"`
}
func New(myID protocol.DeviceID) Configuration {
@@ -318,12 +283,27 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
}
}
if cfg.Version < OldestHandledVersion {
l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
if cfg.Options.Deprecated_URDeclined {
cfg.Options.URAccepted = -1
cfg.Options.URUniqueID = ""
}
cfg.Options.Deprecated_URDeclined = false
cfg.Options.Deprecated_UREnabled = false
// Upgrade configuration versions as appropriate
if cfg.Version <= 5 {
if cfg.Version == 1 {
convertV1V2(cfg)
}
if cfg.Version == 2 {
convertV2V3(cfg)
}
if cfg.Version == 3 {
convertV3V4(cfg)
}
if cfg.Version == 4 {
convertV4V5(cfg)
}
if cfg.Version == 5 {
convertV5V6(cfg)
}
if cfg.Version == 6 {
@@ -390,11 +370,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
}
}
// Very short reconnection intervals are annoying
if cfg.Options.ReconnectIntervalS < 5 {
cfg.Options.ReconnectIntervalS = 5
}
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
cfg.Options.GlobalAnnServers = uniqueStrings(cfg.Options.GlobalAnnServers)
@@ -480,6 +455,111 @@ func convertV5V6(cfg *Configuration) {
cfg.Version = 6
}
func convertV4V5(cfg *Configuration) {
// Renamed a bunch of fields in the structs.
if cfg.Deprecated_Nodes == nil {
cfg.Deprecated_Nodes = []DeviceConfiguration{}
}
if cfg.Deprecated_Repositories == nil {
cfg.Deprecated_Repositories = []FolderConfiguration{}
}
cfg.Devices = cfg.Deprecated_Nodes
cfg.Folders = cfg.Deprecated_Repositories
for i := range cfg.Folders {
cfg.Folders[i].Path = cfg.Folders[i].Deprecated_Directory
cfg.Folders[i].Deprecated_Directory = ""
cfg.Folders[i].Devices = cfg.Folders[i].Deprecated_Nodes
cfg.Folders[i].Deprecated_Nodes = nil
}
cfg.Deprecated_Nodes = nil
cfg.Deprecated_Repositories = nil
cfg.Version = 5
}
func convertV3V4(cfg *Configuration) {
// In previous versions, rescan interval was common for each folder.
// From now, it can be set independently. We have to make sure, that after upgrade
// the individual rescan interval will be defined for every existing folder.
for i := range cfg.Deprecated_Repositories {
cfg.Deprecated_Repositories[i].RescanIntervalS = cfg.Options.Deprecated_RescanIntervalS
}
cfg.Options.Deprecated_RescanIntervalS = 0
// In previous versions, folders held full device configurations.
// Since that's the only place where device configs were in V1, we still have
// to define the deprecated fields to be able to upgrade from V1 to V4.
for i, folder := range cfg.Deprecated_Repositories {
for j := range folder.Deprecated_Nodes {
rncfg := cfg.Deprecated_Repositories[i].Deprecated_Nodes[j]
rncfg.Deprecated_Name = ""
rncfg.Deprecated_Addresses = nil
}
}
cfg.Version = 4
}
func convertV2V3(cfg *Configuration) {
// In previous versions, compression was always on. When upgrading, enable
// compression on all existing new. New devices will get compression on by
// default by the GUI.
for i := range cfg.Deprecated_Nodes {
cfg.Deprecated_Nodes[i].Compression = protocol.CompressMetadata
}
// The global discovery format and port number changed in v0.9. Having the
// default announce server but old port number is guaranteed to be legacy.
if len(cfg.Options.GlobalAnnServers) == 1 && cfg.Options.GlobalAnnServers[0] == "announce.syncthing.net:22025" {
cfg.Options.GlobalAnnServers = []string{"announce.syncthing.net:22026"}
}
cfg.Version = 3
}
func convertV1V2(cfg *Configuration) {
// Collect the list of devices.
// Replace device configs inside folders with only a reference to the
// device ID. Set all folders to read only if the global read only flag is
// set.
var devices = map[string]FolderDeviceConfiguration{}
for i, folder := range cfg.Deprecated_Repositories {
cfg.Deprecated_Repositories[i].ReadOnly = cfg.Options.Deprecated_ReadOnly
for j, device := range folder.Deprecated_Nodes {
id := device.DeviceID.String()
if _, ok := devices[id]; !ok {
devices[id] = device
}
cfg.Deprecated_Repositories[i].Deprecated_Nodes[j] = FolderDeviceConfiguration{DeviceID: device.DeviceID}
}
}
cfg.Options.Deprecated_ReadOnly = false
// Set and sort the list of devices.
for _, device := range devices {
cfg.Deprecated_Nodes = append(cfg.Deprecated_Nodes, DeviceConfiguration{
DeviceID: device.DeviceID,
Name: device.Deprecated_Name,
Addresses: device.Deprecated_Addresses,
})
}
sort.Sort(DeviceConfigurationList(cfg.Deprecated_Nodes))
// GUI
cfg.GUI.Address = cfg.Options.Deprecated_GUIAddress
cfg.GUI.Enabled = cfg.Options.Deprecated_GUIEnabled
cfg.Options.Deprecated_GUIEnabled = false
cfg.Options.Deprecated_GUIAddress = ""
cfg.Version = 2
}
func setDefaults(data interface{}) error {
s := reflect.ValueOf(data).Elem()
t := s.Type()

View File

@@ -7,8 +7,6 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
"os"
"reflect"
@@ -59,7 +57,7 @@ func TestDefaultValues(t *testing.T) {
}
func TestDeviceConfig(t *testing.T) {
for i := OldestHandledVersion; i <= CurrentVersion; i++ {
for i := 1; i <= CurrentVersion; i++ {
os.Remove("testdata/.stfolder")
wr, err := Load(fmt.Sprintf("testdata/v%d.xml", i), device1)
if err != nil {
@@ -440,42 +438,3 @@ func TestRequiresRestart(t *testing.T) {
t.Error("Changing GUI options requires restart")
}
}
func TestCopy(t *testing.T) {
wrapper, err := Load("testdata/example.xml", device1)
if err != nil {
t.Fatal(err)
}
cfg := wrapper.Raw()
bsOrig, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
copy := cfg.Copy()
cfg.Devices[0].Addresses[0] = "wrong"
cfg.Folders[0].Devices[0].DeviceID = protocol.DeviceID{0, 1, 2, 3}
cfg.Options.ListenAddress[0] = "wrong"
cfg.GUI.APIKey = "wrong"
bsChanged, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
bsCopy, err := json.MarshalIndent(copy, "", " ")
if err != nil {
t.Fatal(err)
}
if bytes.Compare(bsOrig, bsChanged) == 0 {
t.Error("Config should have changed")
}
if bytes.Compare(bsOrig, bsCopy) != 0 {
//ioutil.WriteFile("a", bsOrig, 0644)
//ioutil.WriteFile("b", bsCopy, 0644)
t.Error("Copy should be unchanged")
}
}

View File

@@ -1,10 +1,10 @@
<configuration version="10">
<device id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
<configuration version="2">
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
<address></address>
</device>
<device id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
</device>
<device id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
</node>
<node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
</node>
<node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
<address>dynamic</address>
</device>
</node>
</configuration>

View File

@@ -1,14 +1,14 @@
<configuration version="3">
<device id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ">
<address>192.0.2.1</address>
<address>192.0.2.2</address>
</device>
<device id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
</node>
<node id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA">
<address>192.0.2.3:6070</address>
<address>[2001:db8::42]:4242</address>
</device>
<device id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
</node>
<node id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q">
<address>[2001:db8::44]:4444</address>
<address>192.0.2.4:6090</address>
</device>
</node>
</configuration>

Some files were not shown because too many files have changed in this diff Show More