Compare commits

...

49 Commits

Author SHA1 Message Date
Jakob Borg
1816320124 One more translation update 2015-01-11 21:19:42 +01:00
Jakob Borg
f09bfe293d Translation update 2015-01-11 20:31:30 +01:00
Jakob Borg
7b4e8fda4b Modal dialog titles must be manually translated 2015-01-11 14:48:40 +01:00
Audrius Butkevicius
c95812353f Merge pull request #1236 from syncthing/flag-safe
Reject Index and Request messages with unexpected flags
2015-01-11 12:40:40 +00:00
Jakob Borg
b622ec7a28 Reject Index and Request messages with unexpected flags 2015-01-11 13:29:01 +01:00
Jakob Borg
d8fbe7b77f Refactor readerLoop to switch on message type directly 2015-01-11 13:24:56 +01:00
Jakob Borg
dbcac37d91 Can run integration tests between different versions 2015-01-11 09:55:44 +01:00
Jakob Borg
d4d391b34f Change integration test "log" field to "instance" 2015-01-11 09:55:17 +01:00
Jakob Borg
571cf7d490 Merge pull request #1182 from AudriusButkevicius/autoauto
Connecting to a newer node triggers autoupgrade check (fixes #1177)
2015-01-11 09:16:12 +01:00
Jakob Borg
e18b19ca5a Translation base & assets update 2015-01-10 18:15:08 +01:00
Jakob Borg
48651bf482 Merge pull request #1234 from uok/master
Small improvements for footer navbar (ref #1154)
2015-01-10 18:14:47 +01:00
Audrius Butkevicius
5034a41c08 Connecting to a newer node triggers autoupgrade check (fixes #1177) 2015-01-10 17:05:19 +00:00
Ben Schulz
6795173e77 Small improvements for footer navbar 2015-01-10 18:02:27 +01:00
Jakob Borg
219ef996f5 Merge pull request #1226 from syncthing/deregister-fix
All roads lead to Finisher (fixes #1201)
2015-01-10 17:53:01 +01:00
Jakob Borg
00af1db275 Translation base & assets update 2015-01-10 17:51:18 +01:00
Jakob Borg
5935ea896f Merge pull request #1232 from facastagnini/master
"Quick guide to supported patterns" link updated
2015-01-10 17:50:23 +01:00
Jakob Borg
459983c05e Add facastagnini 2015-01-10 17:47:41 +01:00
Jakob Borg
ebf4f029ac Merge pull request #1229 from AudriusButkevicius/cfg-hasher
Make parallel hasher configurable, remove finisher setting (fixes #1199)
2015-01-10 17:45:15 +01:00
Jakob Borg
0eec945df1 Merge pull request #1230 from AudriusButkevicius/separator
Expose and use path separator (fixes #1163)
2015-01-10 17:43:41 +01:00
Jakob Borg
8824b9d68f Merge pull request #1231 from AudriusButkevicius/text
Rename "Last File Synced" to "Last File Received" (fixes #1145)
2015-01-10 17:43:06 +01:00
Jakob Borg
d2862814c5 Merge pull request #1233 from AudriusButkevicius/disco-log
Make discovery logging a bit better (fixes #1188)
2015-01-10 17:42:13 +01:00
Audrius Butkevicius
25fece2d50 Make discovery logging a bit better (fixes #1188) 2015-01-10 16:15:16 +00:00
Federico Castagnini
beb4239d1b The "Quick guide to supported patterns" link now points to the wiki article and will open in a new page/tab to avoid disrupting the settings page. 2015-01-10 10:42:45 -05:00
Audrius Butkevicius
2b78e37d92 Rename "Last File Synced" to "Last File Received" (fixes #1145)
IMHO this is more clear
2015-01-10 14:55:08 +00:00
Audrius Butkevicius
a2070d9ce4 Expose and use path separator (fixes #1163) 2015-01-10 14:51:29 +00:00
Audrius Butkevicius
5827a686b8 Make parallel hasher configurable, remove finisher setting (fixes #1199) 2015-01-10 14:32:20 +00:00
Audrius Butkevicius
dec479532e All roads lead to Finisher (fixes #1201) 2015-01-10 13:45:48 +00:00
Jakob Borg
5d173168cc Merge pull request #1214 from bigbear2nd/master
Added colored status indicator for narrow screens. (Fixes #1084)
2015-01-09 16:23:24 +01:00
bigbear2nd
2aac1cde04 Added colored status indicator for narrow screens. (Fixes #1084) 2015-01-09 20:54:00 +09:00
Audrius Butkevicius
3676f0268f Merge pull request #1220 from syncthing/arm-build
Only build ARMv5 (fixes #1218)
2015-01-09 10:25:50 +00:00
Audrius Butkevicius
a7b75a54bb Merge pull request #1219 from syncthing/refactor-truncated
Refactor stuff around FileInfoTruncated
2015-01-09 10:12:48 +00:00
Jakob Borg
961a87b743 Only build ARMv5 (fixes #1218)
With this change, the build system only builds one ARM variant - ARMv5.
We call the build architecture simply "arm", as this is what
runtime.GOARCH says.
2015-01-09 10:45:15 +01:00
Jakob Borg
e03d59e381 The protocol specs moved again 2015-01-09 08:54:19 +01:00
Jakob Borg
d46ce5003c Implement GetGlobalTruncated 2015-01-09 08:41:02 +01:00
Jakob Borg
4c4143d9be Move FileInfoTruncated to files package
This is where it's used, and it clarifies that it's never used over the
wire.
2015-01-09 08:28:24 +01:00
Jakob Borg
8bc7d259f4 Move FileIntf to files package, expose Iterator type
This is where FileIntf is used, so it should be defined here (it's not
a protocol thing, really).
2015-01-09 08:18:42 +01:00
Jakob Borg
2d047fa428 Remove unused types 2015-01-09 08:14:02 +01:00
Audrius Butkevicius
735d420d40 Merge pull request #1215 from syncthing/new-proto-bc
Add some new protocol fields
2015-01-08 21:54:01 +00:00
Jakob Borg
bc9fc1aece Actually close connection based on unknown protocol version 2015-01-08 22:11:26 +01:00
Jakob Borg
b88e3c99c1 Add fields for future extensibility
This adds a number of fields to the end of existing messages. This is a
backwards compatible change.
2015-01-08 22:11:26 +01:00
Jakob Borg
ce3e6e084c Ensure backwards compatibility before modifying protocol
This change makes sure that things work smoothly when "we" are a newer
version than our peer and have more fields in our messages than they do.
Missing fields will be left at zero/nil.

(The other side will ignore our extra fields, for the same effect.)
2015-01-08 14:25:11 +01:00
Jakob Borg
2a58ca7697 Merge pull request #1212 from cqcallaw/upnp
Properly handle absolute URLs when parsing UPnP service control URLs
2015-01-08 08:50:30 +01:00
Caleb Callaway
af96f7a0cd Properly handle absolute URLs when parsing UPnP service control URLs
Fixes #1187
2015-01-07 21:23:20 -08:00
Jakob Borg
7d39d1a925 Make it possible to include extra external files into binary packages 2015-01-07 16:15:50 +01:00
Jakob Borg
1b6c700e18 Merge remote-tracking branch 'origin/pr/1198'
* origin/pr/1198:
  Fix rendering issue on firefox when zoomed (fixes #1197)
2015-01-07 14:24:49 +01:00
Tim Abell
6304bd60ee Fix rendering issue on firefox when zoomed (fixes #1197)
issue #1197
2015-01-07 09:27:17 +00:00
Jakob Borg
4ad4417740 Add timabell 2015-01-07 08:36:54 +01:00
Jakob Borg
6a4c259a73 Merge pull request #1196 from AudriusButkevicius/finddevice
Add device finder utility
2015-01-07 08:31:54 +01:00
Audrius Butkevicius
12eabb220d Add device finder utility 2015-01-06 23:12:12 +00:00
73 changed files with 1173 additions and 661 deletions

View File

@@ -16,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>
@@ -31,6 +32,7 @@ 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
View File

@@ -23,7 +23,7 @@
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "45c46b7db7ff83b8b9ee09bbd95f36ab50043ece"
"Rev": "214788d8fedfc310c18eca9ed12be408a5054cd5"
},
{
"ImportPath": "github.com/juju/ratelimit",

View File

@@ -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

2
NICKS
View File

@@ -16,6 +16,7 @@ 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>
@@ -32,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>

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View 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)
}
}

View File

@@ -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
})

View File

@@ -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)
@@ -782,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,
@@ -792,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

View File

@@ -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

View File

@@ -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 \

View File

@@ -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;
}
}

View File

@@ -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",

View File

@@ -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": "Синхронизация",

View File

@@ -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",

View File

@@ -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",
@@ -75,7 +78,7 @@
"No": "Nein",
"No File Versioning": "Keine Dateiversionierung",
"Notice": "Hinweis",
"OK": "Ok",
"OK": "OK",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Nicht synchronisiert",
@@ -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",

View File

@@ -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",

View File

@@ -13,6 +13,7 @@
"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.",
@@ -25,6 +26,7 @@
"Device Identification": "Identificación del dispositivo",
"Device Name": "Nombre del dispositivo",
"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,7 +54,7 @@
"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": "Ignorar",
"Ignore Patterns": "Patrones de exclusión",
@@ -61,14 +63,15 @@
"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": "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": "Nuevo dispositivo",
"New Folder": "Nuevo repositorio",
@@ -89,7 +92,7 @@
"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",
@@ -111,11 +114,12 @@
"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",
@@ -160,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",
"items": "Ítems",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."
}

View File

@@ -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.",
@@ -25,6 +26,7 @@
"Device Identification": "Identification de l'appareil",
"Device Name": "Nom du périphérique",
"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",
@@ -61,6 +63,7 @@
"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": "Plus tard",
@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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,7 +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 seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing turi problemų su jūsų užklausa. Perkraukite puslapį arba perkraukite Syncthing jei problema nedings.",
"The aggregated statistics are publicly available at {%url%}.": "Naudojimosi ataskaitą galite peržiūrėti adresu: {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Nauji nustatymai išsaugoti, bet neaktyvuoti. Perleiskite Syncthing programą iš naujo norėdami įgalinti naujus nustatymus.",
"The device ID cannot be blank.": "Įrenginio ID negali būti tuščias.",
@@ -161,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}}\""
}

View File

@@ -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",

View File

@@ -13,6 +13,7 @@
"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.",
@@ -25,6 +26,7 @@
"Device Identification": "Toestel identificatie",
"Device Name": "Naam toestel",
"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",
@@ -61,6 +63,7 @@
"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 Received": "Last File Received",
"Last File Synced": "Laatste Gesynchroniseerde Bestand",
"Last seen": "Laatst gezien op",
"Later": "Later",
@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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.",
@@ -25,6 +26,7 @@
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"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",
@@ -61,6 +63,7 @@
"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": "Mais tarde",
@@ -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,7 +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.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "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.",

View File

@@ -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": "Синхронизация",

View File

@@ -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",

View File

@@ -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": "同步完成度",

View File

@@ -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": "同步作業",
@@ -161,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}}\""
}

View File

@@ -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>&emsp;{{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">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="stopped"><span class="hidden-xs" translate>Stopped</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="scanning"><span class="hidden-xs" translate>Scanning</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span 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>&emsp;<span translate>Last File Synced</span></th>
<th><span class="glyphicon glyphicon-transfer"></span>&emsp;<span translate>Last File Received</span></th>
<td class="text-right">
<span title="{{folderStats[folder.ID].LastFile.Filename}} @ {{folderStats[folder.ID].LastFile.At | date:'yyyy-MM-dd HH:mm'}}">
{{folderStats[folder.ID].LastFile.Filename | basename}}
@@ -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>&emsp;{{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">&#9724;</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">&#9724;</span></span>
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs">&#9724;</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>&ensp;<span translate>Documentation</span></a></li>
<li><a class="navbar-link" href="https://discourse.syncthing.net"><span class="glyphicon glyphicon-question-sign"></span>&ensp;<span translate>Support</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/releases"><span class="glyphicon glyphicon-info-sign"></span>&ensp;<span translate>Changelog</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing/issues"><span class="glyphicon glyphicon-warning-sign"></span>&ensp;<span translate>Bugs</span></a></li>
<li><a class="navbar-link" href="https://github.com/syncthing/syncthing"><span class="glyphicon glyphicon-wrench"></span>&ensp;<span translate>Source Code</span></a></li>
</ul>
</div>
</nav>
@@ -438,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>
@@ -681,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>
@@ -690,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>&emsp;<span translate>Save</span></button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Close</span></button>
</div>
@@ -864,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>
@@ -914,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/>
@@ -938,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>
@@ -952,6 +955,7 @@
<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>

View File

@@ -889,6 +889,9 @@ angular.module('syncthing.core')
$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;

View File

File diff suppressed because one or more lines are too long

View File

@@ -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))
}

View File

@@ -89,7 +89,7 @@ func TestDeviceConfig(t *testing.T) {
RescanIntervalS: 600,
Copiers: 1,
Pullers: 16,
Finishers: 1,
Hashers: 0,
},
}
expectedDevices := []DeviceConfiguration{

View File

@@ -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
}

View File

@@ -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]

View File

@@ -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

View File

@@ -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)
@@ -612,7 +610,7 @@ func ldbGet(db *leveldb.DB, folder, device, file []byte) (protocol.FileInfo, boo
return f, true
}
func ldbGetGlobal(db *leveldb.DB, folder, file []byte) (protocol.FileInfo, bool) {
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, bool)
}
bs, err := snap.Get(k, nil)
if err == leveldb.ErrNotFound {
return protocol.FileInfo{}, false
return nil, false
}
if err != nil {
panic(err)
@@ -658,15 +656,14 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte) (protocol.FileInfo, bool)
panic(err)
}
var f protocol.FileInfo
err = f.UnmarshalXDR(bs)
fi, err := unmarshalTrunc(bs, truncate)
if err != nil {
panic(err)
}
return f, true
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 {

View File

@@ -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
@@ -132,42 +140,42 @@ 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)
}
@@ -181,9 +189,23 @@ func (s *Set) Get(device protocol.DeviceID, file string) (protocol.FileInfo, boo
}
func (s *Set) GetGlobal(file string) (protocol.FileInfo, bool) {
f, ok := ldbGetGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)))
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, ok
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:

