mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-20 19:58:52 -05:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1816320124 | ||
|
|
f09bfe293d | ||
|
|
7b4e8fda4b | ||
|
|
c95812353f | ||
|
|
b622ec7a28 | ||
|
|
d8fbe7b77f | ||
|
|
dbcac37d91 | ||
|
|
d4d391b34f | ||
|
|
571cf7d490 | ||
|
|
e18b19ca5a | ||
|
|
48651bf482 | ||
|
|
5034a41c08 | ||
|
|
6795173e77 | ||
|
|
219ef996f5 | ||
|
|
00af1db275 | ||
|
|
5935ea896f | ||
|
|
459983c05e | ||
|
|
ebf4f029ac | ||
|
|
0eec945df1 | ||
|
|
8824b9d68f | ||
|
|
d2862814c5 | ||
|
|
25fece2d50 | ||
|
|
beb4239d1b | ||
|
|
2b78e37d92 | ||
|
|
a2070d9ce4 | ||
|
|
5827a686b8 | ||
|
|
dec479532e | ||
|
|
5d173168cc | ||
|
|
2aac1cde04 | ||
|
|
3676f0268f | ||
|
|
a7b75a54bb | ||
|
|
961a87b743 | ||
|
|
e03d59e381 | ||
|
|
d46ce5003c | ||
|
|
4c4143d9be | ||
|
|
8bc7d259f4 | ||
|
|
2d047fa428 | ||
|
|
735d420d40 | ||
|
|
bc9fc1aece | ||
|
|
b88e3c99c1 | ||
|
|
ce3e6e084c | ||
|
|
2a58ca7697 | ||
|
|
af96f7a0cd | ||
|
|
7d39d1a925 | ||
|
|
1b6c700e18 | ||
|
|
6304bd60ee | ||
|
|
4ad4417740 | ||
|
|
6a4c259a73 | ||
|
|
12eabb220d | ||
|
|
d68ce2d68c | ||
|
|
8e02c040eb | ||
|
|
a7a317c284 | ||
|
|
9d6ef24660 | ||
|
|
14014408fb | ||
|
|
b933e9666a | ||
|
|
7aff59bcce | ||
|
|
8e2760cb3d | ||
|
|
7a9fc6dbd3 | ||
|
|
75d0dc251e | ||
|
|
9a50c4d93f | ||
|
|
010d5a0192 | ||
|
|
cf1594829a | ||
|
|
854d720ce0 | ||
|
|
2f43c74ece | ||
|
|
b9817ac6b4 | ||
|
|
1e8da0d494 | ||
|
|
c47be7b415 |
4
AUTHORS
4
AUTHORS
@@ -8,6 +8,7 @@ Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||
Ben Sidhom <bsidhom@gmail.com>
|
||||
Brandon Philips <brandon@ifup.org>
|
||||
Brendan Long <self@brendanlong.com>
|
||||
Caleb Callaway <enlightened.despot@gmail.com>
|
||||
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
|
||||
Chris Joel <chris@scriptolo.gy>
|
||||
@@ -15,6 +16,7 @@ Daniel Martí <mvdan@mvdan.cc>
|
||||
Dennis Wilson <dw@risu.io>
|
||||
Dominik Heidler <dominik@heidler.eu>
|
||||
Emil Hessman <emil@hessman.se>
|
||||
Federico Castagnini <federico.castagnini@gmail.com>
|
||||
Felix Ableitner <me@nutomic.com>
|
||||
Felix Unterpaintner <bigbear2nd@gmail.com>
|
||||
Gilli Sigurdsson <gilli@vx.is>
|
||||
@@ -25,10 +27,12 @@ Jochen Voss <voss@seehuhn.de>
|
||||
Lode Hoste <zillode@zillode.be>
|
||||
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||
Michael Tilli <pyfisch@gmail.com>
|
||||
Peter Hoeg <peter@speartail.com>
|
||||
Philippe Schommers <philippe@schommers.be>
|
||||
Phill Luby <phill.luby@newredo.com>
|
||||
Piotr Bejda <piotrb10@gmail.com>
|
||||
Ryan Sullivan <kayoticsully@gmail.com>
|
||||
Tim Abell <tim@timwise.co.uk>
|
||||
Tomas Cerveny <kozec@kozec.com>
|
||||
Tully Robinson <tully@tojr.org>
|
||||
Veeti Paananen <veeti.paananen@rojekti.fi>
|
||||
|
||||
2
Godeps/Godeps.json
generated
2
Godeps/Godeps.json
generated
@@ -23,7 +23,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/calmh/xdr",
|
||||
"Rev": "45c46b7db7ff83b8b9ee09bbd95f36ab50043ece"
|
||||
"Rev": "214788d8fedfc310c18eca9ed12be408a5054cd5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/juju/ratelimit",
|
||||
|
||||
4
Godeps/_workspace/src/github.com/calmh/xdr/reader.go
generated
vendored
4
Godeps/_workspace/src/github.com/calmh/xdr/reader.go
generated
vendored
@@ -154,6 +154,10 @@ func (e XDRError) Error() string {
|
||||
return "xdr " + e.op + ": " + e.err.Error()
|
||||
}
|
||||
|
||||
func (e XDRError) IsEOF() bool {
|
||||
return e.err == io.EOF
|
||||
}
|
||||
|
||||
func (r *Reader) Error() error {
|
||||
if r.err == nil {
|
||||
return nil
|
||||
|
||||
4
NICKS
4
NICKS
@@ -10,11 +10,13 @@ alex2108 <register-github@alex-graf.de>
|
||||
andrew-d <andrew@du.nham.ca>
|
||||
asdil12 <dominik@heidler.eu>
|
||||
bigbear2nd <bigbear2nd@gmail.com>
|
||||
brendanlong <self@brendanlong.com>
|
||||
bsidhom <bsidhom@gmail.com>
|
||||
calmh <jakob@nym.se>
|
||||
cdata <chris@scriptolo.gy>
|
||||
ceh <emil@hessman.se>
|
||||
cqcallaw <enlightened.despot@gmail.com>
|
||||
facastagnini <federico.castagnini@gmail.com>
|
||||
filoozoom <philippe@schommers.be>
|
||||
frioux <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
gillisig <gilli@vx.is>
|
||||
@@ -23,6 +25,7 @@ jpjp <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||
kozec <kozec@kozec.com>
|
||||
marcindziadus <dziadus.marcin@gmail.com>
|
||||
mvdan <mvdan@mvdan.cc>
|
||||
peterhoeg <peter@speartail.com>
|
||||
philips <brandon@ifup.org>
|
||||
piobpl <piotrb10@gmail.com>
|
||||
pluby <phill.luby@newredo.com>
|
||||
@@ -30,6 +33,7 @@ pyfisch <pyfisch@gmail.com>
|
||||
qbit <qbit@deftly.net>
|
||||
seehuhn <voss@seehuhn.de>
|
||||
snnd <dw@risu.io>
|
||||
timabell <tim@timwise.co.uk>
|
||||
tojrobinson <tully@tojr.org>
|
||||
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||
veeti <veeti.paananen@rojekti.fi>
|
||||
|
||||
10
README.md
10
README.md
@@ -11,7 +11,7 @@ This is the `syncthing` project. The following are the project goals:
|
||||
collaborating devices. The protocol should be well defined, unambiguous,
|
||||
easily understood, free to use, efficient, secure and language neutral.
|
||||
This is the [Block Exchange
|
||||
Protocol](https://github.com/syncthing/protocol/blob/master/BEPv1.md).
|
||||
Protocol](https://github.com/syncthing/specs/blob/master/BEPv1.md).
|
||||
|
||||
2. Provide the reference implementation to demonstrate the usability of
|
||||
said protocol. This is the `syncthing` utility. It is the hope that
|
||||
@@ -44,10 +44,10 @@ that describes it for both Unix and Windows.
|
||||
Signed Releases
|
||||
---------------
|
||||
|
||||
As of v0.7.0 and onwards, git tags and release binaries are GPG signed with
|
||||
the key BCE524C7 (http://nym.se/gpg.txt). For release binaries, MD5 and
|
||||
SHA1 checksums are calculated and signed, available in the
|
||||
md5sum.txt.asc and sha1sum.txt.asc files.
|
||||
As of v0.10.15 and onwards, git tags and release binaries are GPG signed
|
||||
with the key D26E6ED000654A3E (see http://syncthing.net/security.html).
|
||||
For release binaries, MD5 and SHA1 checksums are calculated and signed,
|
||||
available in the md5sum.txt.asc and sha1sum.txt.asc files.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
44
build.go
44
build.go
@@ -73,7 +73,7 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
switch goarch {
|
||||
case "386", "amd64", "arm", "armv5", "armv6", "armv7":
|
||||
case "386", "amd64", "arm":
|
||||
break
|
||||
default:
|
||||
log.Printf("Unknown goarch %q; proceed with caution!", goarch)
|
||||
@@ -226,15 +226,20 @@ func buildTar() {
|
||||
build("./cmd/syncthing", tags)
|
||||
filename := name + ".tar.gz"
|
||||
files := []archiveFile{
|
||||
{"README.md", name + "/README.txt"},
|
||||
{"LICENSE", name + "/LICENSE.txt"},
|
||||
{"AUTHORS", name + "/AUTHORS.txt"},
|
||||
{"syncthing", name + "/syncthing"},
|
||||
{"syncthing.md5", name + "/syncthing.md5"},
|
||||
{src: "README.md", dst: name + "/README.txt"},
|
||||
{src: "LICENSE", dst: name + "/LICENSE.txt"},
|
||||
{src: "AUTHORS", dst: name + "/AUTHORS.txt"},
|
||||
{src: "syncthing", dst: name + "/syncthing"},
|
||||
{src: "syncthing.md5", dst: name + "/syncthing.md5"},
|
||||
}
|
||||
|
||||
for _, file := range listFiles("etc") {
|
||||
files = append(files, archiveFile{file, name + "/" + file})
|
||||
files = append(files, archiveFile{src: file, dst: name + "/" + file})
|
||||
}
|
||||
for _, file := range listFiles("extra") {
|
||||
files = append(files, archiveFile{src: file, dst: name + "/" + filepath.Base(file)})
|
||||
}
|
||||
|
||||
tarGz(filename, files)
|
||||
log.Println(filename)
|
||||
}
|
||||
@@ -249,12 +254,17 @@ func buildZip() {
|
||||
build("./cmd/syncthing", tags)
|
||||
filename := name + ".zip"
|
||||
files := []archiveFile{
|
||||
{"README.md", name + "/README.txt"},
|
||||
{"LICENSE", name + "/LICENSE.txt"},
|
||||
{"AUTHORS", name + "/AUTHORS.txt"},
|
||||
{"syncthing.exe", name + "/syncthing.exe"},
|
||||
{"syncthing.exe.md5", name + "/syncthing.exe.md5"},
|
||||
{src: "README.md", dst: name + "/README.txt"},
|
||||
{src: "LICENSE", dst: name + "/LICENSE.txt"},
|
||||
{src: "AUTHORS", dst: name + "/AUTHORS.txt"},
|
||||
{src: "syncthing.exe", dst: name + "/syncthing.exe"},
|
||||
{src: "syncthing.exe.md5", dst: name + "/syncthing.exe.md5"},
|
||||
}
|
||||
|
||||
for _, file := range listFiles("extra") {
|
||||
files = append(files, archiveFile{src: file, dst: name + "/" + filepath.Base(file)})
|
||||
}
|
||||
|
||||
zipFile(filename, files)
|
||||
log.Println(filename)
|
||||
}
|
||||
@@ -275,15 +285,7 @@ func listFiles(dir string) []string {
|
||||
|
||||
func setBuildEnv() {
|
||||
os.Setenv("GOOS", goos)
|
||||
if strings.HasPrefix(goarch, "armv") {
|
||||
os.Setenv("GOARCH", "arm")
|
||||
os.Setenv("GOARM", goarch[4:])
|
||||
} else {
|
||||
os.Setenv("GOARCH", goarch)
|
||||
}
|
||||
if goarch == "386" {
|
||||
os.Setenv("GO386", "387")
|
||||
}
|
||||
os.Setenv("GOARCH", goarch)
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Println("Warning: can't determine current dir:", err)
|
||||
|
||||
6
build.sh
6
build.sh
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
DOCKERIMGV=1.4-4
|
||||
DOCKERIMGV=1.4-5
|
||||
|
||||
case "${1:-default}" in
|
||||
default)
|
||||
@@ -52,9 +52,7 @@ case "${1:-default}" in
|
||||
all)
|
||||
go run build.go -goos linux -goarch amd64 tar
|
||||
go run build.go -goos linux -goarch 386 tar
|
||||
go run build.go -goos linux -goarch armv5 tar
|
||||
go run build.go -goos linux -goarch armv6 tar
|
||||
go run build.go -goos linux -goarch armv7 tar
|
||||
go run build.go -goos linux -goarch arm tar
|
||||
|
||||
go run build.go -goos freebsd -goarch amd64 tar
|
||||
go run build.go -goos freebsd -goarch 386 tar
|
||||
|
||||
52
cmd/stfinddevice/main.go
Normal file
52
cmd/stfinddevice/main.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation, either version 3 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/discover"
|
||||
"github.com/syncthing/syncthing/internal/protocol"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
var server string
|
||||
|
||||
flag.StringVar(&server, "server", "udp4://announce.syncthing.net:22026", "Announce server")
|
||||
flag.Parse()
|
||||
|
||||
if len(flag.Args()) != 1 || server == "" {
|
||||
log.Printf("Usage: %s [-server=\"udp4://announce.syncthing.net:22026\"] <device>", os.Args[0])
|
||||
os.Exit(64)
|
||||
}
|
||||
|
||||
id, err := protocol.DeviceIDFromString(flag.Args()[0])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
discoverer := discover.NewDiscoverer(protocol.LocalDeviceID, nil)
|
||||
discoverer.StartGlobal([]string{server}, 1)
|
||||
for _, addr := range discoverer.Lookup(id) {
|
||||
log.Println(addr)
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@ func main() {
|
||||
|
||||
if *device == "" {
|
||||
log.Printf("*** Global index for folder %q", *folder)
|
||||
fs.WithGlobalTruncated(func(fi protocol.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfoTruncated)
|
||||
fs.WithGlobalTruncated(func(fi files.FileIntf) bool {
|
||||
f := fi.(files.FileInfoTruncated)
|
||||
fmt.Println(f)
|
||||
fmt.Println("\t", fs.Availability(f.Name))
|
||||
return true
|
||||
@@ -55,8 +55,8 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("*** Have index for folder %q device %q", *folder, n)
|
||||
fs.WithHaveTruncated(n, func(fi protocol.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfoTruncated)
|
||||
fs.WithHaveTruncated(n, func(fi files.FileIntf) bool {
|
||||
f := fi.(files.FileInfoTruncated)
|
||||
fmt.Println(f)
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
"github.com/syncthing/syncthing/internal/discover"
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
"github.com/syncthing/syncthing/internal/files"
|
||||
"github.com/syncthing/syncthing/internal/model"
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"github.com/syncthing/syncthing/internal/protocol"
|
||||
@@ -448,6 +449,7 @@ func restGetSystem(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
cpuUsageLock.RUnlock()
|
||||
res["cpuPercent"] = cpusum / 10
|
||||
res["pathSeparator"] = string(filepath.Separator)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
@@ -571,6 +573,10 @@ func restGetEvents(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.LatestRelease(strings.Contains(Version, "-beta"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
@@ -778,9 +784,9 @@ func mimeTypeForFile(file string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} {
|
||||
output := make([]map[string]interface{}, len(files))
|
||||
for i, file := range files {
|
||||
func toNeedSlice(fs []files.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,
|
||||
@@ -788,7 +794,7 @@ func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} {
|
||||
"Version": file.Version,
|
||||
"LocalVersion": file.LocalVersion,
|
||||
"NumBlocks": file.NumBlocks,
|
||||
"Size": protocol.BlocksToSize(file.NumBlocks),
|
||||
"Size": files.BlocksToSize(file.NumBlocks),
|
||||
}
|
||||
}
|
||||
return output
|
||||
|
||||
@@ -1259,28 +1259,35 @@ func standbyMonitor() {
|
||||
}
|
||||
|
||||
func autoUpgrade() {
|
||||
var skipped bool
|
||||
interval := time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour
|
||||
timer := time.NewTimer(0)
|
||||
sub := events.Default.Subscribe(events.DeviceConnected)
|
||||
for {
|
||||
if skipped {
|
||||
time.Sleep(interval)
|
||||
} else {
|
||||
skipped = true
|
||||
select {
|
||||
case event := <-sub.C():
|
||||
data, ok := event.Data.(map[string]string)
|
||||
if !ok || data["clientName"] != "syncthing" || upgrade.CompareVersions(data["clientVersion"], Version) != upgrade.Newer {
|
||||
continue
|
||||
}
|
||||
l.Infof("Connected to device %s with a newer version (current %q < remote %q). Checking for upgrades.", data["id"], Version, data["clientVersion"])
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
rel, err := upgrade.LatestRelease(IsBeta)
|
||||
if err == upgrade.ErrUpgradeUnsupported {
|
||||
events.Default.Unsubscribe(sub)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
// Don't complain too loudly here; we might simply not have
|
||||
// internet connectivity, or the upgrade server might be down.
|
||||
l.Infoln("Automatic upgrade:", err)
|
||||
timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour)
|
||||
continue
|
||||
}
|
||||
|
||||
if upgrade.CompareVersions(rel.Tag, Version) != upgrade.Newer {
|
||||
// Skip equal, older or majorly newer (incompatible) versions
|
||||
timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1288,8 +1295,10 @@ func autoUpgrade() {
|
||||
err = upgrade.To(rel)
|
||||
if err != nil {
|
||||
l.Warnln("Automatic upgrade:", err)
|
||||
timer.Reset(time.Duration(cfg.Options().AutoUpgradeIntervalH) * time.Hour)
|
||||
continue
|
||||
}
|
||||
events.Default.Unsubscribe(sub)
|
||||
l.Warnf("Automatically upgraded to version %q. Restarting in 1 minute.", rel.Tag)
|
||||
time.Sleep(time.Minute)
|
||||
stop <- exitUpgrading
|
||||
|
||||
@@ -15,14 +15,21 @@
|
||||
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var predictableRandomTest sync.Once
|
||||
|
||||
func TestPredictableRandom(t *testing.T) {
|
||||
// predictable random sequence is predictable
|
||||
e := 3440579354231278675
|
||||
if v := predictableRandom.Int(); v != e {
|
||||
t.Errorf("Unexpected random value %d != %d", v, e)
|
||||
}
|
||||
predictableRandomTest.Do(func() {
|
||||
// predictable random sequence is predictable
|
||||
e := 3440579354231278675
|
||||
if v := predictableRandom.Int(); v != e {
|
||||
t.Errorf("Unexpected random value %d != %d", v, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSeedFromBytes(t *testing.T) {
|
||||
|
||||
@@ -17,9 +17,12 @@ RUN curl -sSL https://golang.org/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \
|
||||
| tar -v -C /usr/local -xz
|
||||
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
RUN mkdir /go
|
||||
ENV GOPATH /go
|
||||
ENV GO386 387
|
||||
ENV GOARM 5
|
||||
ENV PATH /go/bin:$PATH
|
||||
|
||||
RUN mkdir /go
|
||||
WORKDIR /go
|
||||
|
||||
# Use gonative to install native Go for most arch/OS combos
|
||||
@@ -45,8 +48,6 @@ RUN bash -xec '\
|
||||
for platform in linux/386 freebsd/386 windows/386 linux/arm openbsd/amd64 openbsd/386; do \
|
||||
GOOS=${platform%/*} \
|
||||
GOARCH=${platform##*/} \
|
||||
GOARM=5 \
|
||||
GO386=387 \
|
||||
CGO_ENABLED=0 \
|
||||
./make.bash --no-clean 2>&1; \
|
||||
done \
|
||||
|
||||
27
etc/linux-systemd/README.md
Normal file
27
etc/linux-systemd/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
This directory contains a configuration for running syncthing under the
|
||||
"systemd" service manager on Linux both under either a systemd system service or
|
||||
systemd user service.
|
||||
|
||||
1. Install systemd.
|
||||
|
||||
2. If you are running this as a system level service:
|
||||
|
||||
1. Create the user you will be running the service as (foo in this example).
|
||||
|
||||
2. Copy the syncthing@.service files to /etc/systemd/system
|
||||
|
||||
3. Enable and start the service
|
||||
systemctl enable syncthing@foo.service
|
||||
systemctl start syncthing@foo.service
|
||||
|
||||
3. If you are running this as a user level service:
|
||||
|
||||
1. Log in as the user you will be running the service as
|
||||
|
||||
2. Copy the syncthing.service files to /etc/systemd/user
|
||||
|
||||
3. Enable and start the service
|
||||
systemctl --user enable syncthing.service
|
||||
systemctl --user start syncthing.service
|
||||
|
||||
Log output is sent to the journal.
|
||||
14
etc/linux-systemd/system/syncthing@.service
Normal file
14
etc/linux-systemd/system/syncthing@.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Syncthing service for %i
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=%i
|
||||
Environment=STARGS=
|
||||
EnvironmentFile=-/etc/default/syncthing
|
||||
Environment=STNORESTART=yes
|
||||
ExecStart=/usr/bin/syncthing ${STARGS}
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
12
etc/linux-systemd/user/syncthing.service
Normal file
12
etc/linux-systemd/user/syncthing.service
Normal file
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Syncthing service
|
||||
|
||||
[Service]
|
||||
Environment=STARGS=
|
||||
EnvironmentFile=-%h/.config/syncthing/environment
|
||||
Environment=STNORESTART=yes
|
||||
ExecStart=/usr/bin/syncthing ${STARGS}
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=cmdline.target
|
||||
@@ -39,6 +39,8 @@ ul+h5 {
|
||||
|
||||
.panel-title {
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
identicon {
|
||||
@@ -54,6 +56,10 @@ identicon {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.checkbox input[type="checkbox"], .radio input[type="radio"] {
|
||||
float: none; /* issue #1197 */
|
||||
}
|
||||
|
||||
.popover {
|
||||
max-width: none;
|
||||
}
|
||||
@@ -69,6 +75,10 @@ identicon {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.panel-heading .glyphicon {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@@ -195,3 +205,15 @@ ul.three-columns li, ul.two-columns li {
|
||||
padding-left: 0.5em;
|
||||
text-indent: -0.5em;
|
||||
}
|
||||
|
||||
/** Footer nav on small devices **/
|
||||
|
||||
@media (max-width: 767px) {
|
||||
body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.navbar-fixed-bottom {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatic upgrades",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "CPU Utilization",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Close",
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Disconnected",
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Download Rate",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Introducer",
|
||||
"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",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Staggered File Versioning",
|
||||
"Start Browser": "Start Browser",
|
||||
"Stopped": "Stopped",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
|
||||
"Synchronization": "Synchronization",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Автоматични ъпдейти",
|
||||
"Bugs": "Бъгове",
|
||||
"CPU Utilization": "Натоварване на Процесора",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Затвори",
|
||||
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
|
||||
"Compression is recommended in most setups.": "Компресията е препоръчителна в повечето конфигурации.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Идентификация на устройство",
|
||||
"Device Name": "Име на устройство",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Прекрати Връзката",
|
||||
"Documentation": "Документация",
|
||||
"Download Rate": "Скорост на Теглене",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Introducer",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
|
||||
"Keep Versions": "Пази Версии",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last File Synced": "Последния синхронизиран файл",
|
||||
"Last seen": "Последно видян",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Наслагващи се Файлови Версии",
|
||||
"Start Browser": "Стартирай Браузъра",
|
||||
"Stopped": "Спряна",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Помощ / Форум",
|
||||
"Sync Protocol Listen Addresses": "Адрес за слушане на синхронизиращия протокол",
|
||||
"Synchronization": "Синхронизация",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.": "Полето идентификатор на устройство не може да бъде празно.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatický upgrade",
|
||||
"Bugs": "Chyby",
|
||||
"CPU Utilization": "Využití CPU",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Zavřít",
|
||||
"Comment, when used at the start of a line": "Komentář, pokud použito na začátku řádku",
|
||||
"Compression is recommended in most setups.": "Komprese je doporučena pro většinu nastavení.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Identifikace přístroje",
|
||||
"Device Name": "Jméno přístroje",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Přístroj {{device}} ({{address}}) žádá o připojení. Chcete ho přidat?",
|
||||
"Devices": "Přístroje",
|
||||
"Disconnected": "Odpojeno",
|
||||
"Documentation": "Dokumentace",
|
||||
"Download Rate": "Rychlost stahování",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Zavaděč",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
|
||||
"Keep Versions": "Ponechat verze",
|
||||
"Last File Received": "Poslední soubor přijat",
|
||||
"Last File Synced": "Poslední soubor synchronizován",
|
||||
"Last seen": "Naposledy spatřen",
|
||||
"Later": "Později",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Vícenásobné verze souborů",
|
||||
"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",
|
||||
@@ -125,6 +129,7 @@
|
||||
"Syncthing is restarting.": "Syncthing se restartuje.",
|
||||
"Syncthing is upgrading.": "Syncthing se aktualizuje.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing se zdá být nefunkční, nebo je problém s připojením k Internetu. Opakuji...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing 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é.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automat. Updates",
|
||||
"Bugs": "Fehler",
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Changelog": "Versionsinfo",
|
||||
"Close": "Schließen",
|
||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
|
||||
"Compression is recommended in most setups.": "Datenkomprimierung ist für die meisten Anwendungen empfohlen",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Gerät Identifikation",
|
||||
"Device Name": "Gerätename",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Gerät {{device}} ({{address}}) möchte sich verbinden. Gerät hinzufügen?",
|
||||
"Devices": "Geräte",
|
||||
"Disconnected": "Getrennt",
|
||||
"Documentation": "Dokumentation",
|
||||
"Download Rate": "Download",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Verteilergerät",
|
||||
"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",
|
||||
@@ -74,8 +77,8 @@
|
||||
"New Folder": "Neues Verzeichnis",
|
||||
"No": "Nein",
|
||||
"No File Versioning": "Keine Dateiversionierung",
|
||||
"Notice": "Benachrichtigung",
|
||||
"OK": "Ok",
|
||||
"Notice": "Hinweis",
|
||||
"OK": "OK",
|
||||
"Offline": "Offline",
|
||||
"Online": "Online",
|
||||
"Out Of Sync": "Nicht synchronisiert",
|
||||
@@ -101,7 +104,7 @@
|
||||
"Settings": "Einstellungen",
|
||||
"Share": "Teilen",
|
||||
"Share Folder": "Teile Verzeichnis",
|
||||
"Share Folders With Device": "Teile Verzeichnisse mit Gerät",
|
||||
"Share Folders With Device": "Teile Verzeichnisse mit diesem Gerät",
|
||||
"Share With Devices": "Teile mit diesen Geräten",
|
||||
"Share this folder?": "Dieses Verzeichnis teilen?",
|
||||
"Shared With": "Geteilt mit",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Stufenweise Dateiversionierung",
|
||||
"Start Browser": "Browser starten",
|
||||
"Stopped": "Gestoppt",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Adresse(n) für das Synchronisierungsprotokoll",
|
||||
"Synchronization": "Synchronisierung",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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.": "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.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatic upgrades",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "CPU Utilization",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Close",
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Disconnected",
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Download Rate",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Introducer",
|
||||
"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",
|
||||
@@ -79,6 +82,7 @@
|
||||
"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)",
|
||||
"Override Changes": "Override Changes",
|
||||
"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": "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",
|
||||
@@ -110,12 +114,14 @@
|
||||
"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 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.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||
"Shutdown": "Shutdown",
|
||||
"Shutdown Complete": "Shutdown Complete",
|
||||
"Simple File Versioning": "Simple File Versioning",
|
||||
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
|
||||
"Source Code": "Source Code",
|
||||
"Staggered File Versioning": "Staggered File Versioning",
|
||||
"Start Browser": "Start Browser",
|
||||
"Stopped": "Stopped",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
|
||||
"Synchronization": "Synchronization",
|
||||
@@ -125,6 +131,7 @@
|
||||
"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 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.",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "Clave API",
|
||||
"About": "Acerca de",
|
||||
"Add": "Add",
|
||||
"Add": "Agregar",
|
||||
"Add Device": "Agregar dispositivo",
|
||||
"Add Folder": "Agregar repositorio",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "¿Agregar nuevo repositorio?",
|
||||
"Address": "Dirección",
|
||||
"Addresses": "Direcciones",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir reporte anónimo de uso?",
|
||||
@@ -13,18 +13,20 @@
|
||||
"Automatic upgrades": "Actualizaciones automáticas",
|
||||
"Bugs": "Errores",
|
||||
"CPU Utilization": "Uso de la CPU",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Cerrar",
|
||||
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
|
||||
"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": "Copied from elsewhere",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copied from elsewhere": "Copiado de otra parte.",
|
||||
"Copied from original": "Copiado del original",
|
||||
"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",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "El dispositivo {{device}} ({{address}}) se quiere conectar. ¿Agregar nuevo dispositivo?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Desconectado",
|
||||
"Documentation": "Documentación",
|
||||
"Download Rate": "Tasa de descarga",
|
||||
@@ -52,26 +54,27 @@
|
||||
"Generate": "Generar",
|
||||
"Global Discovery": "Búsqueda en internet",
|
||||
"Global Discovery Server": "Servidor global de identificación",
|
||||
"Global State": "Estado Global",
|
||||
"Global State": "Estado global",
|
||||
"Idle": "Inactivo",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Patrones de exclusión",
|
||||
"Ignore Permissions": "Ignorar permisos",
|
||||
"Incoming Rate Limit (KiB/s)": "Límite de velocidad de entrada (KiB/s)",
|
||||
"Introducer": "Introductor",
|
||||
"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": "Last File Received",
|
||||
"Last File Synced": "Último archivo sincronizado.",
|
||||
"Last seen": "Visto por ultima vez",
|
||||
"Later": "Later",
|
||||
"Later": "Más tarde",
|
||||
"Latest Release": "Última versión",
|
||||
"Local Discovery": "Búsqueda en red local",
|
||||
"Local State": "Estado Local",
|
||||
"Local State": "Estado local",
|
||||
"Maximum Age": "Edad máxima",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Carácter comodín multinivel (coincide en el directorio y sus subdirectorios)",
|
||||
"Never": "Nunca",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "Nuevo dispositivo",
|
||||
"New Folder": "Nuevo repositorio",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sin control de versiones de archivos",
|
||||
"Notice": "Aviso",
|
||||
@@ -89,33 +92,34 @@
|
||||
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
|
||||
"RAM Utilization": "Utilización de RAM",
|
||||
"Rescan": "Reescanear",
|
||||
"Rescan Interval": "Intervalo de Reescaneo",
|
||||
"Rescan Interval": "Intervalo de reescaneo",
|
||||
"Restart": "Reiniciar",
|
||||
"Restart Needed": "Es necesario reiniciar",
|
||||
"Restarting": "Reiniciando",
|
||||
"Reused": "Reused",
|
||||
"Reused": "Reutilizado",
|
||||
"Save": "Guardar",
|
||||
"Scanning": "Actualización",
|
||||
"Select the devices to share this folder with.": "Seleccione los dispositivos con los cuales compartir este repositorio.",
|
||||
"Select the folders to share with this device.": "Seleccione los repositorios para compartir con este dispositivo.",
|
||||
"Settings": "Configuración",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share": "Compartir",
|
||||
"Share Folder": "Compartir repositorio",
|
||||
"Share Folders With Device": "Compartir repositorios con dispositivo",
|
||||
"Share With Devices": "Compartir con los dispositivos",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "¿Compartir este repositorio?",
|
||||
"Shared With": "Compartido con",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador corto para el repositorio. Debe ser el mismo en todos los dispositivos del grupo.",
|
||||
"Show ID": "Mostrar ID",
|
||||
"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 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.": "Mostrado en vez del ID del dispositivo en el estado del grupo. Si dejado en blanco, será usado el nombre sugerido por el dispositivo.",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Será sugerido a otros dispositivos como nombre predeterminado opcional.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Si se deja en blanco, será usado el nombre sugerido por el dispositivo.",
|
||||
"Shutdown": "Apagar",
|
||||
"Simple File Versioning": "Versiones simple de archivos",
|
||||
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
|
||||
"Single level wildcard (matches within a directory only)": "Carácter comodín de un solo nivel (coincide sólo dentro de un directorio)",
|
||||
"Source Code": "Código fuente",
|
||||
"Staggered File Versioning": "Versiones del archivo escalonado",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
"Stopped": "Parado",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Soporte / Foro",
|
||||
"Sync Protocol Listen Addresses": "Dirección de escucha del protocolo de sincronización",
|
||||
"Synchronization": "Sincronización",
|
||||
@@ -125,9 +129,10 @@
|
||||
"Syncthing is restarting.": "Syncthing está reiniciando.",
|
||||
"Syncthing is upgrading.": "Syncthing se está actualizando.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing parece estar apagado, o hay un problema con su conexión de Internet. Reintentando...",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Las estadísticas acumuladas están públicamente disponibles en {{url}}.",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing parece estar experimentando un problema al procesar su solicitud. Por favor, recargue el navegador o reinicie Syncthing si el problema persiste.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Las estadísticas acumuladas están disponibles públicamente en {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuración ha sido guardada pero no activada.\nSyncthing debe reiniciarse para activar la nueva configuración.",
|
||||
"The device ID cannot be blank.": "El ID del dispositivo no puede estar en blanco.",
|
||||
"The device ID cannot be blank.": "La ID del dispositivo no puede estar en blanco.",
|
||||
"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.",
|
||||
@@ -144,7 +149,7 @@
|
||||
"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": "Unused",
|
||||
"Unused": "No utilizado",
|
||||
"Up to Date": "Actualizado",
|
||||
"Upgrade To {%version%}": "Actualizar a {{version}}",
|
||||
"Upgrading": "Actualizando",
|
||||
@@ -159,6 +164,6 @@
|
||||
"Yes": "Sí",
|
||||
"You must keep at least one version.": "Debe mantener al menos una versión",
|
||||
"full documentation": "documentación completa",
|
||||
"items": "Artículos",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
"items": "Ítems",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "Clé API",
|
||||
"About": "À propos",
|
||||
"Add": "Add",
|
||||
"Add": "Ajouter",
|
||||
"Add Device": "Ajouter un périphérique",
|
||||
"Add Folder": "Ajouter un répertoire",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "Ajouter un nouveau répertoire ?",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adresses",
|
||||
"Allow Anonymous Usage Reporting?": "Autoriser le rapport anonyme de statistiques d'utilisation ?",
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Mises à jour automatiques",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Utilisation du CPU",
|
||||
"Changelog": "Nouveautés",
|
||||
"Close": "Fermer",
|
||||
"Comment, when used at the start of a line": "Commentaire, lorsque utilisé en début de ligne",
|
||||
"Compression is recommended in most setups.": "La compression est recommandée pour la plupart des configurations.",
|
||||
@@ -24,7 +25,8 @@
|
||||
"Device ID": "ID du périphérique",
|
||||
"Device Identification": "Identification de l'appareil",
|
||||
"Device Name": "Nom du périphérique",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "La machine {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette machine ?",
|
||||
"Devices": "Machine",
|
||||
"Disconnected": "Déconnecté",
|
||||
"Documentation": "Documentation",
|
||||
"Download Rate": "Débit de réception",
|
||||
@@ -45,7 +47,7 @@
|
||||
"Folder ID": "ID du répertoire",
|
||||
"Folder Master": "Répertoire maître",
|
||||
"Folder Path": "Chemin du répertoire",
|
||||
"Folders": "Dossiers",
|
||||
"Folders": "Répertoires",
|
||||
"GUI Authentication Password": "Mot de passe d'authentification GUI",
|
||||
"GUI Authentication User": "Utilistateur autorisé GUI",
|
||||
"GUI Listen Addresses": "Adresse du GUI",
|
||||
@@ -54,24 +56,25 @@
|
||||
"Global Discovery Server": "Serveur global de recherche",
|
||||
"Global State": "État global",
|
||||
"Idle": "Au repos",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "Ignorer",
|
||||
"Ignore Patterns": "Modèles à éviter",
|
||||
"Ignore Permissions": "Ignorer les permissions",
|
||||
"Incoming Rate Limit (KiB/s)": "Limite du débit entrant (KiB/s)",
|
||||
"Introducer": "Initiateur",
|
||||
"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": "Later",
|
||||
"Later": "Plus tard",
|
||||
"Latest Release": "Dernière version",
|
||||
"Local Discovery": "Recherche locale",
|
||||
"Local State": "État local",
|
||||
"Maximum Age": "Ancienneté maximum",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Astérisque à plusieurs niveaux (correspond aux répertoires et sous-répertoires)",
|
||||
"Never": "Jamais",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "Nouvelle machine",
|
||||
"New Folder": "Nouveau répertoire",
|
||||
"No": "Non",
|
||||
"No File Versioning": "Pas de version de fichier",
|
||||
"Notice": "Notification",
|
||||
@@ -97,13 +100,13 @@
|
||||
"Save": "Sauver",
|
||||
"Scanning": "En cours de scan",
|
||||
"Select the devices to share this folder with.": "Sélectionner les machines avec qui partager ce répertoire.",
|
||||
"Select the folders to share with this device.": "Sélectionner les dossiers à partager avec cette machine.",
|
||||
"Select the folders to share with this device.": "Sélectionner les répertoires à partager avec cette machine.",
|
||||
"Settings": "Configuration",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share Folders With Device": "Partage du dossier avec les machines",
|
||||
"Share": "Partager",
|
||||
"Share Folder": "Partager le répertoire",
|
||||
"Share Folders With Device": "Partager des répertoires avec des machines",
|
||||
"Share With Devices": "Partager avec les machines",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "Voulez-vous partager ce répertoire ?",
|
||||
"Shared With": "Partagé avec",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Court identifiant du répertoire. Il doit être le même sur l'ensemble des machines du groupe.",
|
||||
"Show ID": "Montrer l'ID",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Versions échelonnées de fichier",
|
||||
"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",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 é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.",
|
||||
@@ -160,5 +165,5 @@
|
||||
"You must keep at least one version.": "Vous devez garder au minimum une version.",
|
||||
"full documentation": "documentation complète",
|
||||
"items": "éléments",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le répertoire \"{{folder}}\"."
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatikus frissítés",
|
||||
"Bugs": "Hibák",
|
||||
"CPU Utilization": "Processzor használat",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Bezárás",
|
||||
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
|
||||
"Compression is recommended in most setups.": "A tömörítés a a legtöbb esetben ajánlott",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Eszköz azonosító",
|
||||
"Device Name": "Eszköz neve",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Kapcsolat bontva",
|
||||
"Documentation": "Dokumentáció",
|
||||
"Download Rate": "Letöltési sebesség",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Bevezető",
|
||||
"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": "Last File Received",
|
||||
"Last File Synced": "Utolsó szinkronizált fájl",
|
||||
"Last seen": "Utoljára látva",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Többszintű fájl verziókövetés",
|
||||
"Start Browser": "Böngésző indítása",
|
||||
"Stopped": "Leállítva",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Támogatás / Fórum",
|
||||
"Sync Protocol Listen Addresses": "Szinkronizációs protokoll címe",
|
||||
"Synchronization": "Szinkronizálás",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 reload your browser or restart Syncthing if the problem persists.",
|
||||
"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.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Aggiornamenti automatici",
|
||||
"Bugs": "Bug",
|
||||
"CPU Utilization": "Utilizzo CPU",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Chiudi",
|
||||
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
|
||||
"Compression is recommended in most setups.": "La compressione è raccomandata nella maggior parte delle configurazioni.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Identificazione Dispositivo",
|
||||
"Device Name": "Nome Dispositivo",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Il dispositivo {{device}} ({{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Disconnesso",
|
||||
"Documentation": "Documentazione",
|
||||
"Download Rate": "Velocità Download",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Introduttore",
|
||||
"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": "Last File Received",
|
||||
"Last File Synced": "Ultimo File Sincronizzato",
|
||||
"Last seen": "Ultima connessione",
|
||||
"Later": "Più Tardi",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Controllo Versione Cadenzato",
|
||||
"Start Browser": "Avvia Browser",
|
||||
"Stopped": "Fermato",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Supporto / Forum",
|
||||
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
|
||||
"Synchronization": "Sincronizzazione",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 reload your browser or restart Syncthing if the problem persists.",
|
||||
"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.",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "API raktas",
|
||||
"About": "Apie programą",
|
||||
"Add": "Add",
|
||||
"Add": "Pridėti",
|
||||
"Add Device": "Pridėti įrenginį",
|
||||
"Add Folder": "Pridėti aplanką",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "Pridėti naują aplanką?",
|
||||
"Address": "Adresas",
|
||||
"Addresses": "Adresai",
|
||||
"Allow Anonymous Usage Reporting?": "Siųsti anonimišką vartojimo ataskaitą?",
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatiniai atnaujinimai",
|
||||
"Bugs": "Klaidos",
|
||||
"CPU Utilization": "Procesoriaus panaudojimas",
|
||||
"Changelog": "Pasikeitimai",
|
||||
"Close": "Uždaryti",
|
||||
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
|
||||
"Compression is recommended in most setups.": "Daugumoje atvejų spaudimas rekomenduojamas.",
|
||||
@@ -24,7 +25,8 @@
|
||||
"Device ID": "Įrenginio ID",
|
||||
"Device Identification": "Įrenginio identifikacija",
|
||||
"Device Name": "Įrenginio pavadinimas",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Įrenginys {{device}} ({{address}}) nori prisijungti. Pridėti naują įrenginį?",
|
||||
"Devices": "Įrenginiai",
|
||||
"Disconnected": "Atsijungęs",
|
||||
"Documentation": "Aprašymas",
|
||||
"Download Rate": "Parsisiuntimo greitis",
|
||||
@@ -54,24 +56,25 @@
|
||||
"Global Discovery Server": "Visuotinio matomumo serveris",
|
||||
"Global State": "Visuotinė būsena",
|
||||
"Idle": "Laisvas",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "Ignoruoti",
|
||||
"Ignore Patterns": "Nepaisyti šablonų",
|
||||
"Ignore Permissions": "Nepaisyti failų prieigos leidimų",
|
||||
"Incoming Rate Limit (KiB/s)": "Įeinančio srauto maksimalus greitis (KiB/s)",
|
||||
"Introducer": "Supažindintojas",
|
||||
"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": "Later",
|
||||
"Later": "Vėliau",
|
||||
"Latest Release": "Paskutinė versija",
|
||||
"Local Discovery": "Vietinis matomumas",
|
||||
"Local State": "Vietinė būsena",
|
||||
"Maximum Age": "Maksimalus amžius",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Keletos lygių pakaitos (atitinka keletą direktorijų lygių)",
|
||||
"Never": "Niekada",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "Naujas įrenginys",
|
||||
"New Folder": "Naujas aplankas",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Nėra versijų valdymo",
|
||||
"Notice": "Įspėjimas",
|
||||
@@ -99,11 +102,11 @@
|
||||
"Select the devices to share this folder with.": "Pasirinkite įrenginius, su kuriais dalinsitės šį aplanką.",
|
||||
"Select the folders to share with this device.": "Pasirinkite aplankus kuriais norite dalintis su šiuo įrenginiu.",
|
||||
"Settings": "Nustatymai",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share": "Dalintis",
|
||||
"Share Folder": "Dalintis aplanku",
|
||||
"Share Folders With Device": "Dalintis aplankalais su šiuo įrenginiu",
|
||||
"Share With Devices": "Dalintis su įrenginiais",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "Dalintis šiuo aplanku?",
|
||||
"Shared With": "Dalinamasi su",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Trumpas aplanko identifikatorius. Privalo būti toks pat visuose įrenginiuose.",
|
||||
"Show ID": "Rodyti ID",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Pakopinis versijų valdymas",
|
||||
"Start Browser": "Paleisti naršyklę",
|
||||
"Stopped": "Sustabdyta",
|
||||
"Support": "Pagalba",
|
||||
"Support / Forum": "Palaikymas / Diskusijos",
|
||||
"Sync Protocol Listen Addresses": "Sutapatinimo taisyklių adresas",
|
||||
"Synchronization": "Sutapatinimas",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.",
|
||||
@@ -160,5 +165,5 @@
|
||||
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
|
||||
"full documentation": "pilna dokumentacija",
|
||||
"items": "įrašai",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\""
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatiske oppdateringer",
|
||||
"Bugs": "Programfeil",
|
||||
"CPU Utilization": "CPU-utnyttelse",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Lukk",
|
||||
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
|
||||
"Compression is recommended in most setups.": "Komprimering er anbefalt i de fleste tilfeller.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Enhetskjennemerke",
|
||||
"Device Name": "Navn På Enhet",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Frakoblet",
|
||||
"Documentation": "Dokumentasjon",
|
||||
"Download Rate": "Nedlastingsrate",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Introduktør",
|
||||
"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": "Last File Received",
|
||||
"Last File Synced": "Last File Synced",
|
||||
"Last seen": "Sist sett",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Forskjøvet Versjonskontroll",
|
||||
"Start Browser": "Start Nettleser",
|
||||
"Stopped": "Stoppa",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Brukerstøtte / Forum",
|
||||
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
|
||||
"Synchronization": "Synkronisering",
|
||||
@@ -125,6 +129,7 @@
|
||||
"Syncthing is restarting.": "Syncthing starter 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 å være nede, eller så er det et problem med nettforbindelsen din. Prøver 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 reload your browser or restart Syncthing if the problem persists.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Samlet statistikk er åpent tilgjengelig på {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Innstillingene har blitt lagret men ikke aktivert. Syncthing må starte på ny for å aktivere de nye innstillingene.",
|
||||
"The device ID cannot be blank.": "Enhets-ID kan ikke være tom.",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "API-sleutel",
|
||||
"About": "Over",
|
||||
"Add": "Add",
|
||||
"Add": "Toevoegen",
|
||||
"Add Device": "Toestel toevoegen",
|
||||
"Add Folder": "Folder toevoegen",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "Nieuwe folder toevoegen?",
|
||||
"Address": "Adres",
|
||||
"Addresses": "Adressen",
|
||||
"Allow Anonymous Usage Reporting?": "Bijhouden van anonieme gebruikers statistieken toestaan?",
|
||||
@@ -13,23 +13,25 @@
|
||||
"Automatic upgrades": "Automatisch bijwerken",
|
||||
"Bugs": "Fouten",
|
||||
"CPU Utilization": "CPU Gebruik",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Sluiten",
|
||||
"Comment, when used at the start of a line": "Commentaar, indien gebruikt aan het begin van de lijn",
|
||||
"Compression is recommended in most setups.": "Gegevenscompressie is aan te raden in de meeste situaties.",
|
||||
"Connection Error": "Verbindingsfout",
|
||||
"Copied from elsewhere": "Copied from elsewhere",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copied from elsewhere": "Van elders gekopieerd",
|
||||
"Copied from original": "Gekopieerd van het origineel",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg en de onderstaande bijdragers:",
|
||||
"Delete": "Verwijderen",
|
||||
"Device ID": "Toestel ID",
|
||||
"Device Identification": "Toestel identificatie",
|
||||
"Device Name": "Naam toestel",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Het toestel {{device}} ({{address}}) wenst te verbinden. Dit toestel toevoegen?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Niet Verbonden",
|
||||
"Documentation": "Documentatie",
|
||||
"Download Rate": "Downloadsnelheid",
|
||||
"Downloaded": "Downloaded",
|
||||
"Downloading": "Downloading",
|
||||
"Downloaded": "Gedownload",
|
||||
"Downloading": "Bezig met downloaden",
|
||||
"Edit": "Bewerk",
|
||||
"Edit Device": "Toestel aanpassen",
|
||||
"Edit Folder": "Folder aanpassen",
|
||||
@@ -54,14 +56,15 @@
|
||||
"Global Discovery Server": "Globale zoekserver",
|
||||
"Global State": "Globale status",
|
||||
"Idle": "Inactief",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "Negeren",
|
||||
"Ignore Patterns": "Te negeren patronen",
|
||||
"Ignore Permissions": "Rechten negeren",
|
||||
"Incoming Rate Limit (KiB/s)": "Download snelheidslimiet (KiB/s)",
|
||||
"Introducer": "Introductietoestel",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversie van de gegeven voorwaarde (bv. niet uitsluiten)",
|
||||
"Keep Versions": "Versies behouden",
|
||||
"Last File Synced": "Last File Synced",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last File Synced": "Laatste Gesynchroniseerde Bestand",
|
||||
"Last seen": "Laatst gezien op",
|
||||
"Later": "Later",
|
||||
"Latest Release": "Laatste uitgave",
|
||||
@@ -70,8 +73,8 @@
|
||||
"Maximum Age": "Maximum leeftijd",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Wildcard op meerder niveaus (toepasbaar op meerdere niveaus van folders)",
|
||||
"Never": "Nooit",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "Nieuw Toestel",
|
||||
"New Folder": "Nieuwe Folder",
|
||||
"No": "Nee",
|
||||
"No File Versioning": "Geen versiebeheer",
|
||||
"Notice": "Notificatie",
|
||||
@@ -93,17 +96,17 @@
|
||||
"Restart": "Herstart",
|
||||
"Restart Needed": "Herstart nodig",
|
||||
"Restarting": "Herstarten",
|
||||
"Reused": "Reused",
|
||||
"Reused": "Hergebruikt",
|
||||
"Save": "Bewaar",
|
||||
"Scanning": "Aan het zoeken",
|
||||
"Select the devices to share this folder with.": "Selecteer de toestellen om deze folder mee te delen.",
|
||||
"Select the folders to share with this device.": "Select the folders to share with this device.",
|
||||
"Select the folders to share with this device.": "Selecteer de folders om met dit toestel te delen.",
|
||||
"Settings": "Instellingen",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share Folders With Device": "Share Folders With Device",
|
||||
"Share": "Delen",
|
||||
"Share Folder": "Folder Delen",
|
||||
"Share Folders With Device": "Folders delen met toestellen",
|
||||
"Share With Devices": "Delen met toestellen",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "Deze folder delen?",
|
||||
"Shared With": "Gedeeld met",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Korte aanduiding voor deze folder. Moet dezelfde zijn op alle toestellen in de cluster.",
|
||||
"Show ID": "Toon ID",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Gelaagd versiebeheer",
|
||||
"Start Browser": "Start browser",
|
||||
"Stopped": "Gestopt",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Synchronisatie protocol luister adres",
|
||||
"Synchronization": "Synchronisatie",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.",
|
||||
@@ -143,8 +148,8 @@
|
||||
"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": "Unshared",
|
||||
"Unused": "Unused",
|
||||
"Unshared": "Niet gedeeld",
|
||||
"Unused": "Ongebruikt",
|
||||
"Up to Date": "Gesynchroniseerd",
|
||||
"Upgrade To {%version%}": "Upgrade naar {{version}}",
|
||||
"Upgrading": "Bezig met upgrade",
|
||||
@@ -160,5 +165,5 @@
|
||||
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
|
||||
"full documentation": "volledige documentatie",
|
||||
"items": "objecten",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de folder \"{{folder}}\" delen."
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatiske oppdateringar",
|
||||
"Bugs": "Programfeil",
|
||||
"CPU Utilization": "CPU-utnytting",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Lukk",
|
||||
"Comment, when used at the start of a line": "Kommentar, når det vert brukt i starten av ei linje.",
|
||||
"Compression is recommended in most setups.": "Komprimering er tilrådd i dei fleste høve.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Einingskjennemerke",
|
||||
"Device Name": "Namn På Eining",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Fråkopla",
|
||||
"Documentation": "Dokumentasjon",
|
||||
"Download Rate": "Nedlastingsrate",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Introduktør",
|
||||
"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": "Last File Received",
|
||||
"Last File Synced": "Last File Synced",
|
||||
"Last seen": "Sist sett",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Forskyvd Versjonskontroll",
|
||||
"Start Browser": "Start Nettlesar",
|
||||
"Stopped": "Stoppa",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Brukarstøtte / Forum",
|
||||
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
|
||||
"Synchronization": "Synkronisering",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatyczne aktualizacje",
|
||||
"Bugs": "Błędy",
|
||||
"CPU Utilization": "Użycie CPU",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Zamknij",
|
||||
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
|
||||
"Compression is recommended in most setups.": "Kompresja jest zalecana w większości przypadków",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Identyfikator urządzenia",
|
||||
"Device Name": "Nazwa urządzenia",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Rozłączony",
|
||||
"Documentation": "Dokumentacja",
|
||||
"Download Rate": "Prędkość pobierania",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Wprowadzający",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Odwrócenie podanego wzorca (np. nie wykluczaj)",
|
||||
"Keep Versions": "Zachowuj wersje",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last File Synced": "Last File Synced",
|
||||
"Last seen": "Ostatnio widziany",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Rozbudowane wersjonowanie pliku",
|
||||
"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",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "Chave da API",
|
||||
"About": "Acerca da aplicação",
|
||||
"Add": "Add",
|
||||
"Add": "Adicionar",
|
||||
"Add Device": "Adicionar dispositivo",
|
||||
"Add Folder": "Adicionar pasta",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "Adicionar nova pasta?",
|
||||
"Address": "Endereço",
|
||||
"Addresses": "Endereços",
|
||||
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anónimos de utilização?",
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Actualizações automáticas",
|
||||
"Bugs": "Erros",
|
||||
"CPU Utilization": "Utilização da CPU",
|
||||
"Changelog": "Registo de alterações",
|
||||
"Close": "Fechar",
|
||||
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
|
||||
"Compression is recommended in most setups.": "A compressão é recomendada na maior parte dos casos.",
|
||||
@@ -24,7 +25,8 @@
|
||||
"Device ID": "ID do dispositivo",
|
||||
"Device Identification": "Identificação do dispositivo",
|
||||
"Device Name": "Nome do dispositivo",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer conectar-se. Adiciono este novo dispositivo?",
|
||||
"Devices": "Dispositivos",
|
||||
"Disconnected": "Desconectado",
|
||||
"Documentation": "Documentação",
|
||||
"Download Rate": "Velocidade de recepção",
|
||||
@@ -54,24 +56,25 @@
|
||||
"Global Discovery Server": "Servidor da busca global",
|
||||
"Global State": "Estado global",
|
||||
"Idle": "Em espera",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "Ignorar",
|
||||
"Ignore Patterns": "Padrões de exclusão",
|
||||
"Ignore Permissions": "Ignorar permissões",
|
||||
"Incoming Rate Limit (KiB/s)": "Limite de velocidade de recepção (KiB/s)",
|
||||
"Introducer": "Apresentador",
|
||||
"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": "Later",
|
||||
"Later": "Mais tarde",
|
||||
"Latest Release": "Última versão",
|
||||
"Local Discovery": "Busca local",
|
||||
"Local State": "Estado local",
|
||||
"Maximum Age": "Idade máxima",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Caractere polivalente multi-nível (faz corresponder a vários níveis de pastas)",
|
||||
"Never": "Nunca",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "Novo dispositivo",
|
||||
"New Folder": "Nova pasta",
|
||||
"No": "Não",
|
||||
"No File Versioning": "Sem gestão de versões de ficheiros",
|
||||
"Notice": "Avisos",
|
||||
@@ -99,11 +102,11 @@
|
||||
"Select the devices to share this folder with.": "Seleccione os dispositivos com os quais vai partilhar esta pasta.",
|
||||
"Select the folders to share with this device.": "Seleccione as pastas a partilhar com este dispositivo.",
|
||||
"Settings": "Configurações",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share": "Partilhar",
|
||||
"Share Folder": "Partilhar pasta",
|
||||
"Share Folders With Device": "Partilhar pastas com dispositivo",
|
||||
"Share With Devices": "Partilhar com os dispositivos",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "Partilhar esta pasta?",
|
||||
"Shared With": "Partilhado com",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curto para a pasta. Tem que ser igual em todos os dispositivos do grupo.",
|
||||
"Show ID": "Mostrar ID",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Gestão de versões de ficheiros escalonada",
|
||||
"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",
|
||||
@@ -125,6 +129,7 @@
|
||||
"Syncthing is restarting.": "O Syncthing está a reiniciar.",
|
||||
"Syncthing is upgrading.": "O Syncthing está a actualizar-se.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "O Syncthing parece estar em baixo, ou então existe um problema com a sua ligação à Internet. Tentando novamente...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "O Syncthing parece estar com problemas em processar o seu pedido. Tente recarregar a página ou reiniciar o Syncthing, se o problema persistir.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "As estatísticas agrupadas estão disponíveis publicamente em {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi gravada mas não activada. O Syncthing tem que reiniciar para activar a nova configuração.",
|
||||
"The device ID cannot be blank.": "O ID do dispositivo não pode estar vazio.",
|
||||
@@ -160,5 +165,5 @@
|
||||
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
|
||||
"full documentation": "documentação completa",
|
||||
"items": "itens",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\"."
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Автообновление",
|
||||
"Bugs": "Ошибки",
|
||||
"CPU Utilization": "Загрузка ЦПУ",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Закрыть",
|
||||
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
|
||||
"Compression is recommended in most setups.": "Сжатие рекомендуется в большинстве случаев.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Идентификация устройства",
|
||||
"Device Name": "Имя устройства",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Нет соединения",
|
||||
"Documentation": "Документация",
|
||||
"Download Rate": "Скорость загрузки",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "Рекомендатель",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Инвертировать текущее условие (например, исключить)",
|
||||
"Keep Versions": "Количество хранимых версий",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last File Synced": "Последний синхронизированный файл",
|
||||
"Last seen": "Был доступен",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
|
||||
"Start Browser": "Открыть браузер",
|
||||
"Stopped": "Остановлено",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Поддержка / Форум",
|
||||
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
|
||||
"Synchronization": "Синхронизация",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.": "ID устройства не может быть пустым.",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "API-nyckel",
|
||||
"About": "Om",
|
||||
"Add": "Add",
|
||||
"Add": "Lägg till",
|
||||
"Add Device": "Lägg till enhet",
|
||||
"Add Folder": "Lägg till katalog",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "Lägg till katalog?",
|
||||
"Address": "Adress",
|
||||
"Addresses": "Adresser",
|
||||
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "Automatisk uppgradering",
|
||||
"Bugs": "Buggar",
|
||||
"CPU Utilization": "CPU-användning",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "Stäng",
|
||||
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
|
||||
"Compression is recommended in most setups.": "Komprimering är rekommenderat för de flesta.",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "Enhetsidentifikation",
|
||||
"Device Name": "Enhetsnamn",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "Ej ansluten",
|
||||
"Documentation": "Dokumentation",
|
||||
"Download Rate": "Nedladdningshastighet",
|
||||
@@ -54,24 +56,25 @@
|
||||
"Global Discovery Server": "Global uppslagningsserver",
|
||||
"Global State": "Global status",
|
||||
"Idle": "Vilande",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "Ignorera",
|
||||
"Ignore Patterns": "Filmönster",
|
||||
"Ignore Permissions": "Ignorera filrättigheter",
|
||||
"Incoming Rate Limit (KiB/s)": "Max nedladdningshastighet (KiB/s)",
|
||||
"Introducer": "Introducer",
|
||||
"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": "Last File Received",
|
||||
"Last File Synced": "Senast uppdaterad fil",
|
||||
"Last seen": "Senast online",
|
||||
"Later": "Later",
|
||||
"Later": "Senare",
|
||||
"Latest Release": "Senaste version",
|
||||
"Local Discovery": "Lokal uppslagning",
|
||||
"Local State": "Lokal status",
|
||||
"Maximum Age": "Högsta åldersgräns",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
|
||||
"Never": "Aldrig",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "Ny enhet",
|
||||
"New Folder": "Ny katalog",
|
||||
"No": "Nej",
|
||||
"No File Versioning": "Ingen versionshantering",
|
||||
"Notice": "OBS",
|
||||
@@ -99,11 +102,11 @@
|
||||
"Select the devices to share this folder with.": "Ange enheterna att dela den här katalogen med.",
|
||||
"Select the folders to share with this device.": "Välja kataloger att dela med den här enheten",
|
||||
"Settings": "Inställningar",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share": "Dela",
|
||||
"Share Folder": "Dela katalog",
|
||||
"Share Folders With Device": "Dela kataloger med enhet",
|
||||
"Share With Devices": "Dela med enheter",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "Dela den här katalogen?",
|
||||
"Shared With": "Delat med",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort identifieringssträng för katalogen. Måste vara samma på alla enheter i klustern.",
|
||||
"Show ID": "Visa ID",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "Versionshantering i intervall",
|
||||
"Start Browser": "Starta browser",
|
||||
"Stopped": "Stoppad",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "Support / Forum",
|
||||
"Sync Protocol Listen Addresses": "Address för inkommande anslutningar",
|
||||
"Synchronization": "Synkronisering",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 så är där ett 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 reload your browser or restart Syncthing if the problem persists.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "Aggregerad statistik finns publikt tillgänglig på {{url}}.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
|
||||
"The device ID cannot be blank.": "Enhets-ID kan inte vara blankt.",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"Automatic upgrades": "自动升级",
|
||||
"Bugs": "Bug汇报",
|
||||
"CPU Utilization": "CPU使用率",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "关闭",
|
||||
"Comment, when used at the start of a line": "注释,在行首使用",
|
||||
"Compression is recommended in most setups.": "在大多数场合,建议开启压缩",
|
||||
@@ -25,6 +26,7 @@
|
||||
"Device Identification": "设备ID",
|
||||
"Device Name": "设备名",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "连接已断开",
|
||||
"Documentation": "文档",
|
||||
"Download Rate": "下载速度",
|
||||
@@ -61,6 +63,7 @@
|
||||
"Introducer": "介绍人节点",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "对本条件取反(例如:不要排除某项)",
|
||||
"Keep Versions": "保留历史版本数量",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last File Synced": "最近同步的文件",
|
||||
"Last seen": "最后可见",
|
||||
"Later": "Later",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "阶段版本控制",
|
||||
"Start Browser": "启动浏览器",
|
||||
"Stopped": "已停止",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "支持/论坛",
|
||||
"Sync Protocol Listen Addresses": "协议监听地址",
|
||||
"Synchronization": "同步完成度",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.": "设备ID不能为空",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"API Key": "API 金鑰",
|
||||
"About": "關於",
|
||||
"Add": "Add",
|
||||
"Add": "增加",
|
||||
"Add Device": "增加裝置",
|
||||
"Add Folder": "增加資料夾",
|
||||
"Add new folder?": "Add new folder?",
|
||||
"Add new folder?": "新增資料夾?",
|
||||
"Address": "位址",
|
||||
"Addresses": "位址",
|
||||
"Allow Anonymous Usage Reporting?": "允許匿名的使用資訊回報?",
|
||||
@@ -13,18 +13,20 @@
|
||||
"Automatic upgrades": "自動升級",
|
||||
"Bugs": "程式錯誤",
|
||||
"CPU Utilization": "CPU 使用率",
|
||||
"Changelog": "Changelog",
|
||||
"Close": "關閉",
|
||||
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
|
||||
"Compression is recommended in most setups.": "建議在大多數的設置中使用壓縮。",
|
||||
"Connection Error": "連線錯誤",
|
||||
"Copied from elsewhere": "Copied from elsewhere",
|
||||
"Copied from elsewhere": "從別處複製",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "版權所有 © 2014 Jakob Borg 及以下貢獻者:",
|
||||
"Delete": "刪除",
|
||||
"Device ID": "裝置識別碼",
|
||||
"Device Identification": "裝置識別",
|
||||
"Device Name": "裝置名稱",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "裝置 {{device}} ({{address}}) 想要連線。要新增裝置嗎?",
|
||||
"Devices": "Devices",
|
||||
"Disconnected": "斷線",
|
||||
"Documentation": "說明文件",
|
||||
"Download Rate": "下載速率",
|
||||
@@ -54,24 +56,25 @@
|
||||
"Global Discovery Server": "全域探索伺服器",
|
||||
"Global State": "全域狀態",
|
||||
"Idle": "閒置",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore": "忽略",
|
||||
"Ignore Patterns": "忽略樣式",
|
||||
"Ignore Permissions": "忽略權限",
|
||||
"Incoming Rate Limit (KiB/s)": "連入速率限制 (KiB/s)",
|
||||
"Introducer": "引入者",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "反轉給定條件 (即:不要排除)",
|
||||
"Keep Versions": "保留歷史版本數",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last File Synced": "最後同步檔案",
|
||||
"Last seen": "最後發現時間",
|
||||
"Later": "Later",
|
||||
"Later": "稍後",
|
||||
"Latest Release": "最新發佈",
|
||||
"Local Discovery": "本地探索",
|
||||
"Local State": "本地狀態",
|
||||
"Maximum Age": "最長保留時間",
|
||||
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
|
||||
"Never": "從未",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"New Device": "新裝置",
|
||||
"New Folder": "新資料夾",
|
||||
"No": "否",
|
||||
"No File Versioning": "無檔案版本控制",
|
||||
"Notice": "注意",
|
||||
@@ -99,11 +102,11 @@
|
||||
"Select the devices to share this folder with.": "選擇要共享這個資料夾的裝置。",
|
||||
"Select the folders to share with this device.": "選擇要共享這個資料夾的裝置。",
|
||||
"Settings": "設定",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share": "分享",
|
||||
"Share Folder": "分享資料夾",
|
||||
"Share Folders With Device": "與裝置共享資料夾",
|
||||
"Share With Devices": "與這些裝置共享",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Share this folder?": "分享此資料夾?",
|
||||
"Shared With": "與誰共享",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "資料夾的簡短識別字。必須在叢集內所有的裝置上皆相同。",
|
||||
"Show ID": "顯示識別碼",
|
||||
@@ -116,6 +119,7 @@
|
||||
"Staggered File Versioning": "變動式檔案版本控制",
|
||||
"Start Browser": "啟動瀏覽器",
|
||||
"Stopped": "已停止",
|
||||
"Support": "Support",
|
||||
"Support / Forum": "支援 / 論壇",
|
||||
"Sync Protocol Listen Addresses": "同步通訊協定監聽位址",
|
||||
"Synchronization": "同步作業",
|
||||
@@ -125,6 +129,7 @@
|
||||
"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 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.": "裝置識別碼不能為空白。",
|
||||
@@ -160,5 +165,5 @@
|
||||
"You must keep at least one version.": "您必須保留至少一個版本。",
|
||||
"full documentation": "完整說明文件",
|
||||
"items": "個項目",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\"。"
|
||||
}
|
||||
@@ -89,19 +89,20 @@
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel-group" id="folders">
|
||||
<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>
|
||||
<h3 class="panel-title">
|
||||
<span class="glyphicon glyphicon-hdd"></span> {{folder.ID}}
|
||||
<span class="pull-right hidden-xs text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
|
||||
<span translate ng-switch-when="unknown">Unknown</span>
|
||||
<span translate ng-switch-when="unshared">Unshared</span>
|
||||
<span translate ng-switch-when="stopped">Stopped</span>
|
||||
<span translate ng-switch-when="scanning">Scanning</span>
|
||||
<span translate ng-switch-when="idle">Up to Date</span>
|
||||
<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">◼</span></span>
|
||||
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="scanning"><span class="hidden-xs" translate>Scanning</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="syncing">
|
||||
<span translate>Syncing</span>
|
||||
<span class="hidden-xs" translate>Syncing</span>
|
||||
({{syncPercentage(folder.ID)}}%)
|
||||
</span>
|
||||
</span>
|
||||
@@ -154,7 +155,7 @@
|
||||
<td class="text-right">{{sharesFolder(folder)}}</td>
|
||||
</tr>
|
||||
<tr ng-if="folderStats[folder.ID].LastFile">
|
||||
<th><span class="glyphicon glyphicon-transfer"></span> <span translate>Last File Synced</span></th>
|
||||
<th><span class="glyphicon glyphicon-transfer"></span> <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}}
|
||||
@@ -187,6 +188,7 @@
|
||||
<!-- This device -->
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="visible-xs"><h3><span translate>Devices</span></h3><hr></div>
|
||||
<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">
|
||||
@@ -244,13 +246,13 @@
|
||||
<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> {{deviceName(deviceCfg)}}
|
||||
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right hidden-xs text-{{deviceClass(deviceCfg)}}">
|
||||
<span translate ng-switch-when="insync">Up to Date</span>
|
||||
<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">◼</span></span>
|
||||
<span ng-switch-when="syncing">
|
||||
<span translate>Syncing</span> ({{completion[deviceCfg.DeviceID]._total | number:0}}%)
|
||||
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.DeviceID]._total | number:0}}%)
|
||||
</span>
|
||||
<span translate ng-switch-when="disconnected">Disconnected</span>
|
||||
<span translate ng-switch-when="unused">Unused</span>
|
||||
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs">◼</span></span>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -396,14 +398,14 @@
|
||||
|
||||
<!-- Bottom bar -->
|
||||
|
||||
<nav class="navbar navbar-default navbar-fixed-bottom hidden-xs">
|
||||
<nav class="navbar navbar-default navbar-fixed-bottom">
|
||||
<div class="container">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a class="navbar-link" href="http://discourse.syncthing.net/"><span translate>Support / Forum</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases"><span translate>Latest Release</span></a></li>
|
||||
<li><a class="navbar-link" href="http://discourse.syncthing.net/category/documentation"><span translate>Documentation</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues"><span translate>Bugs</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing"><span translate>Source Code</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/wiki"><span class="glyphicon glyphicon-book"></span> <span translate>Documentation</span></a></li>
|
||||
<li><a class="navbar-link" href="https://discourse.syncthing.net"><span class="glyphicon glyphicon-question-sign"></span> <span translate>Support</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases"><span class="glyphicon glyphicon-info-sign"></span> <span translate>Changelog</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues"><span class="glyphicon glyphicon-warning-sign"></span> <span translate>Bugs</span></a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing"><span class="glyphicon glyphicon-wrench"></span> <span translate>Source Code</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -416,6 +418,14 @@
|
||||
</p>
|
||||
</modal>
|
||||
|
||||
<!-- HTTP error modal -->
|
||||
|
||||
<modal id="httpError" status="danger" icon="exclamation-sign" title="{{'Connection Error' | translate}}">
|
||||
<p translate>
|
||||
Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.
|
||||
</p>
|
||||
</modal>
|
||||
|
||||
<!-- Restarting modal -->
|
||||
|
||||
<modal id="restarting" icon="refresh" title="{{'Restarting' | translate}}" status="info">
|
||||
@@ -430,7 +440,7 @@
|
||||
|
||||
<!-- Shutdown modal -->
|
||||
|
||||
<modal id="shutdown" icon="off" status="success" title="Shutdown Complete">
|
||||
<modal id="shutdown" icon="off" status="success" title="{{'Shutdown Complete' | translate}}">
|
||||
<p translate>Syncthing has been shut down.</p>
|
||||
</modal>
|
||||
|
||||
@@ -673,7 +683,7 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://discourse.syncthing.net/t/80" translate>full documentation</a>):</p>
|
||||
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://github.com/syncthing/syncthing/wiki/Ignoring-Files" target="_blank" translate>full documentation</a>):</p>
|
||||
<dl class="dl-horizontal dl-narrow small">
|
||||
<dt><code>!</code></dt> <dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
|
||||
<dt><code>*</code></dt> <dd><span translate>Single level wildcard (matches within a directory only)</span></dd>
|
||||
@@ -682,7 +692,7 @@
|
||||
</dl>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.Path}}/.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> <span translate>Save</span></button>
|
||||
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||
</div>
|
||||
@@ -856,7 +866,7 @@
|
||||
|
||||
<!-- Needed files modal -->
|
||||
|
||||
<modal id="needed" large="yes" status="info" icon="cloud-download" close="yes" title="Out of Sync Items">
|
||||
<modal id="needed" large="yes" status="info" icon="cloud-download" close="yes" title="{{'Out of Sync Items' | translate}}">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-success" style="width: 20%"><span translate class="show">Reused</span></div>
|
||||
<div class="progress-bar" style="width: 20%"><span translate class="show">Copied from original</span></div>
|
||||
@@ -906,7 +916,7 @@
|
||||
|
||||
<!-- About modal -->
|
||||
|
||||
<modal id="about" large="yes" close="yes" status="info" title="About">
|
||||
<modal id="about" large="yes" close="yes" status="info" title="{{'About' | translate}}">
|
||||
<h1 class="text-center"><img alt="Syncthing" title="Syncthing" src="assets/img/logo-text-256.png" style="vertical-align: -16px" height="100" width="366"/><br/><small>{{version}}</small></h1>
|
||||
<hr/>
|
||||
|
||||
@@ -922,6 +932,7 @@
|
||||
<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>Chris Joel</li>
|
||||
@@ -929,6 +940,7 @@
|
||||
<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>
|
||||
@@ -938,10 +950,12 @@
|
||||
<li>Lode Hoste</li>
|
||||
<li>Marcin Dziadus</li>
|
||||
<li>Michael Tilli</li>
|
||||
<li>Peter Hoeg</li>
|
||||
<li>Philippe Schommers</li>
|
||||
<li>Phill Luby</li>
|
||||
<li>Piotr Bejda</li>
|
||||
<li>Ryan Sullivan</li>
|
||||
<li>Tim Abell</li>
|
||||
<li>Tomas Cerveny</li>
|
||||
<li>Tully Robinson</li>
|
||||
<li>Veeti Paananen</li>
|
||||
|
||||
@@ -10,7 +10,6 @@ angular.module('syncthing.core')
|
||||
var restarting = false;
|
||||
|
||||
function initController() {
|
||||
|
||||
LocaleService.autoConfigLocale();
|
||||
|
||||
refreshSystem();
|
||||
@@ -21,11 +20,11 @@ angular.module('syncthing.core')
|
||||
|
||||
$http.get(urlbase + '/version').success(function (data) {
|
||||
$scope.version = data.version;
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
|
||||
$http.get(urlbase + '/report').success(function (data) {
|
||||
$scope.reportData = data;
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
|
||||
$http.get(urlbase + '/upgrade').success(function (data) {
|
||||
$scope.upgradeInfo = data;
|
||||
@@ -106,6 +105,30 @@ angular.module('syncthing.core')
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('HTTPError', function (event, arg) {
|
||||
// Emitted when a HTTP call fails. We use the status code to try
|
||||
// to figure out what's wrong.
|
||||
|
||||
if (navigatingAway || !online) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('HTTPError', arg);
|
||||
online = false;
|
||||
if (!restarting) {
|
||||
if (arg.status === 0) {
|
||||
// A network error, not an HTTP error
|
||||
$scope.$emit('UIOffline');
|
||||
} else if (arg.status >= 400 && arg.status <= 599) {
|
||||
// A genuine HTTP error
|
||||
$('#networkError').modal('hide');
|
||||
$('#restarting').modal('hide');
|
||||
$('#shutdown').modal('hide');
|
||||
$('#httpError').modal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('StateChanged', function (event, arg) {
|
||||
var data = arg.data;
|
||||
if ($scope.model[data.folder]) {
|
||||
@@ -183,7 +206,7 @@ angular.module('syncthing.core')
|
||||
|
||||
$http.get(urlbase + '/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
});
|
||||
|
||||
$scope.$on('DownloadProgress', function (event, arg) {
|
||||
@@ -233,6 +256,10 @@ angular.module('syncthing.core')
|
||||
console.log("DownloadProgress", $scope.progress);
|
||||
});
|
||||
|
||||
$scope.emitHTTPError = function (data, status, headers, config) {
|
||||
$scope.$emit('HTTPError', {data: data, status: status, headers: headers, config: config});
|
||||
};
|
||||
|
||||
var debouncedFuncs = {};
|
||||
|
||||
function refreshFolder(folder) {
|
||||
@@ -242,7 +269,7 @@ angular.module('syncthing.core')
|
||||
$http.get(urlbase + '/model?folder=' + encodeURIComponent(folder)).success(function (data) {
|
||||
$scope.model[folder] = data;
|
||||
console.log("refreshFolder", folder, data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}, 1000, true);
|
||||
}
|
||||
debouncedFuncs[key]();
|
||||
@@ -289,7 +316,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
$scope.announceServersFailed = failed;
|
||||
console.log("refreshSystem", data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}
|
||||
|
||||
function refreshCompletion(device, folder) {
|
||||
@@ -318,7 +345,7 @@ angular.module('syncthing.core')
|
||||
$scope.completion[device]._total = tot / cnt;
|
||||
|
||||
console.log("refreshCompletion", device, folder, $scope.completion[device]);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}, 1000, true);
|
||||
}
|
||||
debouncedFuncs[key]();
|
||||
@@ -345,25 +372,25 @@ angular.module('syncthing.core')
|
||||
}
|
||||
$scope.connections = data;
|
||||
console.log("refreshConnections", data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}
|
||||
|
||||
function refreshErrors() {
|
||||
$http.get(urlbase + '/errors').success(function (data) {
|
||||
$scope.errors = data.errors;
|
||||
console.log("refreshErrors", data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}
|
||||
|
||||
function refreshConfig() {
|
||||
$http.get(urlbase + '/config').success(function (data) {
|
||||
updateLocalConfig(data);
|
||||
console.log("refreshConfig", data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
|
||||
$http.get(urlbase + '/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}
|
||||
|
||||
function refreshNeed(folder) {
|
||||
@@ -372,7 +399,7 @@ angular.module('syncthing.core')
|
||||
console.log("refreshNeed", folder, data);
|
||||
$scope.needed = data;
|
||||
}
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}
|
||||
|
||||
var refreshDeviceStats = debounce(function () {
|
||||
@@ -383,7 +410,7 @@ angular.module('syncthing.core')
|
||||
$scope.deviceStats[device].LastSeenDays = (new Date() - $scope.deviceStats[device].LastSeen) / 1000 / 86400;
|
||||
}
|
||||
console.log("refreshDeviceStats", data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}, 500);
|
||||
|
||||
var refreshFolderStats = debounce(function () {
|
||||
@@ -395,7 +422,7 @@ angular.module('syncthing.core')
|
||||
}
|
||||
}
|
||||
console.log("refreshfolderStats", data);
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
}, 500);
|
||||
|
||||
$scope.refresh = function () {
|
||||
@@ -576,7 +603,7 @@ angular.module('syncthing.core')
|
||||
$http.get(urlbase + '/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$scope.saveSettings = function () {
|
||||
@@ -656,7 +683,7 @@ angular.module('syncthing.core')
|
||||
restarting = true;
|
||||
$http.post(urlbase + '/shutdown').success(function () {
|
||||
$('#shutdown').modal();
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
$scope.configInSync = true;
|
||||
};
|
||||
|
||||
@@ -857,11 +884,14 @@ angular.module('syncthing.core')
|
||||
params: { current: newvalue }
|
||||
}).success(function (data) {
|
||||
$scope.directoryList = data;
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
});
|
||||
|
||||
$scope.editFolder = function (folderCfg) {
|
||||
$scope.currentFolder = angular.copy(folderCfg);
|
||||
if ($scope.currentFolder.Path.slice(-1) == $scope.system.pathSeparator) {
|
||||
$scope.currentFolder.Path = $scope.currentFolder.Path.slice(0, -1);
|
||||
}
|
||||
$scope.currentFolder.selectedDevices = {};
|
||||
$scope.currentFolder.Devices.forEach(function (n) {
|
||||
$scope.currentFolder.selectedDevices[n.DeviceID] = true;
|
||||
@@ -1137,7 +1167,7 @@ angular.module('syncthing.core')
|
||||
console.log("bumpFile", folder, data);
|
||||
$scope.needed = data;
|
||||
}
|
||||
});
|
||||
}).error($scope.emitHTTPError);
|
||||
};
|
||||
|
||||
// pseudo main. called on all definitions assigned
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -60,9 +60,9 @@ type FolderConfiguration struct {
|
||||
IgnorePerms bool `xml:"ignorePerms,attr"`
|
||||
Versioning VersioningConfiguration `xml:"versioning"`
|
||||
LenientMtimes bool `xml:"lenientMtimes"`
|
||||
Copiers int `xml:"copiers" default:"1"` // This defines how many files are handled concurrently.
|
||||
Pullers int `xml:"pullers" default:"16"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
|
||||
Finishers int `xml:"finishers" default:"1"` // Most of the time, should be equal to the number of copiers. These are CPU bound due to hashing.
|
||||
Copiers int `xml:"copiers" default:"1"` // This defines how many files are handled concurrently.
|
||||
Pullers int `xml:"pullers" default:"16"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
|
||||
Hashers int `xml:"hashers" default:"0"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
|
||||
|
||||
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
|
||||
|
||||
@@ -358,9 +358,6 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
|
||||
if cfg.Folders[i].Pullers == 0 {
|
||||
cfg.Folders[i].Pullers = 16
|
||||
}
|
||||
if cfg.Folders[i].Finishers == 0 {
|
||||
cfg.Folders[i].Finishers = 1
|
||||
}
|
||||
sort.Sort(FolderDeviceConfigurationList(cfg.Folders[i].Devices))
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestDeviceConfig(t *testing.T) {
|
||||
RescanIntervalS: 600,
|
||||
Copiers: 1,
|
||||
Pullers: 16,
|
||||
Finishers: 1,
|
||||
Hashers: 0,
|
||||
},
|
||||
}
|
||||
expectedDevices := []DeviceConfiguration{
|
||||
|
||||
@@ -94,7 +94,7 @@ func (d *UDPClient) broadcast(pkt []byte) {
|
||||
|
||||
conn, err := net.ListenUDP(d.url.Scheme, d.listenAddress)
|
||||
for err != nil {
|
||||
l.Warnf("Global UDP discovery (%s): %v; trying again in %v", d.url, err, d.errorRetryInterval)
|
||||
l.Warnf("discover %s: broadcast: %v; trying again in %v", d.url, err, d.errorRetryInterval)
|
||||
select {
|
||||
case <-d.stop:
|
||||
return
|
||||
@@ -106,7 +106,7 @@ func (d *UDPClient) broadcast(pkt []byte) {
|
||||
|
||||
remote, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host)
|
||||
for err != nil {
|
||||
l.Warnf("Global UDP discovery (%s): %v; trying again in %v", d.url, err, d.errorRetryInterval)
|
||||
l.Warnf("discover %s: broadcast: %v; trying again in %v", d.url, err, d.errorRetryInterval)
|
||||
select {
|
||||
case <-d.stop:
|
||||
return
|
||||
@@ -125,13 +125,13 @@ func (d *UDPClient) broadcast(pkt []byte) {
|
||||
var ok bool
|
||||
|
||||
if debug {
|
||||
l.Debugf("Global UDP discovery (%s): send announcement -> %v\n%s", d.url, remote, hex.Dump(pkt))
|
||||
l.Debugf("discover %s: broadcast: Sending self announcement to %v", d.url, remote)
|
||||
}
|
||||
|
||||
_, err := conn.WriteTo(pkt, remote)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover %s: warning: %s", d.url, err)
|
||||
l.Debugf("discover %s: broadcast: Failed to send self announcement: %s", d.url, err)
|
||||
}
|
||||
ok = false
|
||||
} else {
|
||||
@@ -141,7 +141,7 @@ func (d *UDPClient) broadcast(pkt []byte) {
|
||||
|
||||
res := d.Lookup(d.id)
|
||||
if debug {
|
||||
l.Debugf("discover %s: external lookup check: %v", d.url, res)
|
||||
l.Debugf("discover %s: broadcast: Self-lookup returned: %v", d.url, res)
|
||||
}
|
||||
ok = len(res) > 0
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
|
||||
extIP, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover %s: %v; no external lookup", d.url, err)
|
||||
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
|
||||
conn, err := net.DialUDP(d.url.Scheme, d.listenAddress, extIP)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover %s: %v; no external lookup", d.url, err)
|
||||
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
|
||||
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover %s: %v; no external lookup", d.url, err)
|
||||
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugf("discover %s: %v; no external lookup", d.url, err)
|
||||
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -202,20 +202,16 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
|
||||
return nil
|
||||
}
|
||||
if debug {
|
||||
l.Debugf("discover %s: %v; no external lookup", d.url, err)
|
||||
l.Debugf("discover %s: Lookup(%s): %s", d.url, device, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover %s: read external:\n%s", d.url, hex.Dump(buf[:n]))
|
||||
}
|
||||
|
||||
var pkt Announce
|
||||
err = pkt.UnmarshalXDR(buf[:n])
|
||||
if err != nil && err != io.EOF {
|
||||
if debug {
|
||||
l.Debugln("discover %s:", d.url, err)
|
||||
l.Debugf("discover %s: Lookup(%s): %s\n%s", d.url, device, err, hex.Dump(buf[:n]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -225,6 +221,9 @@ func (d *UDPClient) Lookup(device protocol.DeviceID) []string {
|
||||
deviceAddr := net.JoinHostPort(net.IP(a.IP).String(), strconv.Itoa(int(a.Port)))
|
||||
addrs = append(addrs, deviceAddr)
|
||||
}
|
||||
if debug {
|
||||
l.Debugf("discover %s: Lookup(%s) result: %v", d.url, device, addrs)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func (d *Discoverer) StartLocal(localPort int, localMCAddr string) {
|
||||
bb, err := beacon.NewBroadcast(localPort)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
l.Debugln("discover: Start local v4:", err)
|
||||
}
|
||||
l.Infoln("Local discovery over IPv4 unavailable")
|
||||
} else {
|
||||
@@ -85,7 +85,7 @@ func (d *Discoverer) StartLocal(localPort int, localMCAddr string) {
|
||||
mb, err := beacon.NewMulticast(localMCAddr)
|
||||
if err != nil {
|
||||
if debug {
|
||||
l.Debugln(err)
|
||||
l.Debugln("discover: Start local v6:", err)
|
||||
}
|
||||
l.Infoln("Local discovery over IPv6 unavailable")
|
||||
} else {
|
||||
@@ -247,10 +247,10 @@ func (d *Discoverer) announcementPkt() *Announce {
|
||||
for _, astr := range d.listenAddrs {
|
||||
addr, err := net.ResolveTCPAddr("tcp", astr)
|
||||
if err != nil {
|
||||
l.Warnln("%v: not announcing %s", err, astr)
|
||||
l.Warnln("discover: %v: not announcing %s", err, astr)
|
||||
continue
|
||||
} else if debug {
|
||||
l.Debugf("discover: announcing %s: %#v", astr, addr)
|
||||
l.Debugf("discover: resolved %s as %#v", astr, addr)
|
||||
}
|
||||
if len(addr.IP) == 0 || addr.IP.IsUnspecified() {
|
||||
addrs = append(addrs, Address{Port: uint16(addr.Port)})
|
||||
@@ -295,16 +295,19 @@ func (d *Discoverer) recvAnnouncements(b beacon.Interface) {
|
||||
for {
|
||||
buf, addr := b.Recv()
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: read announcement from %s:\n%s", addr, hex.Dump(buf))
|
||||
}
|
||||
|
||||
var pkt Announce
|
||||
err := pkt.UnmarshalXDR(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
if debug {
|
||||
l.Debugf("discover: Failed to unmarshal local announcement from %s:\n%s", addr, hex.Dump(buf))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.This.ID))
|
||||
}
|
||||
|
||||
var newDevice bool
|
||||
if bytes.Compare(pkt.This.ID, d.myID[:]) != 0 {
|
||||
newDevice = d.registerDevice(addr, pkt.This)
|
||||
@@ -352,7 +355,7 @@ func (d *Discoverer) registerDevice(addr net.Addr, device Device) bool {
|
||||
}
|
||||
|
||||
if debug {
|
||||
l.Debugf("discover: register: %v -> %v", id, current)
|
||||
l.Debugf("discover: Caching %s addresses: %v", id, current)
|
||||
}
|
||||
|
||||
d.registry[id] = current
|
||||
@@ -375,7 +378,7 @@ func (d *Discoverer) filterCached(c []CacheEntry) []CacheEntry {
|
||||
for i := 0; i < len(c); {
|
||||
if ago := time.Since(c[i].Seen); ago > d.cacheLifetime {
|
||||
if debug {
|
||||
l.Debugf("removing cached address %s: seen %v ago", c[i].Address, ago)
|
||||
l.Debugf("discover: Removing cached address %s - seen %v ago", c[i].Address, ago)
|
||||
}
|
||||
c[i] = c[len(c)-1]
|
||||
c = c[:len(c)-1]
|
||||
|
||||
@@ -187,6 +187,10 @@ func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) C() <-chan Event {
|
||||
return s.events
|
||||
}
|
||||
|
||||
type BufferedSubscription struct {
|
||||
sub *Subscription
|
||||
buf []Event
|
||||
|
||||
@@ -166,8 +166,6 @@ func globalKeyFolder(key []byte) []byte {
|
||||
|
||||
type deletionHandler func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64
|
||||
|
||||
type fileIterator func(f protocol.FileIntf) bool
|
||||
|
||||
func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, deleteFn deletionHandler) uint64 {
|
||||
runtime.GC()
|
||||
|
||||
@@ -246,7 +244,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
if debugDB {
|
||||
l.Debugln("generic replace; exists - compare")
|
||||
}
|
||||
var ef protocol.FileInfoTruncated
|
||||
var ef FileInfoTruncated
|
||||
ef.UnmarshalXDR(dbi.Value())
|
||||
if fs[fsi].Version > ef.Version ||
|
||||
(fs[fsi].Version == ef.Version && fs[fsi].Flags != ef.Flags) {
|
||||
@@ -308,7 +306,7 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) u
|
||||
|
||||
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
|
||||
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
|
||||
var tf protocol.FileInfoTruncated
|
||||
var tf FileInfoTruncated
|
||||
err := tf.UnmarshalXDR(dbi.Value())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -378,7 +376,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
|
||||
continue
|
||||
}
|
||||
|
||||
var ef protocol.FileInfoTruncated
|
||||
var ef FileInfoTruncated
|
||||
err = ef.UnmarshalXDR(bs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -528,7 +526,7 @@ func ldbRemoveFromGlobal(db dbReader, batch dbWriter, folder, device, file []byt
|
||||
}
|
||||
}
|
||||
|
||||
func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn fileIterator) {
|
||||
func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterator) {
|
||||
start := deviceKey(folder, device, nil) // before all folder/device files
|
||||
limit := deviceKey(folder, device, []byte{0xff, 0xff, 0xff, 0xff}) // after all folder/device files
|
||||
snap, err := db.GetSnapshot()
|
||||
@@ -559,7 +557,7 @@ func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn fileIt
|
||||
}
|
||||
}
|
||||
|
||||
func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []byte, f protocol.FileInfoTruncated) bool) {
|
||||
func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []byte, f FileInfoTruncated) bool) {
|
||||
runtime.GC()
|
||||
|
||||
start := deviceKey(folder, nil, nil) // before all folder/device files
|
||||
@@ -583,7 +581,7 @@ func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []b
|
||||
|
||||
for dbi.Next() {
|
||||
device := deviceKeyDevice(dbi.Key())
|
||||
var f protocol.FileInfoTruncated
|
||||
var f FileInfoTruncated
|
||||
err := f.UnmarshalXDR(dbi.Value())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -594,11 +592,11 @@ func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []b
|
||||
}
|
||||
}
|
||||
|
||||
func ldbGet(db *leveldb.DB, folder, device, file []byte) protocol.FileInfo {
|
||||
func ldbGet(db *leveldb.DB, folder, device, file []byte) (protocol.FileInfo, bool) {
|
||||
nk := deviceKey(folder, device, file)
|
||||
bs, err := db.Get(nk, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
return protocol.FileInfo{}
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -609,10 +607,10 @@ func ldbGet(db *leveldb.DB, folder, device, file []byte) protocol.FileInfo {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
return f, true
|
||||
}
|
||||
|
||||
func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
|
||||
func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf, bool) {
|
||||
k := globalKey(folder, file)
|
||||
snap, err := db.GetSnapshot()
|
||||
if err != nil {
|
||||
@@ -633,7 +631,7 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
|
||||
}
|
||||
bs, err := snap.Get(k, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
return protocol.FileInfo{}
|
||||
return nil, false
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -658,15 +656,14 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var f protocol.FileInfo
|
||||
err = f.UnmarshalXDR(bs)
|
||||
fi, err := unmarshalTrunc(bs, truncate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f
|
||||
return fi, true
|
||||
}
|
||||
|
||||
func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn fileIterator) {
|
||||
func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn Iterator) {
|
||||
runtime.GC()
|
||||
|
||||
start := globalKey(folder, nil)
|
||||
@@ -754,7 +751,7 @@ func ldbAvailability(db *leveldb.DB, folder, file []byte) []protocol.DeviceID {
|
||||
return devices
|
||||
}
|
||||
|
||||
func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn fileIterator) {
|
||||
func ldbWithNeed(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterator) {
|
||||
runtime.GC()
|
||||
|
||||
start := globalKey(folder, nil)
|
||||
@@ -938,9 +935,9 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
|
||||
dbi.Release()
|
||||
}
|
||||
|
||||
func unmarshalTrunc(bs []byte, truncate bool) (protocol.FileIntf, error) {
|
||||
func unmarshalTrunc(bs []byte, truncate bool) (FileIntf, error) {
|
||||
if truncate {
|
||||
var tf protocol.FileInfoTruncated
|
||||
var tf FileInfoTruncated
|
||||
err := tf.UnmarshalXDR(bs)
|
||||
return tf, err
|
||||
} else {
|
||||
|
||||
@@ -30,14 +30,6 @@ import (
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
type fileRecord struct {
|
||||
File protocol.FileInfo
|
||||
Usage int
|
||||
Global bool
|
||||
}
|
||||
|
||||
type bitset uint64
|
||||
|
||||
type Set struct {
|
||||
localVersion map[protocol.DeviceID]uint64
|
||||
mutex sync.Mutex
|
||||
@@ -46,6 +38,22 @@ type Set struct {
|
||||
blockmap *BlockMap
|
||||
}
|
||||
|
||||
// FileIntf is the set of methods implemented by both protocol.FileInfo and
|
||||
// protocol.FileInfoTruncated.
|
||||
type FileIntf interface {
|
||||
Size() int64
|
||||
IsDeleted() bool
|
||||
IsInvalid() bool
|
||||
IsDirectory() bool
|
||||
IsSymlink() bool
|
||||
HasPermissionBits() bool
|
||||
}
|
||||
|
||||
// The Iterator is called with either a protocol.FileInfo or a
|
||||
// protocol.FileInfoTruncated (depending on the method) and returns true to
|
||||
// continue iteration, false to stop.
|
||||
type Iterator func(f FileIntf) bool
|
||||
|
||||
func NewSet(folder string, db *leveldb.DB) *Set {
|
||||
var s = Set{
|
||||
localVersion: make(map[protocol.DeviceID]uint64),
|
||||
@@ -57,7 +65,7 @@ func NewSet(folder string, db *leveldb.DB) *Set {
|
||||
ldbCheckGlobals(db, []byte(folder))
|
||||
|
||||
var deviceID protocol.DeviceID
|
||||
ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f protocol.FileInfoTruncated) bool {
|
||||
ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f FileInfoTruncated) bool {
|
||||
copy(deviceID[:], device)
|
||||
if f.LocalVersion > s.localVersion[deviceID] {
|
||||
s.localVersion[deviceID] = f.LocalVersion
|
||||
@@ -118,8 +126,8 @@ func (s *Set) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
discards := make([]protocol.FileInfo, 0, len(fs))
|
||||
updates := make([]protocol.FileInfo, 0, len(fs))
|
||||
for _, newFile := range fs {
|
||||
existingFile := ldbGet(s.db, []byte(s.folder), device[:], []byte(newFile.Name))
|
||||
if existingFile.Version <= newFile.Version {
|
||||
existingFile, ok := ldbGet(s.db, []byte(s.folder), device[:], []byte(newFile.Name))
|
||||
if !ok || existingFile.Version <= newFile.Version {
|
||||
discards = append(discards, existingFile)
|
||||
updates = append(updates, newFile)
|
||||
}
|
||||
@@ -132,58 +140,72 @@ func (s *Set) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) WithNeed(device protocol.DeviceID, fn fileIterator) {
|
||||
func (s *Set) WithNeed(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithNeed(%v)", s.folder, device)
|
||||
}
|
||||
ldbWithNeed(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *Set) WithNeedTruncated(device protocol.DeviceID, fn fileIterator) {
|
||||
func (s *Set) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
|
||||
}
|
||||
ldbWithNeed(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *Set) WithHave(device protocol.DeviceID, fn fileIterator) {
|
||||
func (s *Set) WithHave(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithHave(%v)", s.folder, device)
|
||||
}
|
||||
ldbWithHave(s.db, []byte(s.folder), device[:], false, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *Set) WithHaveTruncated(device protocol.DeviceID, fn fileIterator) {
|
||||
func (s *Set) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
|
||||
}
|
||||
ldbWithHave(s.db, []byte(s.folder), device[:], true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *Set) WithGlobal(fn fileIterator) {
|
||||
func (s *Set) WithGlobal(fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithGlobal()", s.folder)
|
||||
}
|
||||
ldbWithGlobal(s.db, []byte(s.folder), false, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *Set) WithGlobalTruncated(fn fileIterator) {
|
||||
func (s *Set) WithGlobalTruncated(fn Iterator) {
|
||||
if debug {
|
||||
l.Debugf("%s WithGlobalTruncated()", s.folder)
|
||||
}
|
||||
ldbWithGlobal(s.db, []byte(s.folder), true, nativeFileIterator(fn))
|
||||
}
|
||||
|
||||
func (s *Set) Get(device protocol.DeviceID, file string) protocol.FileInfo {
|
||||
f := ldbGet(s.db, []byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
|
||||
func (s *Set) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {
|
||||
f, ok := ldbGet(s.db, []byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
|
||||
f.Name = osutil.NativeFilename(f.Name)
|
||||
return f
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func (s *Set) GetGlobal(file string) protocol.FileInfo {
|
||||
f := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)))
|
||||
func (s *Set) GetGlobal(file string) (protocol.FileInfo, bool) {
|
||||
fi, ok := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
|
||||
if !ok {
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
f := fi.(protocol.FileInfo)
|
||||
f.Name = osutil.NativeFilename(f.Name)
|
||||
return f
|
||||
return f, true
|
||||
}
|
||||
|
||||
func (s *Set) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
|
||||
fi, ok := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
|
||||
if !ok {
|
||||
return FileInfoTruncated{}, false
|
||||
}
|
||||
f := fi.(FileInfoTruncated)
|
||||
f.Name = osutil.NativeFilename(f.Name)
|
||||
return f, true
|
||||
}
|
||||
|
||||
func (s *Set) Availability(file string) []protocol.DeviceID {
|
||||
@@ -218,13 +240,13 @@ func normalizeFilenames(fs []protocol.FileInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func nativeFileIterator(fn fileIterator) fileIterator {
|
||||
return func(fi protocol.FileIntf) bool {
|
||||
func nativeFileIterator(fn Iterator) Iterator {
|
||||
return func(fi FileIntf) bool {
|
||||
switch f := fi.(type) {
|
||||
case protocol.FileInfo:
|
||||
f.Name = osutil.NativeFilename(f.Name)
|
||||
return fn(f)
|
||||
case protocol.FileInfoTruncated:
|
||||
case FileInfoTruncated:
|
||||
f.Name = osutil.NativeFilename(f.Name)
|
||||
return fn(f)
|
||||
default:
|
||||
|
||||
@@ -51,7 +51,7 @@ func genBlocks(n int) []protocol.BlockInfo {
|
||||
|
||||
func globalList(s *files.Set) []protocol.FileInfo {
|
||||
var fs []protocol.FileInfo
|
||||
s.WithGlobal(func(fi protocol.FileIntf) bool {
|
||||
s.WithGlobal(func(fi files.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfo)
|
||||
fs = append(fs, f)
|
||||
return true
|
||||
@@ -61,7 +61,7 @@ func globalList(s *files.Set) []protocol.FileInfo {
|
||||
|
||||
func haveList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo {
|
||||
var fs []protocol.FileInfo
|
||||
s.WithHave(n, func(fi protocol.FileIntf) bool {
|
||||
s.WithHave(n, func(fi files.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfo)
|
||||
fs = append(fs, f)
|
||||
return true
|
||||
@@ -71,7 +71,7 @@ func haveList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo {
|
||||
|
||||
func needList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo {
|
||||
var fs []protocol.FileInfo
|
||||
s.WithNeed(n, func(fi protocol.FileIntf) bool {
|
||||
s.WithNeed(n, func(fi files.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfo)
|
||||
fs = append(fs, f)
|
||||
return true
|
||||
@@ -209,27 +209,42 @@ func TestGlobalSet(t *testing.T) {
|
||||
t.Errorf("Need incorrect;\n A: %v !=\n E: %v", n, expectedRemoteNeed)
|
||||
}
|
||||
|
||||
f := m.Get(protocol.LocalDeviceID, "b")
|
||||
f, ok := m.Get(protocol.LocalDeviceID, "b")
|
||||
if !ok {
|
||||
t.Error("Unexpectedly not OK")
|
||||
}
|
||||
if fmt.Sprint(f) != fmt.Sprint(localTot[1]) {
|
||||
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, localTot[1])
|
||||
}
|
||||
|
||||
f = m.Get(remoteDevice0, "b")
|
||||
f, ok = m.Get(remoteDevice0, "b")
|
||||
if !ok {
|
||||
t.Error("Unexpectedly not OK")
|
||||
}
|
||||
if fmt.Sprint(f) != fmt.Sprint(remote1[0]) {
|
||||
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, remote1[0])
|
||||
}
|
||||
|
||||
f = m.GetGlobal("b")
|
||||
f, ok = m.GetGlobal("b")
|
||||
if !ok {
|
||||
t.Error("Unexpectedly not OK")
|
||||
}
|
||||
if fmt.Sprint(f) != fmt.Sprint(remote1[0]) {
|
||||
t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, remote1[0])
|
||||
}
|
||||
|
||||
f = m.Get(protocol.LocalDeviceID, "zz")
|
||||
f, ok = m.Get(protocol.LocalDeviceID, "zz")
|
||||
if ok {
|
||||
t.Error("Unexpectedly OK")
|
||||
}
|
||||
if f.Name != "" {
|
||||
t.Errorf("Get incorrect;\n A: %v !=\n E: %v", f, protocol.FileInfo{})
|
||||
}
|
||||
|
||||
f = m.GetGlobal("zz")
|
||||
f, ok = m.GetGlobal("zz")
|
||||
if ok {
|
||||
t.Error("Unexpectedly OK")
|
||||
}
|
||||
if f.Name != "" {
|
||||
t.Errorf("GetGlobal incorrect;\n A: %v !=\n E: %v", f, protocol.FileInfo{})
|
||||
}
|
||||
|
||||
75
internal/files/truncated.go
Normal file
75
internal/files/truncated.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation, either version 3 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:generate -command genxdr go run ../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
|
||||
//go:generate genxdr -o truncated_xdr.go truncated.go
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/protocol"
|
||||
)
|
||||
|
||||
// Used for unmarshalling a FileInfo structure but skipping the block list.
|
||||
type FileInfoTruncated struct {
|
||||
Name string // max:8192
|
||||
Flags uint32
|
||||
Modified int64
|
||||
Version uint64
|
||||
LocalVersion uint64
|
||||
NumBlocks uint32
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) String() string {
|
||||
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}",
|
||||
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.NumBlocks)
|
||||
}
|
||||
|
||||
// Returns a statistical guess on the size, not the exact figure
|
||||
func (f FileInfoTruncated) Size() int64 {
|
||||
if f.IsDeleted() || f.IsDirectory() {
|
||||
return 128
|
||||
}
|
||||
return BlocksToSize(f.NumBlocks)
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsDeleted() bool {
|
||||
return f.Flags&protocol.FlagDeleted != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsInvalid() bool {
|
||||
return f.Flags&protocol.FlagInvalid != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsDirectory() bool {
|
||||
return f.Flags&protocol.FlagDirectory != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsSymlink() bool {
|
||||
return f.Flags&protocol.FlagSymlink != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) HasPermissionBits() bool {
|
||||
return f.Flags&protocol.FlagNoPermBits == 0
|
||||
}
|
||||
|
||||
func BlocksToSize(num uint32) int64 {
|
||||
if num < 2 {
|
||||
return protocol.BlockSize / 2
|
||||
}
|
||||
return int64(num-1)*protocol.BlockSize + protocol.BlockSize/2
|
||||
}
|
||||
112
internal/files/truncated_xdr.go
Normal file
112
internal/files/truncated_xdr.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// ************************************************************
|
||||
// This file is automatically generated by genxdr. Do not edit.
|
||||
// ************************************************************
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/calmh/xdr"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
FileInfoTruncated Structure:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Name |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Name (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Flags |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ Modified (64 bits) +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ Version (64 bits) +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ Local Version (64 bits) +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Num Blocks |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct FileInfoTruncated {
|
||||
string Name<8192>;
|
||||
unsigned int Flags;
|
||||
hyper Modified;
|
||||
unsigned hyper Version;
|
||||
unsigned hyper LocalVersion;
|
||||
unsigned int NumBlocks;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) MarshalXDR() ([]byte, error) {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) MustMarshalXDR() []byte {
|
||||
bs, err := o.MarshalXDR()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.encodeXDR(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) 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))
|
||||
xw.WriteUint64(o.Version)
|
||||
xw.WriteUint64(o.LocalVersion)
|
||||
xw.WriteUint32(o.NumBlocks)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Name = xr.ReadStringMax(8192)
|
||||
o.Flags = xr.ReadUint32()
|
||||
o.Modified = int64(xr.ReadUint64())
|
||||
o.Version = xr.ReadUint64()
|
||||
o.LocalVersion = xr.ReadUint64()
|
||||
o.NumBlocks = xr.ReadUint32()
|
||||
return xr.Error()
|
||||
}
|
||||
@@ -190,7 +190,6 @@ func (m *Model) StartFolderRW(folder string) {
|
||||
progressEmitter: m.progressEmitter,
|
||||
copiers: cfg.Copiers,
|
||||
pullers: cfg.Pullers,
|
||||
finishers: cfg.Finishers,
|
||||
queue: newJobQueue(),
|
||||
}
|
||||
m.folderRunners[folder] = p
|
||||
@@ -310,7 +309,7 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) float64 {
|
||||
return 0 // Folder doesn't exist, so we hardly have any of it
|
||||
}
|
||||
|
||||
rf.WithGlobalTruncated(func(f protocol.FileIntf) bool {
|
||||
rf.WithGlobalTruncated(func(f files.FileIntf) bool {
|
||||
if !f.IsDeleted() {
|
||||
tot += f.Size()
|
||||
}
|
||||
@@ -322,7 +321,7 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) float64 {
|
||||
}
|
||||
|
||||
var need int64
|
||||
rf.WithNeedTruncated(device, func(f protocol.FileIntf) bool {
|
||||
rf.WithNeedTruncated(device, func(f files.FileIntf) bool {
|
||||
if !f.IsDeleted() {
|
||||
need += f.Size()
|
||||
}
|
||||
@@ -347,7 +346,7 @@ func sizeOf(fs []protocol.FileInfo) (files, deleted int, bytes int64) {
|
||||
return
|
||||
}
|
||||
|
||||
func sizeOfFile(f protocol.FileIntf) (files, deleted int, bytes int64) {
|
||||
func sizeOfFile(f files.FileIntf) (files, deleted int, bytes int64) {
|
||||
if !f.IsDeleted() {
|
||||
files++
|
||||
} else {
|
||||
@@ -359,15 +358,15 @@ func sizeOfFile(f protocol.FileIntf) (files, deleted int, bytes int64) {
|
||||
|
||||
// GlobalSize returns the number of files, deleted files and total bytes for all
|
||||
// files in the global model.
|
||||
func (m *Model) GlobalSize(folder string) (files, deleted int, bytes int64) {
|
||||
func (m *Model) GlobalSize(folder string) (nfiles, deleted int, bytes int64) {
|
||||
defer m.leveldbPanicWorkaround()
|
||||
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
rf.WithGlobalTruncated(func(f protocol.FileIntf) bool {
|
||||
rf.WithGlobalTruncated(func(f files.FileIntf) bool {
|
||||
fs, de, by := sizeOfFile(f)
|
||||
files += fs
|
||||
nfiles += fs
|
||||
deleted += de
|
||||
bytes += by
|
||||
return true
|
||||
@@ -378,18 +377,18 @@ func (m *Model) GlobalSize(folder string) (files, deleted int, bytes int64) {
|
||||
|
||||
// LocalSize returns the number of files, deleted files and total bytes for all
|
||||
// files in the local folder.
|
||||
func (m *Model) LocalSize(folder string) (files, deleted int, bytes int64) {
|
||||
func (m *Model) LocalSize(folder string) (nfiles, deleted int, bytes int64) {
|
||||
defer m.leveldbPanicWorkaround()
|
||||
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
rf.WithHaveTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool {
|
||||
rf.WithHaveTruncated(protocol.LocalDeviceID, func(f files.FileIntf) bool {
|
||||
if f.IsInvalid() {
|
||||
return true
|
||||
}
|
||||
fs, de, by := sizeOfFile(f)
|
||||
files += fs
|
||||
nfiles += fs
|
||||
deleted += de
|
||||
bytes += by
|
||||
return true
|
||||
@@ -399,22 +398,22 @@ func (m *Model) LocalSize(folder string) (files, deleted int, bytes int64) {
|
||||
}
|
||||
|
||||
// NeedSize returns the number and total size of currently needed files.
|
||||
func (m *Model) NeedSize(folder string) (files int, bytes int64) {
|
||||
func (m *Model) NeedSize(folder string) (nfiles int, bytes int64) {
|
||||
defer m.leveldbPanicWorkaround()
|
||||
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
rf.WithNeedTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool {
|
||||
rf.WithNeedTruncated(protocol.LocalDeviceID, func(f files.FileIntf) bool {
|
||||
fs, de, by := sizeOfFile(f)
|
||||
files += fs + de
|
||||
nfiles += fs + de
|
||||
bytes += by
|
||||
return true
|
||||
})
|
||||
}
|
||||
bytes -= m.progressEmitter.BytesCompleted(folder)
|
||||
if debug {
|
||||
l.Debugf("%v NeedSize(%q): %d %d", m, folder, files, bytes)
|
||||
l.Debugf("%v NeedSize(%q): %d %d", m, folder, nfiles, bytes)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -422,38 +421,42 @@ func (m *Model) NeedSize(folder string) (files int, bytes int64) {
|
||||
// NeedFiles returns the list of currently needed files in progress, queued,
|
||||
// and to be queued on next puller iteration. Also takes a soft cap which is
|
||||
// only respected when adding files from the model rather than the runner queue.
|
||||
func (m *Model) NeedFolderFiles(folder string, max int) ([]protocol.FileInfoTruncated, []protocol.FileInfoTruncated, []protocol.FileInfoTruncated) {
|
||||
func (m *Model) NeedFolderFiles(folder string, max int) ([]files.FileInfoTruncated, []files.FileInfoTruncated, []files.FileInfoTruncated) {
|
||||
defer m.leveldbPanicWorkaround()
|
||||
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
if rf, ok := m.folderFiles[folder]; ok {
|
||||
var progress, queued, rest []protocol.FileInfoTruncated
|
||||
var progress, queued, rest []files.FileInfoTruncated
|
||||
var seen map[string]bool
|
||||
|
||||
runner, ok := m.folderRunners[folder]
|
||||
if ok {
|
||||
progressNames, queuedNames := runner.Jobs()
|
||||
|
||||
progress = make([]protocol.FileInfoTruncated, len(progressNames))
|
||||
queued = make([]protocol.FileInfoTruncated, len(queuedNames))
|
||||
progress = make([]files.FileInfoTruncated, len(progressNames))
|
||||
queued = make([]files.FileInfoTruncated, len(queuedNames))
|
||||
seen = make(map[string]bool, len(progressNames)+len(queuedNames))
|
||||
|
||||
for i, name := range progressNames {
|
||||
progress[i] = rf.GetGlobal(name).ToTruncated() /// XXX: Should implement GetGlobalTruncated directly
|
||||
seen[name] = true
|
||||
if f, ok := rf.GetGlobalTruncated(name); ok {
|
||||
progress[i] = f
|
||||
seen[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
for i, name := range queuedNames {
|
||||
queued[i] = rf.GetGlobal(name).ToTruncated() /// XXX: Should implement GetGlobalTruncated directly
|
||||
seen[name] = true
|
||||
if f, ok := rf.GetGlobalTruncated(name); ok {
|
||||
queued[i] = f
|
||||
seen[name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
left := max - len(progress) - len(queued)
|
||||
if max < 1 || left > 0 {
|
||||
rf.WithNeedTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool {
|
||||
rf.WithNeedTruncated(protocol.LocalDeviceID, func(f files.FileIntf) bool {
|
||||
left--
|
||||
ft := f.(protocol.FileInfoTruncated)
|
||||
ft := f.(files.FileInfoTruncated)
|
||||
if !seen[ft.Name] {
|
||||
rest = append(rest, ft)
|
||||
}
|
||||
@@ -573,8 +576,21 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
} else {
|
||||
m.deviceVer[deviceID] = cm.ClientName + " " + cm.ClientVersion
|
||||
}
|
||||
|
||||
event := map[string]string{
|
||||
"id": deviceID.String(),
|
||||
"clientName": cm.ClientName,
|
||||
"clientVersion": cm.ClientVersion,
|
||||
}
|
||||
|
||||
if conn, ok := m.rawConn[deviceID].(*tls.Conn); ok {
|
||||
event["addr"] = conn.RemoteAddr().String()
|
||||
}
|
||||
|
||||
m.pmut.Unlock()
|
||||
|
||||
events.Default.Log(events.DeviceConnected, event)
|
||||
|
||||
l.Infof(`Device %s client is "%s %s"`, deviceID, cm.ClientName, cm.ClientVersion)
|
||||
|
||||
var changed bool
|
||||
@@ -704,7 +720,11 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
|
||||
return nil, ErrNoSuchFile
|
||||
}
|
||||
|
||||
lf := r.Get(protocol.LocalDeviceID, name)
|
||||
lf, ok := r.Get(protocol.LocalDeviceID, name)
|
||||
if !ok {
|
||||
return nil, ErrNoSuchFile
|
||||
}
|
||||
|
||||
if lf.IsInvalid() || lf.IsDeleted() {
|
||||
if debug {
|
||||
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d; invalid: %v", m, deviceID, folder, name, offset, size, lf)
|
||||
@@ -759,18 +779,18 @@ func (m *Model) ReplaceLocal(folder string, fs []protocol.FileInfo) {
|
||||
m.fmut.RUnlock()
|
||||
}
|
||||
|
||||
func (m *Model) CurrentFolderFile(folder string, file string) protocol.FileInfo {
|
||||
func (m *Model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
|
||||
m.fmut.RLock()
|
||||
f := m.folderFiles[folder].Get(protocol.LocalDeviceID, file)
|
||||
f, ok := m.folderFiles[folder].Get(protocol.LocalDeviceID, file)
|
||||
m.fmut.RUnlock()
|
||||
return f
|
||||
return f, ok
|
||||
}
|
||||
|
||||
func (m *Model) CurrentGlobalFile(folder string, file string) protocol.FileInfo {
|
||||
func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
|
||||
m.fmut.RLock()
|
||||
f := m.folderFiles[folder].GetGlobal(file)
|
||||
f, ok := m.folderFiles[folder].GetGlobal(file)
|
||||
m.fmut.RUnlock()
|
||||
return f
|
||||
return f, ok
|
||||
}
|
||||
|
||||
type cFiler struct {
|
||||
@@ -779,7 +799,7 @@ type cFiler struct {
|
||||
}
|
||||
|
||||
// Implements scanner.CurrentFiler
|
||||
func (cf cFiler) CurrentFile(file string) protocol.FileInfo {
|
||||
func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
|
||||
return cf.m.CurrentFolderFile(cf.r, file)
|
||||
}
|
||||
|
||||
@@ -962,7 +982,7 @@ func sendIndexTo(initial bool, minLocalVer uint64, conn protocol.Connection, fol
|
||||
maxLocalVer := uint64(0)
|
||||
var err error
|
||||
|
||||
fs.WithHave(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
|
||||
fs.WithHave(protocol.LocalDeviceID, func(fi files.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfo)
|
||||
if f.LocalVersion <= minLocalVer {
|
||||
return true
|
||||
@@ -1130,6 +1150,7 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
|
||||
TempLifetime: time.Duration(m.cfg.Options().KeepTemporariesH) * time.Hour,
|
||||
CurrentFiler: cFiler{m, folder},
|
||||
IgnorePerms: folderCfg.IgnorePerms,
|
||||
Hashers: folderCfg.Hashers,
|
||||
}
|
||||
|
||||
m.setState(folder, FolderScanning)
|
||||
@@ -1161,8 +1182,8 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
|
||||
batch = batch[:0]
|
||||
// TODO: We should limit the Have scanning to start at sub
|
||||
seenPrefix := false
|
||||
fs.WithHaveTruncated(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
|
||||
f := fi.(protocol.FileInfoTruncated)
|
||||
fs.WithHaveTruncated(protocol.LocalDeviceID, func(fi files.FileIntf) bool {
|
||||
f := fi.(files.FileInfoTruncated)
|
||||
if !strings.HasPrefix(f.Name, sub) {
|
||||
// Return true so that we keep iterating, until we get to the part
|
||||
// of the tree we are interested in. Then return false so we stop
|
||||
@@ -1302,15 +1323,15 @@ func (m *Model) Override(folder string) {
|
||||
|
||||
m.setState(folder, FolderScanning)
|
||||
batch := make([]protocol.FileInfo, 0, indexBatchSize)
|
||||
fs.WithNeed(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
|
||||
fs.WithNeed(protocol.LocalDeviceID, func(fi files.FileIntf) bool {
|
||||
need := fi.(protocol.FileInfo)
|
||||
if len(batch) == indexBatchSize {
|
||||
fs.Update(protocol.LocalDeviceID, batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
have := fs.Get(protocol.LocalDeviceID, need.Name)
|
||||
if have.Name != need.Name {
|
||||
have, ok := fs.Get(protocol.LocalDeviceID, need.Name)
|
||||
if !ok || have.Name != need.Name {
|
||||
// We are missing the file
|
||||
need.Flags |= protocol.FlagDeleted
|
||||
need.Blocks = nil
|
||||
|
||||
@@ -47,8 +47,6 @@ func expectTimeout(w *events.Subscription, t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProgressEmitter(t *testing.T) {
|
||||
l.Debugln("test progress emitter")
|
||||
|
||||
w := events.Default.Subscribe(events.DownloadProgress)
|
||||
|
||||
c := config.Wrap("/tmp/test", config.Configuration{})
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/internal/config"
|
||||
"github.com/syncthing/syncthing/internal/events"
|
||||
"github.com/syncthing/syncthing/internal/files"
|
||||
"github.com/syncthing/syncthing/internal/ignore"
|
||||
"github.com/syncthing/syncthing/internal/osutil"
|
||||
"github.com/syncthing/syncthing/internal/protocol"
|
||||
@@ -76,7 +77,6 @@ type Puller struct {
|
||||
progressEmitter *ProgressEmitter
|
||||
copiers int
|
||||
pullers int
|
||||
finishers int
|
||||
queue *jobQueue
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
|
||||
var doneWg sync.WaitGroup
|
||||
|
||||
if debug {
|
||||
l.Debugln(p, "c", p.copiers, "p", p.pullers, "f", p.finishers)
|
||||
l.Debugln(p, "c", p.copiers, "p", p.pullers)
|
||||
}
|
||||
|
||||
for i := 0; i < p.copiers; i++ {
|
||||
@@ -278,17 +278,15 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < p.finishers; i++ {
|
||||
doneWg.Add(1)
|
||||
// finisherRoutine finishes when finisherChan is closed
|
||||
go func() {
|
||||
p.finisherRoutine(finisherChan)
|
||||
doneWg.Done()
|
||||
}()
|
||||
}
|
||||
doneWg.Add(1)
|
||||
// finisherRoutine finishes when finisherChan is closed
|
||||
go func() {
|
||||
p.finisherRoutine(finisherChan)
|
||||
doneWg.Done()
|
||||
}()
|
||||
|
||||
p.model.fmut.RLock()
|
||||
files := p.model.folderFiles[p.folder]
|
||||
folderFiles := p.model.folderFiles[p.folder]
|
||||
p.model.fmut.RUnlock()
|
||||
|
||||
// !!!
|
||||
@@ -301,7 +299,7 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
|
||||
|
||||
var deletions []protocol.FileInfo
|
||||
|
||||
files.WithNeed(protocol.LocalDeviceID, func(intf protocol.FileIntf) bool {
|
||||
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf files.FileIntf) bool {
|
||||
|
||||
// Needed items are delivered sorted lexicographically. This isn't
|
||||
// really optimal from a performance point of view - it would be
|
||||
@@ -348,8 +346,12 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
f := p.model.CurrentGlobalFile(p.folder, fileName)
|
||||
p.handleFile(f, copyChan, finisherChan)
|
||||
if f, ok := p.model.CurrentGlobalFile(p.folder, fileName); ok {
|
||||
p.handleFile(f, copyChan, finisherChan)
|
||||
} else {
|
||||
// File is no longer in the index. Mark it as done and drop it.
|
||||
p.queue.Done(fileName)
|
||||
}
|
||||
}
|
||||
|
||||
// Signal copy and puller routines that we are done with the in data for
|
||||
@@ -386,7 +388,7 @@ func (p *Puller) handleDir(file protocol.FileInfo) {
|
||||
}
|
||||
|
||||
if debug {
|
||||
curFile := p.model.CurrentFolderFile(p.folder, file.Name)
|
||||
curFile, _ := p.model.CurrentFolderFile(p.folder, file.Name)
|
||||
l.Debugf("need dir\n\t%v\n\t%v", file, curFile)
|
||||
}
|
||||
|
||||
@@ -480,9 +482,9 @@ func (p *Puller) deleteFile(file protocol.FileInfo) {
|
||||
// handleFile queues the copies and pulls as necessary for a single new or
|
||||
// changed file.
|
||||
func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState) {
|
||||
curFile := p.model.CurrentFolderFile(p.folder, file.Name)
|
||||
curFile, ok := p.model.CurrentFolderFile(p.folder, file.Name)
|
||||
|
||||
if len(curFile.Blocks) == len(file.Blocks) && scanner.BlocksEqual(curFile.Blocks, file.Blocks) {
|
||||
if ok && len(curFile.Blocks) == len(file.Blocks) && scanner.BlocksEqual(curFile.Blocks, file.Blocks) {
|
||||
// We are supposed to copy the entire file, and then fetch nothing. We
|
||||
// are only updating metadata, so we don't actually *need* to make the
|
||||
// copy.
|
||||
@@ -608,17 +610,17 @@ func (p *Puller) shortcutSymlink(curFile, file protocol.FileInfo) {
|
||||
func (p *Puller) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBlockState, out chan<- *sharedPullerState) {
|
||||
buf := make([]byte, protocol.BlockSize)
|
||||
|
||||
nextFile:
|
||||
for state := range in {
|
||||
if p.progressEmitter != nil {
|
||||
p.progressEmitter.Register(state.sharedPullerState)
|
||||
}
|
||||
|
||||
dstFd, err := state.tempFile()
|
||||
if err != nil {
|
||||
// Nothing more to do for this failed file (the error was logged
|
||||
// when it happened)
|
||||
continue nextFile
|
||||
}
|
||||
|
||||
if p.progressEmitter != nil {
|
||||
p.progressEmitter.Register(state.sharedPullerState)
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
}
|
||||
|
||||
evictionChan := make(chan lfu.Eviction)
|
||||
@@ -682,7 +684,7 @@ nextFile:
|
||||
|
||||
_, err = dstFd.WriteAt(buf, block.Offset)
|
||||
if err != nil {
|
||||
state.earlyClose("dst write", err)
|
||||
state.fail("dst write", err)
|
||||
}
|
||||
if file == state.file.Name {
|
||||
state.copiedFromOrigin()
|
||||
@@ -734,9 +736,9 @@ func (p *Puller) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPulle
|
||||
selected := activity.leastBusy(potentialDevices)
|
||||
if selected == (protocol.DeviceID{}) {
|
||||
if lastError != nil {
|
||||
state.earlyClose("pull", lastError)
|
||||
state.fail("pull", lastError)
|
||||
} else {
|
||||
state.earlyClose("pull", errNoDevice)
|
||||
state.fail("pull", errNoDevice)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -762,13 +764,13 @@ func (p *Puller) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPulle
|
||||
// Save the block data we got from the cluster
|
||||
_, err = fd.WriteAt(buf, state.block.Offset)
|
||||
if err != nil {
|
||||
state.earlyClose("save", err)
|
||||
state.fail("save", err)
|
||||
} else {
|
||||
state.pullDone()
|
||||
out <- state.sharedPullerState
|
||||
}
|
||||
break
|
||||
}
|
||||
out <- state.sharedPullerState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,7 +860,9 @@ func (p *Puller) finisherRoutine(in <-chan *sharedPullerState) {
|
||||
}
|
||||
|
||||
p.queue.Done(state.file.Name)
|
||||
p.performFinish(state)
|
||||
if state.failed() == nil {
|
||||
p.performFinish(state)
|
||||
}
|
||||
p.model.receivedFile(p.folder, state.file.Name)
|
||||
if p.progressEmitter != nil {
|
||||
p.progressEmitter.Deregister(state)
|
||||
|
||||
@@ -382,3 +382,172 @@ func TestLastResortPulling(t *testing.T) {
|
||||
(<-finisherChan).fd.Close()
|
||||
os.Remove(filepath.Join("testdata", defTempNamer.TempName("newfile")))
|
||||
}
|
||||
|
||||
func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
file := protocol.FileInfo{
|
||||
Name: "filex",
|
||||
Flags: 0,
|
||||
Modified: 0,
|
||||
Blocks: []protocol.BlockInfo{
|
||||
blocks[0], blocks[2], blocks[0], blocks[0],
|
||||
blocks[5], blocks[0], blocks[0], blocks[8],
|
||||
},
|
||||
}
|
||||
defer os.Remove(defTempNamer.TempName("filex"))
|
||||
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
cw := config.Wrap("/tmp/test", config.Configuration{})
|
||||
m := NewModel(cw, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
|
||||
|
||||
emitter := NewProgressEmitter(cw)
|
||||
go emitter.Serve()
|
||||
|
||||
p := Puller{
|
||||
folder: "default",
|
||||
dir: "testdata",
|
||||
model: m,
|
||||
queue: newJobQueue(),
|
||||
progressEmitter: emitter,
|
||||
}
|
||||
|
||||
// queue.Done should be called by the finisher routine
|
||||
p.queue.Push("filex")
|
||||
p.queue.Pop()
|
||||
|
||||
if len(p.queue.progress) != 1 {
|
||||
t.Fatal("Expected file in progress")
|
||||
}
|
||||
|
||||
copyChan := make(chan copyBlocksState)
|
||||
pullChan := make(chan pullBlockState)
|
||||
finisherBufferChan := make(chan *sharedPullerState)
|
||||
finisherChan := make(chan *sharedPullerState)
|
||||
|
||||
go p.copierRoutine(copyChan, pullChan, finisherBufferChan)
|
||||
go p.finisherRoutine(finisherChan)
|
||||
|
||||
p.handleFile(file, copyChan, finisherChan)
|
||||
|
||||
// Receive a block at puller, to indicate that atleast a single copier
|
||||
// loop has been performed.
|
||||
toPull := <-pullChan
|
||||
// Wait until copier is trying to pass something down to the puller again
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Close the file
|
||||
toPull.sharedPullerState.fail("test", os.ErrNotExist)
|
||||
// Unblock copier
|
||||
<-pullChan
|
||||
|
||||
select {
|
||||
case state := <-finisherBufferChan:
|
||||
// At this point the file should still be registered with both the job
|
||||
// queue, and the progress emitter. Verify this.
|
||||
if len(p.progressEmitter.registry) != 1 || len(p.queue.progress) != 1 || len(p.queue.queued) != 0 {
|
||||
t.Fatal("Could not find file")
|
||||
}
|
||||
|
||||
// Pass the file down the real finisher, and give it time to consume
|
||||
finisherChan <- state
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if state.fd != nil {
|
||||
t.Fatal("File not closed?")
|
||||
}
|
||||
|
||||
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
|
||||
t.Fatal("Still registered", len(p.progressEmitter.registry), len(p.queue.progress), len(p.queue.queued))
|
||||
}
|
||||
|
||||
// Doing it again should have no effect
|
||||
finisherChan <- state
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
|
||||
t.Fatal("Still registered")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Didn't get anything to the finisher")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeregisterOnFailInPull(t *testing.T) {
|
||||
file := protocol.FileInfo{
|
||||
Name: "filex",
|
||||
Flags: 0,
|
||||
Modified: 0,
|
||||
Blocks: []protocol.BlockInfo{
|
||||
blocks[0], blocks[2], blocks[0], blocks[0],
|
||||
blocks[5], blocks[0], blocks[0], blocks[8],
|
||||
},
|
||||
}
|
||||
defer os.Remove(defTempNamer.TempName("filex"))
|
||||
|
||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
cw := config.Wrap("/tmp/test", config.Configuration{})
|
||||
m := NewModel(cw, "device", "syncthing", "dev", db)
|
||||
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
|
||||
|
||||
emitter := NewProgressEmitter(cw)
|
||||
go emitter.Serve()
|
||||
|
||||
p := Puller{
|
||||
folder: "default",
|
||||
dir: "testdata",
|
||||
model: m,
|
||||
queue: newJobQueue(),
|
||||
progressEmitter: emitter,
|
||||
}
|
||||
|
||||
// queue.Done should be called by the finisher routine
|
||||
p.queue.Push("filex")
|
||||
p.queue.Pop()
|
||||
|
||||
if len(p.queue.progress) != 1 {
|
||||
t.Fatal("Expected file in progress")
|
||||
}
|
||||
|
||||
copyChan := make(chan copyBlocksState)
|
||||
pullChan := make(chan pullBlockState)
|
||||
finisherBufferChan := make(chan *sharedPullerState)
|
||||
finisherChan := make(chan *sharedPullerState)
|
||||
|
||||
go p.copierRoutine(copyChan, pullChan, finisherBufferChan)
|
||||
go p.pullerRoutine(pullChan, finisherBufferChan)
|
||||
go p.finisherRoutine(finisherChan)
|
||||
|
||||
p.handleFile(file, copyChan, finisherChan)
|
||||
|
||||
// Receove at finisher, we shoud error out as puller has nowhere to pull
|
||||
// from.
|
||||
select {
|
||||
case state := <-finisherBufferChan:
|
||||
// At this point the file should still be registered with both the job
|
||||
// queue, and the progress emitter. Verify this.
|
||||
if len(p.progressEmitter.registry) != 1 || len(p.queue.progress) != 1 || len(p.queue.queued) != 0 {
|
||||
t.Fatal("Could not find file")
|
||||
}
|
||||
|
||||
// Pass the file down the real finisher, and give it time to consume
|
||||
finisherChan <- state
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if state.fd != nil {
|
||||
t.Fatal("File not closed?")
|
||||
}
|
||||
|
||||
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
|
||||
t.Fatal("Still registered", len(p.progressEmitter.registry), len(p.queue.progress), len(p.queue.queued))
|
||||
}
|
||||
|
||||
// Doing it again should have no effect
|
||||
finisherChan <- state
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if len(p.progressEmitter.registry) != 0 || len(p.queue.progress) != 0 || len(p.queue.queued) != 0 {
|
||||
t.Fatal("Still registered")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("Didn't get anything to the finisher")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ func BenchmarkJobQueuePushPopDone10k(b *testing.B) {
|
||||
for _, f := range files {
|
||||
q.Push(f.Name)
|
||||
}
|
||||
for range files {
|
||||
for _ = range files {
|
||||
n, _ := q.Pop()
|
||||
q.Done(n)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/files"
|
||||
"github.com/syncthing/syncthing/internal/protocol"
|
||||
)
|
||||
|
||||
@@ -42,7 +43,6 @@ type sharedPullerState struct {
|
||||
copyOrigin uint32 // Number of blocks copied from the original file
|
||||
copyNeeded uint32 // Number of copy actions still pending
|
||||
pullNeeded uint32 // Number of block pulls still pending
|
||||
closed bool // Set when the file has been closed
|
||||
mut sync.Mutex // Protects the above
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
// here.
|
||||
dir := filepath.Dir(s.tempName)
|
||||
if info, err := os.Stat(dir); err != nil {
|
||||
s.earlyCloseLocked("dst stat dir", err)
|
||||
s.failLocked("dst stat dir", err)
|
||||
return nil, err
|
||||
} else if info.Mode()&0200 == 0 {
|
||||
err := os.Chmod(dir, 0755)
|
||||
@@ -118,13 +118,13 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
// make sure we have write permissions on the file before opening it.
|
||||
err := os.Chmod(s.tempName, 0644)
|
||||
if err != nil {
|
||||
s.earlyCloseLocked("dst create chmod", err)
|
||||
s.failLocked("dst create chmod", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fd, err := os.OpenFile(s.tempName, flags, 0644)
|
||||
if err != nil {
|
||||
s.earlyCloseLocked("dst create", err)
|
||||
s.failLocked("dst create", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func (s *sharedPullerState) sourceFile() (*os.File, error) {
|
||||
// Attempt to open the existing file
|
||||
fd, err := os.Open(s.realName)
|
||||
if err != nil {
|
||||
s.earlyCloseLocked("src open", err)
|
||||
s.failLocked("src open", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -157,24 +157,20 @@ func (s *sharedPullerState) sourceFile() (*os.File, error) {
|
||||
// earlyClose prints a warning message composed of the context and
|
||||
// error, and marks the sharedPullerState as failed. Is a no-op when called on
|
||||
// an already failed state.
|
||||
func (s *sharedPullerState) earlyClose(context string, err error) {
|
||||
func (s *sharedPullerState) fail(context string, err error) {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
|
||||
s.earlyCloseLocked(context, err)
|
||||
s.failLocked(context, err)
|
||||
}
|
||||
|
||||
func (s *sharedPullerState) earlyCloseLocked(context string, err error) {
|
||||
func (s *sharedPullerState) failLocked(context string, err error) {
|
||||
if s.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.Infof("Puller (folder %q, file %q): %s: %v", s.folder, s.file.Name, context, err)
|
||||
s.err = err
|
||||
if s.fd != nil {
|
||||
s.fd.Close()
|
||||
}
|
||||
s.closed = true
|
||||
}
|
||||
|
||||
func (s *sharedPullerState) failed() error {
|
||||
@@ -229,21 +225,16 @@ func (s *sharedPullerState) finalClose() (bool, error) {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
|
||||
if s.pullNeeded+s.copyNeeded != 0 {
|
||||
if s.pullNeeded+s.copyNeeded != 0 && s.err == nil {
|
||||
// Not done yet.
|
||||
return false, nil
|
||||
}
|
||||
if s.closed {
|
||||
// Already handled.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s.closed = true
|
||||
if fd := s.fd; fd != nil {
|
||||
s.fd = nil
|
||||
return true, fd.Close()
|
||||
}
|
||||
return true, nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Returns the momentarily progress for the puller
|
||||
@@ -259,7 +250,7 @@ func (s *sharedPullerState) Progress() *pullerProgress {
|
||||
CopiedFromElsewhere: s.copyTotal - s.copyNeeded - s.copyOrigin,
|
||||
Pulled: s.pullTotal - s.pullNeeded,
|
||||
Pulling: s.pullNeeded,
|
||||
BytesTotal: protocol.BlocksToSize(total),
|
||||
BytesDone: protocol.BlocksToSize(done),
|
||||
BytesTotal: files.BlocksToSize(total),
|
||||
BytesDone: files.BlocksToSize(done),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,5 +86,5 @@ func TestReadOnlyDir(t *testing.T) {
|
||||
t.Fatal("Unexpected nil fd")
|
||||
}
|
||||
|
||||
s.earlyClose("Test done", nil)
|
||||
s.fail("Test done", nil)
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ func Rename(from, to string) error {
|
||||
|
||||
// Make sure the destination directory is writeable
|
||||
toDir := filepath.Dir(to)
|
||||
if info, err := os.Stat(toDir); err == nil {
|
||||
os.Chmod(toDir, 0777)
|
||||
if info, err := os.Stat(toDir); err == nil && info.IsDir() && info.Mode()&0200 == 0 {
|
||||
os.Chmod(toDir, 0755)
|
||||
defer os.Chmod(toDir, info.Mode())
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ package protocol
|
||||
import "fmt"
|
||||
|
||||
type IndexMessage struct {
|
||||
Folder string // max:64
|
||||
Files []FileInfo
|
||||
Folder string // max:64
|
||||
Files []FileInfo
|
||||
Flags uint32
|
||||
Options []Option // max:64
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
@@ -69,76 +71,6 @@ func (f FileInfo) HasPermissionBits() bool {
|
||||
return f.Flags&FlagNoPermBits == 0
|
||||
}
|
||||
|
||||
func (f FileInfo) ToTruncated() FileInfoTruncated {
|
||||
return FileInfoTruncated{
|
||||
Name: f.Name,
|
||||
Flags: f.Flags,
|
||||
Modified: f.Modified,
|
||||
Version: f.Version,
|
||||
LocalVersion: f.LocalVersion,
|
||||
NumBlocks: uint32(len(f.Blocks)),
|
||||
}
|
||||
}
|
||||
|
||||
// Used for unmarshalling a FileInfo structure but skipping the actual block list
|
||||
type FileInfoTruncated struct {
|
||||
Name string // max:8192
|
||||
Flags uint32
|
||||
Modified int64
|
||||
Version uint64
|
||||
LocalVersion uint64
|
||||
NumBlocks uint32
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) String() string {
|
||||
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}",
|
||||
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.NumBlocks)
|
||||
}
|
||||
|
||||
func BlocksToSize(num uint32) int64 {
|
||||
if num < 2 {
|
||||
return BlockSize / 2
|
||||
}
|
||||
return int64(num-1)*BlockSize + BlockSize/2
|
||||
}
|
||||
|
||||
// Returns a statistical guess on the size, not the exact figure
|
||||
func (f FileInfoTruncated) Size() int64 {
|
||||
if f.IsDeleted() || f.IsDirectory() {
|
||||
return 128
|
||||
}
|
||||
return BlocksToSize(f.NumBlocks)
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsDeleted() bool {
|
||||
return f.Flags&FlagDeleted != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsInvalid() bool {
|
||||
return f.Flags&FlagInvalid != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsDirectory() bool {
|
||||
return f.Flags&FlagDirectory != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) IsSymlink() bool {
|
||||
return f.Flags&FlagSymlink != 0
|
||||
}
|
||||
|
||||
func (f FileInfoTruncated) HasPermissionBits() bool {
|
||||
return f.Flags&FlagNoPermBits == 0
|
||||
}
|
||||
|
||||
type FileIntf interface {
|
||||
Size() int64
|
||||
IsDeleted() bool
|
||||
IsInvalid() bool
|
||||
IsDirectory() bool
|
||||
IsSymlink() bool
|
||||
HasPermissionBits() bool
|
||||
}
|
||||
|
||||
type BlockInfo struct {
|
||||
Offset int64 // noencode (cache only)
|
||||
Size uint32
|
||||
@@ -150,14 +82,18 @@ func (b BlockInfo) String() string {
|
||||
}
|
||||
|
||||
type RequestMessage struct {
|
||||
Folder string // max:64
|
||||
Name string // max:8192
|
||||
Offset uint64
|
||||
Size uint32
|
||||
Folder string // max:64
|
||||
Name string // max:8192
|
||||
Offset uint64
|
||||
Size uint32
|
||||
Hash []byte // max:64
|
||||
Flags uint32
|
||||
Options []Option // max:64
|
||||
}
|
||||
|
||||
type ResponseMessage struct {
|
||||
Data []byte
|
||||
Data []byte
|
||||
Error uint32
|
||||
}
|
||||
|
||||
type ClusterConfigMessage struct {
|
||||
@@ -194,6 +130,7 @@ type Option struct {
|
||||
|
||||
type CloseMessage struct {
|
||||
Reason string // max:1024
|
||||
Code uint32
|
||||
}
|
||||
|
||||
type EmptyMessage struct{}
|
||||
|
||||
@@ -30,11 +30,21 @@ IndexMessage Structure:
|
||||
\ Zero or more FileInfo Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Flags |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Options |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Zero or more Option Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct IndexMessage {
|
||||
string Folder<64>;
|
||||
FileInfo Files<>;
|
||||
unsigned int Flags;
|
||||
Option Options<64>;
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -75,6 +85,17 @@ func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
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].encodeXDR(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
}
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
@@ -96,6 +117,15 @@ func (o *IndexMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
for i := range o.Files {
|
||||
(&o.Files[i]).decodeXDR(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]).decodeXDR(xr)
|
||||
}
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
@@ -215,106 +245,6 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
|
||||
|
||||
/*
|
||||
|
||||
FileInfoTruncated Structure:
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Name |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Name (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Flags |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ Modified (64 bits) +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ Version (64 bits) +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| |
|
||||
+ Local Version (64 bits) +
|
||||
| |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Num Blocks |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct FileInfoTruncated {
|
||||
string Name<8192>;
|
||||
unsigned int Flags;
|
||||
hyper Modified;
|
||||
unsigned hyper Version;
|
||||
unsigned hyper LocalVersion;
|
||||
unsigned int NumBlocks;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) {
|
||||
var xw = xdr.NewWriter(w)
|
||||
return o.encodeXDR(xw)
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) MarshalXDR() ([]byte, error) {
|
||||
return o.AppendXDR(make([]byte, 0, 128))
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) MustMarshalXDR() []byte {
|
||||
bs, err := o.MarshalXDR()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) {
|
||||
var aw = xdr.AppendWriter(bs)
|
||||
var xw = xdr.NewWriter(&aw)
|
||||
_, err := o.encodeXDR(xw)
|
||||
return []byte(aw), err
|
||||
}
|
||||
|
||||
func (o FileInfoTruncated) 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))
|
||||
xw.WriteUint64(o.Version)
|
||||
xw.WriteUint64(o.LocalVersion)
|
||||
xw.WriteUint32(o.NumBlocks)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error {
|
||||
xr := xdr.NewReader(r)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error {
|
||||
var br = bytes.NewReader(bs)
|
||||
var xr = xdr.NewReader(br)
|
||||
return o.decodeXDR(xr)
|
||||
}
|
||||
|
||||
func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Name = xr.ReadStringMax(8192)
|
||||
o.Flags = xr.ReadUint32()
|
||||
o.Modified = int64(xr.ReadUint64())
|
||||
o.Version = xr.ReadUint64()
|
||||
o.LocalVersion = xr.ReadUint64()
|
||||
o.NumBlocks = xr.ReadUint32()
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
BlockInfo Structure:
|
||||
|
||||
0 1 2 3
|
||||
@@ -412,6 +342,20 @@ RequestMessage Structure:
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Size |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Length of Hash |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Hash (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Flags |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Number of Options |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Zero or more Option Structures \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct RequestMessage {
|
||||
@@ -419,6 +363,9 @@ struct RequestMessage {
|
||||
string Name<8192>;
|
||||
unsigned hyper Offset;
|
||||
unsigned int Size;
|
||||
opaque Hash<64>;
|
||||
unsigned int Flags;
|
||||
Option Options<64>;
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -458,6 +405,21 @@ func (o RequestMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteString(o.Name)
|
||||
xw.WriteUint64(o.Offset)
|
||||
xw.WriteUint32(o.Size)
|
||||
if l := len(o.Hash); l > 64 {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
|
||||
}
|
||||
xw.WriteBytes(o.Hash)
|
||||
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].encodeXDR(xw)
|
||||
if err != nil {
|
||||
return xw.Tot(), err
|
||||
}
|
||||
}
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
@@ -477,6 +439,16 @@ func (o *RequestMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Name = xr.ReadStringMax(8192)
|
||||
o.Offset = xr.ReadUint64()
|
||||
o.Size = xr.ReadUint32()
|
||||
o.Hash = xr.ReadBytesMax(64)
|
||||
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]).decodeXDR(xr)
|
||||
}
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
@@ -493,10 +465,13 @@ ResponseMessage Structure:
|
||||
\ Data (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Error |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct ResponseMessage {
|
||||
opaque Data<>;
|
||||
unsigned int Error;
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -527,6 +502,7 @@ func (o ResponseMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||
|
||||
func (o ResponseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
xw.WriteBytes(o.Data)
|
||||
xw.WriteUint32(o.Error)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
@@ -543,6 +519,7 @@ func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
|
||||
|
||||
func (o *ResponseMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Data = xr.ReadBytes()
|
||||
o.Error = xr.ReadUint32()
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
@@ -940,10 +917,13 @@ CloseMessage Structure:
|
||||
\ Reason (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Code |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
|
||||
struct CloseMessage {
|
||||
string Reason<1024>;
|
||||
unsigned int Code;
|
||||
}
|
||||
|
||||
*/
|
||||
@@ -977,6 +957,7 @@ func (o CloseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
||||
return xw.Tot(), xdr.ElementSizeExceeded("Reason", l, 1024)
|
||||
}
|
||||
xw.WriteString(o.Reason)
|
||||
xw.WriteUint32(o.Code)
|
||||
return xw.Tot(), xw.Error()
|
||||
}
|
||||
|
||||
@@ -993,6 +974,7 @@ func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
|
||||
|
||||
func (o *CloseMessage) decodeXDR(xr *xdr.Reader) error {
|
||||
o.Reason = xr.ReadStringMax(1024)
|
||||
o.Code = xr.ReadUint32()
|
||||
return xr.Error()
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,10 @@ var (
|
||||
ErrClosed = errors.New("connection closed")
|
||||
)
|
||||
|
||||
// Specific variants of empty messages...
|
||||
type pingMessage struct{ EmptyMessage }
|
||||
type pongMessage struct{ EmptyMessage }
|
||||
|
||||
type Model interface {
|
||||
// An index was received from the peer device
|
||||
Index(deviceID DeviceID, folder string, files []FileInfo)
|
||||
@@ -133,6 +137,10 @@ type encodable interface {
|
||||
AppendXDR([]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type isEofer interface {
|
||||
IsEOF() bool
|
||||
}
|
||||
|
||||
const (
|
||||
pingTimeout = 30 * time.Second
|
||||
pingIdleTime = 60 * time.Second
|
||||
@@ -183,7 +191,10 @@ func (c *rawConnection) Index(folder string, idx []FileInfo) error {
|
||||
default:
|
||||
}
|
||||
c.idxMut.Lock()
|
||||
c.send(-1, messageTypeIndex, IndexMessage{folder, idx})
|
||||
c.send(-1, messageTypeIndex, IndexMessage{
|
||||
Folder: folder,
|
||||
Files: idx,
|
||||
})
|
||||
c.idxMut.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -196,7 +207,10 @@ func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo) error {
|
||||
default:
|
||||
}
|
||||
c.idxMut.Lock()
|
||||
c.send(-1, messageTypeIndexUpdate, IndexMessage{folder, idx})
|
||||
c.send(-1, messageTypeIndexUpdate, IndexMessage{
|
||||
Folder: folder,
|
||||
Files: idx,
|
||||
})
|
||||
c.idxMut.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -218,7 +232,12 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
|
||||
c.awaiting[id] = rc
|
||||
c.awaitingMut.Unlock()
|
||||
|
||||
ok := c.send(id, messageTypeRequest, RequestMessage{folder, name, uint64(offset), uint32(size)})
|
||||
ok := c.send(id, messageTypeRequest, RequestMessage{
|
||||
Folder: folder,
|
||||
Name: name,
|
||||
Offset: uint64(offset),
|
||||
Size: uint32(size),
|
||||
})
|
||||
if !ok {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
@@ -274,48 +293,60 @@ func (c *rawConnection) readerLoop() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
switch hdr.msgType {
|
||||
case messageTypeIndex:
|
||||
if c.state < stateCCRcvd {
|
||||
return fmt.Errorf("protocol error: index message in state %d", c.state)
|
||||
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)
|
||||
}
|
||||
c.handleIndex(msg.(IndexMessage))
|
||||
c.state = stateIdxRcvd
|
||||
|
||||
case messageTypeIndexUpdate:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: index update message in state %d", c.state)
|
||||
switch hdr.msgType {
|
||||
case messageTypeIndex:
|
||||
if c.state < stateCCRcvd {
|
||||
return fmt.Errorf("protocol error: index message in state %d", c.state)
|
||||
}
|
||||
c.handleIndex(msg)
|
||||
c.state = stateIdxRcvd
|
||||
|
||||
case messageTypeIndexUpdate:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: index update message in state %d", c.state)
|
||||
}
|
||||
c.handleIndexUpdate(msg)
|
||||
}
|
||||
c.handleIndexUpdate(msg.(IndexMessage))
|
||||
|
||||
case messageTypeRequest:
|
||||
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)
|
||||
}
|
||||
// Requests are handled asynchronously
|
||||
go c.handleRequest(hdr.msgID, msg.(RequestMessage))
|
||||
go c.handleRequest(hdr.msgID, msg)
|
||||
|
||||
case messageTypeResponse:
|
||||
case ResponseMessage:
|
||||
if c.state < stateIdxRcvd {
|
||||
return fmt.Errorf("protocol error: response message in state %d", c.state)
|
||||
}
|
||||
c.handleResponse(hdr.msgID, msg.(ResponseMessage))
|
||||
c.handleResponse(hdr.msgID, msg)
|
||||
|
||||
case messageTypePing:
|
||||
c.send(hdr.msgID, messageTypePong, EmptyMessage{})
|
||||
case pingMessage:
|
||||
c.send(hdr.msgID, messageTypePong, pongMessage{})
|
||||
|
||||
case messageTypePong:
|
||||
case pongMessage:
|
||||
c.handlePong(hdr.msgID)
|
||||
|
||||
case messageTypeClusterConfig:
|
||||
case ClusterConfigMessage:
|
||||
if c.state != stateInitial {
|
||||
return fmt.Errorf("protocol error: cluster config message in state %d", c.state)
|
||||
}
|
||||
go c.receiver.ClusterConfig(c.id, msg.(ClusterConfigMessage))
|
||||
go c.receiver.ClusterConfig(c.id, msg)
|
||||
c.state = stateCCRcvd
|
||||
|
||||
case messageTypeClose:
|
||||
return errors.New(msg.(CloseMessage).Reason)
|
||||
case CloseMessage:
|
||||
return errors.New(msg.Reason)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
||||
@@ -341,6 +372,11 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
||||
l.Debugf("read header %v (msglen=%d)", hdr, msglen)
|
||||
}
|
||||
|
||||
if hdr.version != 0 {
|
||||
err = fmt.Errorf("unknown protocol version 0x%x", hdr.version)
|
||||
return
|
||||
}
|
||||
|
||||
if cap(c.rdbuf0) < msglen {
|
||||
c.rdbuf0 = make([]byte, msglen)
|
||||
} else {
|
||||
@@ -376,33 +412,58 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// We check each returned error for the XDRError.IsEOF() method.
|
||||
// IsEOF()==true here means that the message contained fewer fields than
|
||||
// expected. It does not signify an EOF on the socket, because we've
|
||||
// successfully read a size value and that many bytes already. New fields
|
||||
// we expected but the other peer didn't send should be interpreted as
|
||||
// zero/nil, and if that's not valid we'll verify it somewhere else.
|
||||
|
||||
switch hdr.msgType {
|
||||
case messageTypeIndex, messageTypeIndexUpdate:
|
||||
var idx IndexMessage
|
||||
err = idx.UnmarshalXDR(msgBuf)
|
||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||
err = nil
|
||||
}
|
||||
msg = idx
|
||||
|
||||
case messageTypeRequest:
|
||||
var req RequestMessage
|
||||
err = req.UnmarshalXDR(msgBuf)
|
||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||
err = nil
|
||||
}
|
||||
msg = req
|
||||
|
||||
case messageTypeResponse:
|
||||
var resp ResponseMessage
|
||||
err = resp.UnmarshalXDR(msgBuf)
|
||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||
err = nil
|
||||
}
|
||||
msg = resp
|
||||
|
||||
case messageTypePing, messageTypePong:
|
||||
msg = EmptyMessage{}
|
||||
case messageTypePing:
|
||||
msg = pingMessage{}
|
||||
|
||||
case messageTypePong:
|
||||
msg = pongMessage{}
|
||||
|
||||
case messageTypeClusterConfig:
|
||||
var cc ClusterConfigMessage
|
||||
err = cc.UnmarshalXDR(msgBuf)
|
||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||
err = nil
|
||||
}
|
||||
msg = cc
|
||||
|
||||
case messageTypeClose:
|
||||
var cm CloseMessage
|
||||
err = cm.UnmarshalXDR(msgBuf)
|
||||
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||
err = nil
|
||||
}
|
||||
msg = cm
|
||||
|
||||
default:
|
||||
@@ -429,7 +490,9 @@ func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
|
||||
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
|
||||
data, _ := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), int(req.Size))
|
||||
|
||||
c.send(msgID, messageTypeResponse, ResponseMessage{data})
|
||||
c.send(msgID, messageTypeResponse, ResponseMessage{
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
|
||||
|
||||
@@ -189,7 +189,7 @@ func TestVersionErr(t *testing.T) {
|
||||
msgID: 0,
|
||||
msgType: 0,
|
||||
}))
|
||||
w.WriteUint32(0)
|
||||
w.WriteUint32(0) // Avoids reader closing due to EOF
|
||||
|
||||
if !m1.isClosed() {
|
||||
t.Error("Connection should close due to unknown version")
|
||||
@@ -212,7 +212,7 @@ func TestTypeErr(t *testing.T) {
|
||||
msgID: 0,
|
||||
msgType: 42,
|
||||
}))
|
||||
w.WriteUint32(0)
|
||||
w.WriteUint32(0) // Avoids reader closing due to EOF
|
||||
|
||||
if !m1.isClosed() {
|
||||
t.Error("Connection should close due to unknown message type")
|
||||
|
||||
@@ -49,6 +49,8 @@ type Walker struct {
|
||||
// detected. Scanned files will get zero permission bits and the
|
||||
// NoPermissionBits flag set.
|
||||
IgnorePerms bool
|
||||
// Number of routines to use for hashing
|
||||
Hashers int
|
||||
}
|
||||
|
||||
type TempNamer interface {
|
||||
@@ -60,7 +62,7 @@ type TempNamer interface {
|
||||
|
||||
type CurrentFiler interface {
|
||||
// CurrentFile returns the file as seen at last scan.
|
||||
CurrentFile(name string) protocol.FileInfo
|
||||
CurrentFile(name string) (protocol.FileInfo, bool)
|
||||
}
|
||||
|
||||
// Walk returns the list of files found in the local folder by scanning the
|
||||
@@ -75,9 +77,14 @@ func (w *Walker) Walk() (chan protocol.FileInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workers := w.Hashers
|
||||
if workers < 1 {
|
||||
workers = runtime.NumCPU()
|
||||
}
|
||||
|
||||
files := make(chan protocol.FileInfo)
|
||||
hashedFiles := make(chan protocol.FileInfo)
|
||||
newParallelHasher(w.Dir, w.BlockSize, runtime.NumCPU(), hashedFiles, files)
|
||||
newParallelHasher(w.Dir, w.BlockSize, workers, hashedFiles, files)
|
||||
|
||||
go func() {
|
||||
hashFiles := w.walkAndHashFiles(files)
|
||||
@@ -183,13 +190,14 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
|
||||
if w.CurrentFiler != nil {
|
||||
// A symlink is "unchanged", if
|
||||
// - it exists
|
||||
// - it wasn't deleted (because it isn't now)
|
||||
// - it was a symlink
|
||||
// - it wasn't invalid
|
||||
// - the symlink type (file/dir) was the same
|
||||
// - the block list (i.e. hash of target) was the same
|
||||
cf := w.CurrentFiler.CurrentFile(rn)
|
||||
if !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && SymlinkTypeEqual(flags, cf.Flags) && BlocksEqual(cf.Blocks, blocks) {
|
||||
cf, ok := w.CurrentFiler.CurrentFile(rn)
|
||||
if ok && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && SymlinkTypeEqual(flags, cf.Flags) && BlocksEqual(cf.Blocks, blocks) {
|
||||
return rval
|
||||
}
|
||||
}
|
||||
@@ -214,14 +222,15 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
if info.Mode().IsDir() {
|
||||
if w.CurrentFiler != nil {
|
||||
// A directory is "unchanged", if it
|
||||
// - exists
|
||||
// - has the same permissions as previously, unless we are ignoring permissions
|
||||
// - was not marked deleted (since it apparently exists now)
|
||||
// - was a directory previously (not a file or something else)
|
||||
// - was not a symlink (since it's a directory now)
|
||||
// - was not invalid (since it looks valid now)
|
||||
cf := w.CurrentFiler.CurrentFile(rn)
|
||||
cf, ok := w.CurrentFiler.CurrentFile(rn)
|
||||
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Flags, uint32(info.Mode()))
|
||||
if permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
|
||||
if ok && permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -248,6 +257,7 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
if info.Mode().IsRegular() {
|
||||
if w.CurrentFiler != nil {
|
||||
// A file is "unchanged", if it
|
||||
// - exists
|
||||
// - has the same permissions as previously, unless we are ignoring permissions
|
||||
// - was not marked deleted (since it apparently exists now)
|
||||
// - had the same modification time as it has now
|
||||
@@ -255,9 +265,9 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFun
|
||||
// - was not a symlink (since it's a file now)
|
||||
// - was not invalid (since it looks valid now)
|
||||
// - has the same size as previously
|
||||
cf := w.CurrentFiler.CurrentFile(rn)
|
||||
cf, ok := w.CurrentFiler.CurrentFile(rn)
|
||||
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Flags, uint32(info.Mode()))
|
||||
if permUnchanged && !cf.IsDeleted() && cf.Modified == info.ModTime().Unix() && !cf.IsDirectory() &&
|
||||
if ok && permUnchanged && !cf.IsDeleted() && cf.Modified == info.ModTime().Unix() && !cf.IsDirectory() &&
|
||||
!cf.IsSymlink() && !cf.IsInvalid() && cf.Size() == info.Size() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation, either version 3 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func releaseName(tag string) string {
|
||||
return fmt.Sprintf("syncthing-macosx-%s-%s.", runtime.GOARCH, tag)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation, either version 3 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func releaseName(tag string) string {
|
||||
return fmt.Sprintf("syncthing-linux-armv%s-%s.", goARM(), tag)
|
||||
}
|
||||
|
||||
// Get the current ARM architecture version for upgrade purposes. If we can't
|
||||
// figure it out from the uname, default to ARMv6 (same as Go distribution).
|
||||
func goARM() string {
|
||||
var name syscall.Utsname
|
||||
syscall.Uname(&name)
|
||||
machine := string(name.Machine[:5])
|
||||
if strings.HasPrefix(machine, "armv") {
|
||||
return machine[4:]
|
||||
}
|
||||
return "6"
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation, either version 3 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !arm,!darwin
|
||||
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func releaseName(tag string) string {
|
||||
return fmt.Sprintf("syncthing-%s-%s-%s.", runtime.GOOS, runtime.GOARCH, tag)
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package upgrade
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -225,3 +227,12 @@ func versionParts(v string) ([]int, []interface{}) {
|
||||
|
||||
return release, prerelease
|
||||
}
|
||||
|
||||
func releaseName(tag string) string {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return fmt.Sprintf("syncthing-macosx-%s-%s.", runtime.GOARCH, tag)
|
||||
default:
|
||||
return fmt.Sprintf("syncthing-%s-%s-%s.", runtime.GOOS, runtime.GOARCH, tag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,19 +448,27 @@ func getIGDServices(rootURL string, device upnpDevice, wanDeviceURN string, wanC
|
||||
}
|
||||
|
||||
func replaceRawPath(u *url.URL, rp string) {
|
||||
var p, q string
|
||||
fs := strings.Split(rp, "?")
|
||||
p = fs[0]
|
||||
if len(fs) > 1 {
|
||||
q = fs[1]
|
||||
}
|
||||
|
||||
if p[0] == '/' {
|
||||
u.Path = p
|
||||
asURL, err := url.Parse(rp)
|
||||
if err != nil {
|
||||
return
|
||||
} else if asURL.IsAbs() {
|
||||
u.Path = asURL.Path
|
||||
u.RawQuery = asURL.RawQuery
|
||||
} else {
|
||||
u.Path += p
|
||||
var p, q string
|
||||
fs := strings.Split(rp, "?")
|
||||
p = fs[0]
|
||||
if len(fs) > 1 {
|
||||
q = fs[1]
|
||||
}
|
||||
|
||||
if p[0] == '/' {
|
||||
u.Path = p
|
||||
} else {
|
||||
u.Path += p
|
||||
}
|
||||
u.RawQuery = q
|
||||
}
|
||||
u.RawQuery = q
|
||||
}
|
||||
|
||||
func soapRequest(url, service, function, message string) ([]byte, error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ package upnp
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -40,3 +41,25 @@ func TestExternalIPParsing(t *testing.T) {
|
||||
t.Error("Parse of SOAP request failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlURLParsing(t *testing.T) {
|
||||
rootURL := "http://192.168.243.1:80/igd.xml"
|
||||
|
||||
u, _ := url.Parse(rootURL)
|
||||
subject := "/upnp?control=WANCommonIFC1"
|
||||
expected := "http://192.168.243.1:80/upnp?control=WANCommonIFC1"
|
||||
replaceRawPath(u, subject)
|
||||
|
||||
if u.String() != expected {
|
||||
t.Error("URL normalization of", subject, "failed; expected", expected, "got", u.String())
|
||||
}
|
||||
|
||||
u, _ = url.Parse(rootURL)
|
||||
subject = "http://192.168.243.1:80/upnp?control=WANCommonIFC1"
|
||||
expected = "http://192.168.243.1:80/upnp?control=WANCommonIFC1"
|
||||
replaceRawPath(u, subject)
|
||||
|
||||
if u.String() != expected {
|
||||
t.Error("URL normalization of", subject, "failed; expected", expected, "got", u.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Syncthing uses the protocols defined in
|
||||
https://github.com/syncthing/protocol/.
|
||||
https://github.com/syncthing/specs/.
|
||||
|
||||
@@ -101,10 +101,10 @@ func testFileTypeChange(t *testing.T) {
|
||||
log.Println("Syncing...")
|
||||
|
||||
sender := syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = sender.start()
|
||||
if err != nil {
|
||||
@@ -112,10 +112,10 @@ func testFileTypeChange(t *testing.T) {
|
||||
}
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
instance: "2",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -41,9 +41,9 @@ var jsonEndpoints = []string{
|
||||
|
||||
func TestGetIndex(t *testing.T) {
|
||||
st := syncthingProcess{
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
instance: "2",
|
||||
}
|
||||
err := st.start()
|
||||
if err != nil {
|
||||
@@ -84,9 +84,9 @@ func TestGetIndex(t *testing.T) {
|
||||
|
||||
func TestGetIndexAuth(t *testing.T) {
|
||||
st := syncthingProcess{
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
instance: "1",
|
||||
}
|
||||
err := st.start()
|
||||
if err != nil {
|
||||
@@ -142,9 +142,9 @@ func TestGetIndexAuth(t *testing.T) {
|
||||
|
||||
func TestGetJSON(t *testing.T) {
|
||||
st := syncthingProcess{
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
instance: "2",
|
||||
}
|
||||
err := st.start()
|
||||
if err != nil {
|
||||
@@ -174,9 +174,9 @@ func TestGetJSON(t *testing.T) {
|
||||
|
||||
func TestPOSTWithoutCSRF(t *testing.T) {
|
||||
st := syncthingProcess{
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
instance: "2",
|
||||
}
|
||||
err := st.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -39,10 +39,10 @@ func TestStressHTTP(t *testing.T) {
|
||||
|
||||
log.Println("Starting up...")
|
||||
sender := syncthingProcess{ // id1
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
instance: "2",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = sender.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -36,10 +36,10 @@ func TestIgnores(t *testing.T) {
|
||||
}
|
||||
|
||||
p := syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = p.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -43,10 +43,10 @@ func TestManyPeers(t *testing.T) {
|
||||
}
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
instance: "2",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
@@ -90,10 +90,10 @@ func TestManyPeers(t *testing.T) {
|
||||
|
||||
log.Println("Starting up...")
|
||||
sender := syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = sender.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -46,10 +46,10 @@ func TestParallellScan(t *testing.T) {
|
||||
|
||||
log.Println("Starting up...")
|
||||
st := syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = st.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -54,10 +54,10 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
|
||||
|
||||
log.Println("Starting up...")
|
||||
sender := syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = sender.start()
|
||||
if err != nil {
|
||||
@@ -65,10 +65,10 @@ func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool
|
||||
}
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
instance: "2",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -177,10 +177,10 @@ func testSymlinks(t *testing.T) {
|
||||
log.Println("Syncing...")
|
||||
|
||||
sender := syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = sender.start()
|
||||
if err != nil {
|
||||
@@ -188,10 +188,10 @@ func testSymlinks(t *testing.T) {
|
||||
}
|
||||
|
||||
receiver := syncthingProcess{ // id2
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
instance: "2",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = receiver.start()
|
||||
if err != nil {
|
||||
|
||||
@@ -254,10 +254,10 @@ func scStartProcesses() ([]syncthingProcess, error) {
|
||||
p := make([]syncthingProcess, 3)
|
||||
|
||||
p[0] = syncthingProcess{ // id1
|
||||
log: "1.out",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
instance: "1",
|
||||
argv: []string{"-home", "h1"},
|
||||
port: 8081,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err := p[0].start()
|
||||
if err != nil {
|
||||
@@ -265,10 +265,10 @@ func scStartProcesses() ([]syncthingProcess, error) {
|
||||
}
|
||||
|
||||
p[1] = syncthingProcess{ // id2
|
||||
log: "2.out",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
instance: "2",
|
||||
argv: []string{"-home", "h2"},
|
||||
port: 8082,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = p[1].start()
|
||||
if err != nil {
|
||||
@@ -277,10 +277,10 @@ func scStartProcesses() ([]syncthingProcess, error) {
|
||||
}
|
||||
|
||||
p[2] = syncthingProcess{ // id3
|
||||
log: "3.out",
|
||||
argv: []string{"-home", "h3"},
|
||||
port: 8083,
|
||||
apiKey: apiKey,
|
||||
instance: "3",
|
||||
argv: []string{"-home", "h3"},
|
||||
port: 8083,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
err = p[2].start()
|
||||
if err != nil {
|
||||
|
||||
@@ -37,7 +37,7 @@ var env = []string{
|
||||
}
|
||||
|
||||
type syncthingProcess struct {
|
||||
log string
|
||||
instance string
|
||||
argv []string
|
||||
port int
|
||||
apiKey string
|
||||
@@ -50,14 +50,26 @@ type syncthingProcess struct {
|
||||
|
||||
func (p *syncthingProcess) start() error {
|
||||
if p.logfd == nil {
|
||||
logfd, err := os.Create(p.log)
|
||||
logfd, err := os.Create(p.instance + ".out")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.logfd = logfd
|
||||
}
|
||||
|
||||
cmd := exec.Command("../bin/syncthing", p.argv...)
|
||||
binary := "../bin/syncthing"
|
||||
|
||||
// We check to see if there's an instance specific binary we should run,
|
||||
// for example if we are running integration tests between different
|
||||
// versions. If there isn't, we just go with the default.
|
||||
if _, err := os.Stat(binary + "-" + p.instance); err == nil {
|
||||
binary = binary + "-" + p.instance
|
||||
}
|
||||
if _, err := os.Stat(binary + "-" + p.instance + ".exe"); err == nil {
|
||||
binary = binary + "-" + p.instance + ".exe"
|
||||
}
|
||||
|
||||
cmd := exec.Command(binary, p.argv...)
|
||||
cmd.Stdout = p.logfd
|
||||
cmd.Stderr = p.logfd
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
@@ -82,7 +94,7 @@ func (p *syncthingProcess) stop() error {
|
||||
p.cmd.Process.Signal(os.Kill)
|
||||
p.cmd.Wait()
|
||||
|
||||
fd, err := os.Open(p.log)
|
||||
fd, err := os.Open(p.instance + ".out")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user