View File

@@ -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

View 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
}

View 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()
}

View File

@@ -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,42 +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 {
if f, ok := rf.GetGlobal(name); ok {
progress[i] = f.ToTruncated() /// XXX: Should implement GetGlobalTruncated directly
if f, ok := rf.GetGlobalTruncated(name); ok {
progress[i] = f
seen[name] = true
}
}
for i, name := range queuedNames {
if f, ok := rf.GetGlobal(name); ok {
queued[i] = f.ToTruncated() /// XXX: Should implement GetGlobalTruncated directly
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)
}
@@ -577,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
@@ -970,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
@@ -1138,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)
@@ -1169,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
@@ -1310,7 +1323,7 @@ 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)

View File

@@ -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{})

View File

@@ -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
@@ -612,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)
@@ -686,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()
@@ -738,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
}
@@ -766,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
}
}
@@ -862,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)

View File

@@ -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")
}
}

View File

@@ -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),
}
}

View File

@@ -86,5 +86,5 @@ func TestReadOnlyDir(t *testing.T) {
t.Fatal("Unexpected nil fd")
}
s.earlyClose("Test done", nil)
s.fail("Test done", nil)
}

View File

@@ -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{}

View File

@@ -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()
}

View File

@@ -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) {

View File

@@ -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")

View File

@@ -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 {
@@ -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)

View File

@@ -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)
}

View File

@@ -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"
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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())
}
}

View File

@@ -1,2 +1,2 @@
Syncthing uses the protocols defined in
https://github.com/syncthing/protocol/.
https://github.com/syncthing/specs/.

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}