Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9aa375109 | ||
|
|
f7d2c58783 | ||
|
|
4d3e0de4ba | ||
|
|
9dbc509996 | ||
|
|
baec3f909c | ||
|
|
c41aaad3bb | ||
|
|
9682bbfbda | ||
|
|
9e6a1fdcd4 | ||
|
|
49bddfbe53 | ||
|
|
55e0ac3e24 | ||
|
|
cbcc3ea132 | ||
|
|
ab132ff6fe | ||
|
|
19e52a10df | ||
|
|
a8145e187f | ||
|
|
e33fa10115 | ||
|
|
4b6e7e7867 | ||
|
|
70d121a94b | ||
|
|
33ffb07d31 | ||
|
|
7aaa92ac47 | ||
|
|
5883eb9a25 | ||
|
|
e60efa2b3d | ||
|
|
ddf6d64faa | ||
|
|
c7221b035d | ||
|
|
a69ba18f62 | ||
|
|
b31611a8d1 | ||
|
|
0ca0e3e9bd | ||
|
|
0a96a1150b | ||
|
|
b8c249cddc | ||
|
|
e8ba6d4771 | ||
|
|
606fce09ca |
@@ -2,11 +2,10 @@
|
||||
|
||||
---
|
||||
|
||||
[](https://build.syncthing.net/job/syncthing/lastBuild/)
|
||||
[](https://build.syncthing.net/job/syncthing/lastBuild/)
|
||||
[](https://build.syncthing.net/job/syncthing/lastBuild/)
|
||||
[](https://build.syncthing.net/job/syncthing/lastBuild/)
|
||||
[](https://godoc.org/github.com/syncthing/syncthing)
|
||||
[](https://build.syncthing.net/latest/)
|
||||
[](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildLinuxCross&guest=1)
|
||||
[](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildWindows&guest=1)
|
||||
[](https://build.syncthing.net/viewType.html?buildTypeId=Syncthing_BuildMac&guest=1)
|
||||
[](https://www.mozilla.org/MPL/2.0/)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/88)
|
||||
[](https://goreportcard.com/report/github.com/syncthing/syncthing)
|
||||
@@ -111,4 +110,4 @@ All code is licensed under the [MPLv2 License][7].
|
||||
[12]: https://www.bountysource.com/teams/syncthing/issues
|
||||
[13]: https://github.com/syncthing/syncthing/blob/master/GOALS.md
|
||||
[14]: assets/logo-text-128.png
|
||||
[15]: https://syncthing.net/
|
||||
[15]: https://syncthing.net/
|
||||
|
||||
@@ -88,10 +88,10 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
|
||||
}
|
||||
|
||||
rn, _ := filepath.Rel(dir, path)
|
||||
if rn == "." || rn == ".stfolder" {
|
||||
if rn == "." {
|
||||
return nil
|
||||
}
|
||||
if rn == ".stversions" {
|
||||
if rn == ".stversions" || rn == ".stfolder" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,9 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
|
||||
go messageReader(conn, messages, errors)
|
||||
|
||||
pingTicker := time.NewTicker(pingInterval)
|
||||
defer pingTicker.Stop()
|
||||
timeoutTicker := time.NewTimer(networkTimeout)
|
||||
defer timeoutTicker.Stop()
|
||||
joined := false
|
||||
|
||||
for {
|
||||
|
||||
+11
-6
@@ -64,12 +64,13 @@ var (
|
||||
|
||||
limitCheckTimer *time.Timer
|
||||
|
||||
sessionLimitBps int
|
||||
globalLimitBps int
|
||||
overLimit int32
|
||||
descriptorLimit int64
|
||||
sessionLimiter *rate.Limiter
|
||||
globalLimiter *rate.Limiter
|
||||
sessionLimitBps int
|
||||
globalLimitBps int
|
||||
overLimit int32
|
||||
descriptorLimit int64
|
||||
sessionLimiter *rate.Limiter
|
||||
globalLimiter *rate.Limiter
|
||||
networkBufferSize int
|
||||
|
||||
statusAddr string
|
||||
poolAddrs string
|
||||
@@ -81,6 +82,8 @@ var (
|
||||
natLease int
|
||||
natRenewal int
|
||||
natTimeout int
|
||||
|
||||
pprofEnabled bool
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -105,6 +108,8 @@ func main() {
|
||||
flag.IntVar(&natLease, "nat-lease", 60, "NAT lease length in minutes")
|
||||
flag.IntVar(&natRenewal, "nat-renewal", 30, "NAT renewal frequency in minutes")
|
||||
flag.IntVar(&natTimeout, "nat-timeout", 10, "NAT discovery timeout in seconds")
|
||||
flag.BoolVar(&pprofEnabled, "pprof", false, "Enable the built in profiling on the status server")
|
||||
flag.IntVar(&networkBufferSize, "network-buffer", 2048, "Network buffer size (two of these per proxied connection)")
|
||||
flag.Parse()
|
||||
|
||||
if extAddress == "" {
|
||||
|
||||
@@ -254,7 +254,7 @@ func (s *session) proxy(c1, c2 net.Conn) error {
|
||||
atomic.AddInt64(&numProxies, 1)
|
||||
defer atomic.AddInt64(&numProxies, -1)
|
||||
|
||||
buf := make([]byte, 65536)
|
||||
buf := make([]byte, networkBufferSize)
|
||||
for {
|
||||
c1.SetReadDeadline(time.Now().Add(networkTimeout))
|
||||
n, err := c1.Read(buf)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -18,6 +19,9 @@ func statusService(addr string) {
|
||||
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc("/status", getStatus)
|
||||
if pprofEnabled {
|
||||
handler.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
}
|
||||
|
||||
srv := http.Server{
|
||||
Addr: addr,
|
||||
|
||||
@@ -638,12 +638,10 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
l.Infoln(LongVersion)
|
||||
l.Infoln("My ID:", myID)
|
||||
|
||||
// Select SHA256 implementation and report. Affected by the
|
||||
// STHASHING environment variable.
|
||||
sha256.SelectAlgo()
|
||||
sha256.Report()
|
||||
perfWithWeakHash := cpuBench(3, 150*time.Millisecond, true)
|
||||
l.Infof("Hashing performance with weak hash is %.02f MB/s", perfWithWeakHash)
|
||||
perfWithoutWeakHash := cpuBench(3, 150*time.Millisecond, false)
|
||||
l.Infof("Hashing performance without weak hash is %.02f MB/s", perfWithoutWeakHash)
|
||||
|
||||
// Emit the Starting event, now that we know who we are.
|
||||
|
||||
@@ -696,6 +694,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
opts := cfg.Options()
|
||||
|
||||
if opts.WeakHashSelectionMethod == config.WeakHashAuto {
|
||||
perfWithWeakHash := cpuBench(3, 150*time.Millisecond, true)
|
||||
l.Infof("Hashing performance with weak hash is %.02f MB/s", perfWithWeakHash)
|
||||
perfWithoutWeakHash := cpuBench(3, 150*time.Millisecond, false)
|
||||
l.Infof("Hashing performance without weak hash is %.02f MB/s", perfWithoutWeakHash)
|
||||
|
||||
if perfWithoutWeakHash*0.8 > perfWithWeakHash {
|
||||
l.Infof("Weak hash disabled, as it has an unacceptable performance impact.")
|
||||
weakhash.Enabled = false
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (C) 2017 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAutoClosedFile(t *testing.T) {
|
||||
os.RemoveAll("_autoclose")
|
||||
defer os.RemoveAll("_autoclose")
|
||||
os.Mkdir("_autoclose", 0755)
|
||||
file := filepath.FromSlash("_autoclose/tmp")
|
||||
data := []byte("hello, world\n")
|
||||
|
||||
// An autoclosed file that closes very quickly
|
||||
ac := newAutoclosedFile(file, time.Millisecond, time.Millisecond)
|
||||
|
||||
// Write some data.
|
||||
if _, err := ac.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for it to close
|
||||
start := time.Now()
|
||||
for {
|
||||
time.Sleep(time.Millisecond)
|
||||
ac.mut.Lock()
|
||||
fd := ac.fd
|
||||
ac.mut.Unlock()
|
||||
if fd == nil {
|
||||
break
|
||||
}
|
||||
if time.Since(start) > time.Second {
|
||||
t.Fatal("File should have been closed after first write")
|
||||
}
|
||||
}
|
||||
|
||||
// Write more data, which should be an append.
|
||||
if _, err := ac.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Close.
|
||||
if err := ac.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The file should have both writes in it.
|
||||
bs, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(bs) != 2*len(data) {
|
||||
t.Fatalf("Writes failed, expected %d bytes, not %d", 2*len(data), len(bs))
|
||||
}
|
||||
|
||||
// Open the file again.
|
||||
ac = newAutoclosedFile(file, time.Second, time.Second)
|
||||
|
||||
// Write something
|
||||
if _, err := ac.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// It should now contain only one write, because the first open
|
||||
// should be a truncate.
|
||||
bs, err = ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(bs) != len(data) {
|
||||
t.Fatalf("Write failed, expected %d bytes, not %d", len(data), len(bs))
|
||||
}
|
||||
|
||||
// Close.
|
||||
if err := ac.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Запази",
|
||||
"Scan Time Remaining": "Оставащо време за сканиране",
|
||||
"Scanning": "Сканиране",
|
||||
"See external versioner help for supported templated command line parameters.": "Прегледайте документацията на външното приложение за версии и поддържаните от него командни параметри. ",
|
||||
"Select the devices to share this folder with.": "Изберете устройствата, с които да споделите папката.",
|
||||
"Select the folders to share with this device.": "Изберете папките за споделяне с това устройство.",
|
||||
"Send & Receive": "Изпращане & получаване",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Gravar",
|
||||
"Scan Time Remaining": "Temps d'escaneig restant",
|
||||
"Scanning": "Rastrejant",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Selecciona els dispositius amb els que compartir aquesta carpeta.",
|
||||
"Select the folders to share with this device.": "Selecciona les carpetes per a compartir amb aquest dispositiu.",
|
||||
"Send & Receive": "Enviar i Rebre",
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"Enable NAT traversal": "Povolit NAT přenos",
|
||||
"Enable Relaying": "Povolit přenašeče",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Zadajte kladné číslo (např. \"2.35\") a zvolte jednotku. Percenta znamenají část celkové velikosti disku.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Zadejte číslo neprivilegovaného portu (1024-65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Zadejte adresy oddělené čárkou (\"tcp://ip:port\", \"tcp://host:port\") nebo \"dynamic\" pro automatické zjišťování adres.",
|
||||
"Enter ignore patterns, one per line.": "Vložit ignorované vzory, jeden na řádek.",
|
||||
"Error": "Chyba",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Uložit",
|
||||
"Scan Time Remaining": "Zbývající čas skenování",
|
||||
"Scanning": "Skenování",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Vybrat přístroje, se kterými sdílet tento adresář.",
|
||||
"Select the folders to share with this device.": "Vybrat adresáře sdílené s tímto přístrojem.",
|
||||
"Send & Receive": "Odeslat a přijmout",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového přístroje mějte na paměti, že je ho třeba také zadat na druhé straně.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
|
||||
"Yes": "Ano",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "Také můžete vybrat jedno z těchto okolních zařízení:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Vaši volbu můžete kdykoliv změnit v dialogu nastavení.",
|
||||
"You can read more about the two release channels at the link below.": "O kandidátech na vydání si můžete přečíst více v odkazu níže.",
|
||||
"You must keep at least one version.": "Je třeba ponechat alespoň jednu verzi.",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Gem",
|
||||
"Scan Time Remaining": "Tid tilbage af skanningen",
|
||||
"Scanning": "Opdaterer",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Vælg hvilke enheder du vil dele denne mappe med",
|
||||
"Select the folders to share with this device.": "Vælg hvilke mapper du vil dele med denne enhed.",
|
||||
"Send & Receive": "Send & Modtag",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"Add Device": "Gerät hinzufügen",
|
||||
"Add Folder": "Ordner hinzufügen",
|
||||
"Add Remote Device": "Gerät hinzufügen",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Fügt Geräte vom Verteilergerät zu der eigenen Geräteliste hinzu, um gegenseitig geteilte Ordner zu ermöglichen.",
|
||||
"Add new folder?": "Neuen Ordner hinzufügen?",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adressen",
|
||||
@@ -21,10 +21,10 @@
|
||||
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
|
||||
"Allowed Networks": "Erlaubte Netzwerke",
|
||||
"Alphabetic": "Alphabetisch",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "Ein externer Befehl führt die Versionierung durch. Dazu muss die Datei aus dem geteilten Ordner entfernt werden.",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "Ein externer Befehl führt die Versionierung durch. Er muss die Datei aus dem geteilten Ordner entfernen.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem Ordner entfernen.",
|
||||
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteiler eingetragen sind, werden auch bei diesem Gerät eingetragen",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteilergerät eingetragen sind, werden auch bei diesem Gerät hinzugefügt.",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Die automatische Aktualisierung bietet jetzt die Wahl zwischen stabilen Veröffentlichungen und Veröffentlichungskandidaten.",
|
||||
"Automatic upgrades": "Automatische Updates aktivieren",
|
||||
"Be careful!": "Vorsicht!",
|
||||
@@ -32,7 +32,7 @@
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Changelog": "Änderungsprotokoll",
|
||||
"Clean out after": "Löschen nach",
|
||||
"Click to see discovery failures": "Zum Anzeigen von Gerätesuchfehlern klicken",
|
||||
"Click to see discovery failures": "Klick um Gerätesuchfehler anzuzeigen",
|
||||
"Close": "Schließen",
|
||||
"Command": "Befehl",
|
||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
|
||||
@@ -44,7 +44,7 @@
|
||||
"Copied from original": "Vom Original kopiert",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 der folgenden Unterstützer:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 der folgenden Unterstützer:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Erstelle Ignoriermuster, welche die existierende Datei {{path}} überschreiben.",
|
||||
"Danger!": "Achtung!",
|
||||
"Deleted": "Gelöscht",
|
||||
"Device": "Gerät",
|
||||
@@ -65,24 +65,24 @@
|
||||
"Edit Device": "Gerät bearbeiten",
|
||||
"Edit Folder": "Ordner bearbeiten",
|
||||
"Editing": "Bearbeitet",
|
||||
"Editing {%path%}.": "{{path}} wird bearbeitet.",
|
||||
"Editing {%path%}.": "Bearbeite {{path}}.",
|
||||
"Enable NAT traversal": "NAT-Durchdringung aktivieren",
|
||||
"Enable Relaying": "Weiterleitung aktivieren",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Geben Sie eine positive Zahl ein (z.B. \"2.35\") und wählen Sie eine Einheit. Prozentsätze sind Teil der gesamten Festplattengröße.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Geben Sie eine nichtprivilegierte Portnummer ein (1024 - 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Kommagetrennte Adressen (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\" eingeben, um die Adresse automatisch zu ermitteln.",
|
||||
"Enter ignore patterns, one per line.": "Geben Sie Ignoriermuster ein, eines pro Zeile.",
|
||||
"Error": "Fehler",
|
||||
"External File Versioning": "Externe Dateiversionierung",
|
||||
"Failed Items": "Fehlgeschlagene Objekte",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Es wird ein Verbindungsfehler zu IPv6-Servern erwartet, wenn es keine IPv6-Konnektivität gibt.",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Ein Verbindungsfehler zu IPv6-Servern ist zu erwarten, wenn es keine IPv6-Konnektivität gibt.",
|
||||
"File Pull Order": "Dateiübertragungsreihenfolge",
|
||||
"File Versioning": "Dateiversionierung",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Dateizugriffsrechte beim Suchen nach Veränderungen ignorieren. Bei FAT-Dateisystemen zu verwenden.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dateien werden in das .stversions-Verzeichnis verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Wenn Syncthing Dateien ersetzt oder löscht, werden sie in den Ordner .stversions verschoben.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dateien werden mit einem Datumsstempel im Namen versehen und in ein .stversions-Verzeichnis verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden, bevor Syncthing sie löscht oder ersetzt, datiert in den Ordner .stversions verschoben.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dateien werden in den .stversions Ordner verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Dateien werden in den .stversions Ordner verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dateien werden mit Datumsstempel versioniert und in den .stversions Ordner verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden mit Datumsstempel versioniert und in den .stversions Ordner verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind auf diesem Gerät schreibgeschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
|
||||
"Folder": "Ordner",
|
||||
"Folder ID": "Ordnerkennung",
|
||||
@@ -93,9 +93,9 @@
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
|
||||
"GUI Authentication User": "Nutzername für Zugang zur Benutzeroberfläche",
|
||||
"GUI Listen Address": "GUI Listen Address",
|
||||
"GUI Listen Addresses": "Adresse(n) für die Benutzeroberfläche",
|
||||
"GUI Theme": "GUI-Theme",
|
||||
"GUI Listen Address": "Addresse der Benutzeroberfläche",
|
||||
"GUI Listen Addresses": "Adressen der Benutzeroberfläche",
|
||||
"GUI Theme": "GUI Design",
|
||||
"Generate": "Generieren",
|
||||
"Global Changes": "Globale Änderungen",
|
||||
"Global Discovery": "Globale Gerätesuche",
|
||||
@@ -123,7 +123,7 @@
|
||||
"Local Discovery": "Lokale Gerätesuche",
|
||||
"Local State": "Lokaler Status",
|
||||
"Local State (Total)": "Lokaler Status (Gesamt)",
|
||||
"Major Upgrade": "Hauptversionsupgrade",
|
||||
"Major Upgrade": "Hauptversionsupdate",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Höchstalter",
|
||||
"Metadata Only": "Nur Metadaten",
|
||||
@@ -136,7 +136,7 @@
|
||||
"Newest First": "Neueste zuerst",
|
||||
"No": "Nein",
|
||||
"No File Versioning": "Keine Dateiversionierung",
|
||||
"No upgrades": "Keine Upgrades",
|
||||
"No upgrades": "Keine Updates",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Hinweis",
|
||||
"OK": "OK",
|
||||
@@ -150,16 +150,16 @@
|
||||
"Override Changes": "Änderungen überschreiben",
|
||||
"Path": "Pfad",
|
||||
"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": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilden-Zeichen (~) kann als Abkürzung benutzt werden für",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pfad, in dem Versionen gespeichert werden sollen (leer lassen, wenn das Standard-.stversions-Verzeichnis im geteilten Ordner verwendet werden soll).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem alte Dateiversionen gespeichert werden sollen (ohne Angabe wird der Ordner .stversions im Ordner verwendet).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pfad in dem Versionen gespeichert werden sollen (leer lassen, wenn der Standard .stversions Ordner für den geteilten Ordner verwendet werden soll).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Pfad in dem Versionen gespeichert werden sollen (leer lassen, wenn der Standard .stversions Ordner für den geteilten Ordner verwendet werden soll).",
|
||||
"Pause": "Pause",
|
||||
"Pause All": "Alles pausieren",
|
||||
"Paused": "Pausiert",
|
||||
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie eine neue Hauptversion installieren.",
|
||||
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungsnotizen bevor Sie ein neues Hauptversionsupdate installieren.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bitte setze einen Benutzer und ein Passwort für das GUI in den Einstellungen.",
|
||||
"Please wait": "Bitte warten",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Präfix, das anzeigt, dass die Datei gelöscht werden kann, wenn sie die Entfernung des Ordners verhindert",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Präfix, das anzeigt, dass das Muster ohne Beachtung der Groß-/Kleinschreibung übereinstimmen soll",
|
||||
"Preview": "Vorschau",
|
||||
"Preview Usage Report": "Vorschau des Nutzungsberichts",
|
||||
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
|
||||
@@ -167,7 +167,7 @@
|
||||
"Random": "Zufall",
|
||||
"Reduced by ignore patterns": "Durch Ignoriermuster reduziert",
|
||||
"Release Notes": "Veröffentlichungsnotizen",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Veröffentlichungskandidaten enthalten die neuesten Funktionen und Verbesserungen. Sie ähneln den traditionellen zweiwöchentlichen Syncthing-Veröffentlichungen.",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Veröffentlichungskandidaten enthalten die neuesten Funktionen und Verbesserungen. Sie gleichen den üblichen zweiwöchentlichen Syncthing-Veröffentlichungen.",
|
||||
"Remote Devices": "Fern-Geräte",
|
||||
"Remove": "Entfernen",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Erforderlicher Bezeichner für den Ordner. Muss auf allen Verbund-Geräten gleich sein.",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Speichern",
|
||||
"Scan Time Remaining": "Zeit für Scan verbleibend",
|
||||
"Scanning": "Scannen",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Wähle die Geräte aus, mit denen Du diesen Ordner teilen willst.",
|
||||
"Select the folders to share with this device.": "Wähle die Ordner aus, die Du mit diesem Gerät teilen möchtest",
|
||||
"Send & Receive": "Senden & empfangen",
|
||||
@@ -205,8 +206,8 @@
|
||||
"Smallest First": "Kleinstes zuerst",
|
||||
"Source Code": "Quellcode",
|
||||
"Stable releases and release candidates": "Stabile Veröffentlichungen und Veröffentlichungskandidaten",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabile Veröffentlichungen werden ca. 2 Wochen zurückgehalten. Während dieser Zeit durchlaufen sie eine Testphase als Veröffentlichungskandidaten.",
|
||||
"Stable releases only": "Ausschließlich stabile Veröffentlichungen",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabile Veröffentlichungen werden ca. 2 Wochen verzögert. Während dieser Zeit durchlaufen sie eine Testphase als Veröffentlichungskandidaten.",
|
||||
"Stable releases only": "Nur stabile Veröffentlichungen",
|
||||
"Staggered File Versioning": "Stufenweise Dateiversionierung",
|
||||
"Start Browser": "Browser starten",
|
||||
"Statistics": "Statistiken",
|
||||
@@ -246,8 +247,8 @@
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Sie werden automatisch heruntergeladen und werden synchronisiert, wenn der Fehler behoben wurde.",
|
||||
"This Device": "Dieses Gerät",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Dies kann dazu führen, dass Unberechtigte relativ einfach auf Ihre Dateien zugreifen und diese ändern können.",
|
||||
"This is a major version upgrade.": "Dies ist eine neue Hauptversion.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"This is a major version upgrade.": "Dies ist ein neues Hauptversionsupdate.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Diese Einstellung regelt den freien Speicherplatz, der für den Systemordner (d.h. Indexdatenbank) erforderlich ist.",
|
||||
"Time": "Zeit",
|
||||
"Trash Can File Versioning": "Papierkorb Dateiversionierung",
|
||||
"Type": "Typ",
|
||||
@@ -256,12 +257,12 @@
|
||||
"Unused": "Ungenutzt",
|
||||
"Up to Date": "Aktuell",
|
||||
"Updated": "Aktualisiert",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade": "Update",
|
||||
"Upgrade To {%version%}": "Update auf {{version}}",
|
||||
"Upgrading": "Wird aktualisiert",
|
||||
"Upload Rate": "Upload",
|
||||
"Uptime": "Betriebszeit",
|
||||
"Usage reporting is always enabled for candidate releases.": "Nutzungsauswertung ist für Veröffentlichungskandidaten immer aktiviert.",
|
||||
"Usage reporting is always enabled for candidate releases.": "Nutzungsbericht ist für Veröffentlichungskandidaten immer aktiviert.",
|
||||
"Use HTTPS for GUI": "HTTPS für Benutzeroberfläche benutzen",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Versionierungspfad",
|
||||
@@ -273,9 +274,9 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Beachte beim Hinzufügen eines neuen Gerätes, dass dieses Gerät auch auf den anderen Geräten hinzugefügt werden muss.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Beachte bitte beim Hinzufügen eines neuen Ordners, dass die Ordnerkennung dazu verwendet wird, Ordner zwischen Geräten zu verbinden. Die Kennung muss also auf allen Geräten gleich sein, die Groß- und Kleinschreibung muss dabei beachtet werden.",
|
||||
"Yes": "Ja",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "Sie können auch ein in der Nähe befindliches Geräte auswählen:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Sie können Ihre Wahl jederzeit in den Einstellungen ändern.",
|
||||
"You can read more about the two release channels at the link below.": "Über den untenstehenden Link können Sie mehr über die zwei Veröffentlichungskanäle erfahren.",
|
||||
"You can read more about the two release channels at the link below.": "Über den folgenden Link können Sie mehr über die zwei Veröffentlichungskanäle erfahren.",
|
||||
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
|
||||
"days": "Tage",
|
||||
"directories": "Ordner",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Παρακαλώ όρισε στις ρυθμίσεις έναν χρήστη και έναν κωδικό πρόσβασης για τη διεπαφή.",
|
||||
"Please wait": "Παρακαλώ περιμένετε",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Πρόθεμα που δείχνει ότι το αρχείο θα μπορεί να διαγραφεί αν εμποδίζει τη διαγραφή καταλόγου",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Πρόθεμα που δείχνει ότι αντιστοίχιση του προτύπου θα γίνεται χωρίς διάκριση πεζών και κεφαλαίων χαρακτήρων",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Πρόθεμα που δείχνει ότι η αντιστοίχιση προτύπου θα γίνεται χωρίς διάκριση πεζών και κεφαλαίων χαρακτήρων",
|
||||
"Preview": "Προεπισκόπηση",
|
||||
"Preview Usage Report": "Προεπισκόπηση αναφοράς χρήσης",
|
||||
"Quick guide to supported patterns": "Σύντομη βοήθεια σχετικά με τα πρότυπα αναζήτησης που υποστηρίζονται",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Αποθήκευση",
|
||||
"Scan Time Remaining": "Εναπομείναντας χρόνος για τον έλεγχο ",
|
||||
"Scanning": "Έλεγχος για αλλαγές",
|
||||
"See external versioner help for supported templated command line parameters.": "Ανατρέξτε στην τεκμηρίωση της εξωτερικής τήρησης εκδόσεων για πληροφορίες σχετικά με τις υποστηριζόμενες παραμέτρους της γραμμής εντολών.",
|
||||
"Select the devices to share this folder with.": "Διάλεξε τις συσκευές προς τις οποίες θα διαμοιράζεται αυτός ο φάκελος.",
|
||||
"Select the folders to share with this device.": "Διάλεξε ποιοι φάκελοι θα διαμοιράζονται προς αυτή τη συσκευή.",
|
||||
"Send & Receive": "Αποστολή και λήψη",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Θυμήσου πως όταν προσθέτεις μια νέα συσκευή, ετούτη η συσκευή θα πρέπει να προστεθεί και στην άλλη πλευρά.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
|
||||
"Yes": "Ναι",
|
||||
"You can also select one of these nearby devices:": "Μπορείτε επίσης να επιλέξετε μια από αυτές τις κοντινές συσκευές:",
|
||||
"You can also select one of these nearby devices:": "Μπορείτε επίσης να επιλέξετε μια από αυτές τις γειτονικές συσκευές:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Μπορείτε να αλλάξετε τη ρύθμιση αυτή ανά πάσα στιγμή στο παράθυρο «Ρυθμίσεις».",
|
||||
"You can read more about the two release channels at the link below.": "Μπορείτε να διαβάσετε περισσότερα για τα δύο κανάλια εκδόσεων στον παρακάτω σύνδεσμο.",
|
||||
"You must keep at least one version.": "Πρέπει να τηρήσεις τουλάχιστον μια έκδοση.",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Save",
|
||||
"Scan Time Remaining": "Scan Time Remaining",
|
||||
"Scanning": "Scanning",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
|
||||
"Select the folders to share with this device.": "Select the folders to share with this device.",
|
||||
"Send & Receive": "Send & Receive",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Save",
|
||||
"Scan Time Remaining": "Scan Time Remaining",
|
||||
"Scanning": "Scanning",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
|
||||
"Select the folders to share with this device.": "Select the folders to share with this device.",
|
||||
"Send \u0026 Receive": "Send \u0026 Receive",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Konservu",
|
||||
"Scan Time Remaining": "Skanada Restanta Tempo",
|
||||
"Scanning": "Skanado",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Elekti la aparatojn por komunigi ĉi tiun dosierujon.",
|
||||
"Select the folders to share with this device.": "Elekti la dosierujojn por komunigi kun ĉi tiu aparato.",
|
||||
"Send & Receive": "Sendi kaj Ricevi",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Guardar",
|
||||
"Scan Time Remaining": "Tiempo Restante de Escaneo",
|
||||
"Scanning": "Analizando",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Selecciona los dispositivos con los que compartir esta carpeta.",
|
||||
"Select the folders to share with this device.": "Selecciona las carpetas para compartir con este dispositivo.",
|
||||
"Send & Receive": "Enviar y Recibir",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Guardar",
|
||||
"Scan Time Remaining": "Tiempo Restante de Escaneo",
|
||||
"Scanning": "Analizando",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Selecciona los dispositivos con los que compartir esta carpeta.",
|
||||
"Select the folders to share with this device.": "Selecciona las carpetas para compartir con este dispositivo.",
|
||||
"Send & Receive": "Enviar y Recibir",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cuando añada una nueva carpeta, tenga en cuenta que su ID se usa para unir carpetas entre dispositivos. Son sensibles a las mayúsculas y deben coincidir exactamente entre todos los dispositivos.",
|
||||
"Yes": "Si",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "También puede seleccionar uno de estos dispositivos cercanos:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Puedes cambiar tu elección en cualquier momento en el panel de Ajustes.",
|
||||
"You can read more about the two release channels at the link below.": "Puedes leer más sobre los dos método de publicación de versiones en el siguiente enlace.",
|
||||
"You must keep at least one version.": "Debes mantener al menos una versión.",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Grabatu",
|
||||
"Scan Time Remaining": "Gelditzen den azterketa denbora",
|
||||
"Scanning": "Azterketa martxan",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": " Tresnak hauta itzazu partekatze honekin sinkronizatzeko ",
|
||||
"Select the folders to share with this device.": "Tresna honek erabiltzen dituen partekatzeak hauta itzazu",
|
||||
"Send & Receive": "Igorri eta errezibitu",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Tallenna",
|
||||
"Scan Time Remaining": "Skannausaikaa jäljellä",
|
||||
"Scanning": "Skannataan",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Valitse laitteet, joiden kanssa tämä kansio jaetaan.",
|
||||
"Select the folders to share with this device.": "Valitse kansiot jaettavaksi tämän laitteen kanssa.",
|
||||
"Send & Receive": "Lähetä & vastaanota",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Enregistrer",
|
||||
"Scan Time Remaining": "Temps d'analyse restant",
|
||||
"Scanning": "Analyse en cours",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Synchroniser avec :",
|
||||
"Select the folders to share with this device.": "Sélectionner les partages auxquels participe cet appareil.",
|
||||
"Send & Receive": "Envoi & réception",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"A device with that ID is already added.": "L'appareil portant cet ID est déjà présent.",
|
||||
"A device with that ID is already added.": " L'appareil portant cet ID est déjà présent.",
|
||||
"A negative number of days doesn't make sense.": "Ce champ n'accepte qu'un entier positif ou nul.",
|
||||
"A new major version may not be compatible with previous versions.": "Une nouvelle version majeure peut présenter des incompatibilités avec les versions antérieures.",
|
||||
"API Key": "Clé API",
|
||||
@@ -48,7 +48,7 @@
|
||||
"Danger!": "Attention !",
|
||||
"Deleted": "Supprimé",
|
||||
"Device": "Appareil",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{device}}), actuellement à {{address}}, demande à se connecter.\nAcceptez-vous de l'ajouter à votre liste d'appareils connus ?",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{device}}), appareil actuellement à {{address}}, demande à se connecter.\nAcceptez-vous de l'ajouter à votre liste d'appareils connus ?",
|
||||
"Device ID": "ID de l'appareil",
|
||||
"Device Identification": "Identifiant de l'appareil",
|
||||
"Device Name": "Nom de l'appareil",
|
||||
@@ -61,9 +61,9 @@
|
||||
"Download Rate": "Débit de réception",
|
||||
"Downloaded": "Reçu",
|
||||
"Downloading": "Réception",
|
||||
"Edit": "Modifier",
|
||||
"Edit Device": "Modifier l'appareil",
|
||||
"Edit Folder": "Modifier le partage",
|
||||
"Edit": "Gérer",
|
||||
"Edit Device": "Gérer l'appareil",
|
||||
"Edit Folder": "Gérer le partage",
|
||||
"Editing": "Modifications",
|
||||
"Editing {%path%}.": "Modification de {{path}}.",
|
||||
"Enable NAT traversal": "Activer la translation d'adresses (NAT)",
|
||||
@@ -169,7 +169,7 @@
|
||||
"Release Notes": "Notes de version",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Les versions préliminaires contiennent les dernières fonctionnalités et derniers correctifs. Elles sont identiques aux traditionnelles mises à jour bimensuelles.",
|
||||
"Remote Devices": "Autres appareils",
|
||||
"Remove": "Enlever",
|
||||
"Remove": "Supprimer",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur tous les appareils concernés.",
|
||||
"Rescan": "Réanalyser",
|
||||
"Rescan All": "Tout réanalyser",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Enregistrer",
|
||||
"Scan Time Remaining": "Temps d'analyse restant",
|
||||
"Scanning": "Analyse en cours",
|
||||
"See external versioner help for supported templated command line parameters.": "Voir l'aide sur la préservation externe des fichiers pour les paramètres supportés en lignes de commande dans les modèles.",
|
||||
"Select the devices to share this folder with.": "Synchroniser avec :",
|
||||
"Select the folders to share with this device.": "Sélectionner les partages auxquels cet appareil doit participer :",
|
||||
"Send & Receive": "Envoi & réception",
|
||||
|
||||
@@ -58,9 +58,9 @@
|
||||
"Discovery": "Untdekking",
|
||||
"Discovery Failures": "Untdekkingsflaters",
|
||||
"Documentation": "Dokumintaasje",
|
||||
"Download Rate": "Ynlaadfluggens",
|
||||
"Downloaded": "Ynladen",
|
||||
"Downloading": "Oan it ynladen",
|
||||
"Download Rate": "Downloadfluggens",
|
||||
"Downloaded": "Downloaded",
|
||||
"Downloading": "Oan it downloaden",
|
||||
"Edit": "Bewurkje",
|
||||
"Edit Device": "Apparaat Bewurkje",
|
||||
"Edit Folder": "Map Bewurkje",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Bewarje",
|
||||
"Scan Time Remaining": "Oerbleaune skentiid",
|
||||
"Scanning": "Oan it skennen",
|
||||
"See external versioner help for supported templated command line parameters.": "Sjoch de eksterne help fan fersjebehearder foar stipe foarbylden fan kommando-rige-parameters.",
|
||||
"Select the devices to share this folder with.": "Sykje de apparaten út om dizze map mei te dielen.",
|
||||
"Select the folders to share with this device.": "Sykje de mappen út om mei dit apparaat te dielen.",
|
||||
"Send & Receive": "Stjoere & Untfange",
|
||||
@@ -211,7 +212,7 @@
|
||||
"Start Browser": "Browser iepenje wannear't Syncthing start",
|
||||
"Statistics": "Statistiken",
|
||||
"Stopped": "Stoppe",
|
||||
"Support": "Understeuning",
|
||||
"Support": "Help (Forum)",
|
||||
"Sync Protocol Listen Addresses": "Sync-protokolharkadressen",
|
||||
"Syncing": "Oan it Syncen",
|
||||
"Syncthing has been shut down.": "Syncthing is útsetten",
|
||||
@@ -259,7 +260,7 @@
|
||||
"Upgrade": "Fernije",
|
||||
"Upgrade To {%version%}": "Fernije nei {{version}}",
|
||||
"Upgrading": "Oan it fernijen",
|
||||
"Upload Rate": "Oplaadfluggens",
|
||||
"Upload Rate": "Uploadfluggens",
|
||||
"Uptime": "Rintiid",
|
||||
"Usage reporting is always enabled for candidate releases.": "Brûkersrapportaazje stiet altyd oan foar ferzje kandidaten.",
|
||||
"Use HTTPS for GUI": "Brûk HTTPS foar GUI",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Mentés",
|
||||
"Scan Time Remaining": "Fennmaradó átnézési idő",
|
||||
"Scanning": "Átnézés",
|
||||
"See external versioner help for supported templated command line parameters.": "A támogatott parancssori paraméter sablonokat a külső verziókezelő súgójában találod.",
|
||||
"Select the devices to share this folder with.": "Eszközök, amelyekkel megosztandó a mappa",
|
||||
"Select the folders to share with this device.": "Mappák, amelyek megosztandók ezzel az eszközzel.",
|
||||
"Send & Receive": "Küldés és fogadás",
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
{
|
||||
"A device with that ID is already added.": "Perangkat dengan ID tersebut sudah ada.",
|
||||
"A negative number of days doesn't make sense.": "Tidak mungkin jumlah hari dalam nilai negatif.",
|
||||
"A new major version may not be compatible with previous versions.": "Versi penting yang baru mungkin tidak kompatibel dengan versi sebelumnya.",
|
||||
"API Key": "API Key",
|
||||
"About": "Tentang",
|
||||
"Action": "Action",
|
||||
"Actions": "Aksi",
|
||||
"Add": "Tambah",
|
||||
"Add Device": "Tambah Perangkat",
|
||||
"Add Folder": "Tambah Folder",
|
||||
"Add Remote Device": "Add Remote Device",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
|
||||
"Add new folder?": "Tambah folder baru",
|
||||
"Address": "Alamat",
|
||||
"Addresses": "Alamat",
|
||||
"Advanced": "Tingkat Lanjut",
|
||||
"Advanced Configuration": "Konfigurasi Tingkat Lanjut",
|
||||
"Advanced settings": "Advanced settings",
|
||||
"All Data": "Semua Data",
|
||||
"Allow Anonymous Usage Reporting?": "Aktifkan Laporan Penggunaan Anonim?",
|
||||
"Allowed Networks": "Allowed Networks",
|
||||
"Alphabetic": "Alfabet",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "An external command handles the versioning. It has to remove the file from the shared folder.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Perintah eksternal mengatur pemversian. Dia harus menghapus berkas dari folder yang tersinkronisasi.",
|
||||
"Anonymous Usage Reporting": "Pelaporan Penggunaan Anonim",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Semua perangkat yang dikonfigurasi di perangkat pengenal akan ditambahkan di perangkat ini juga.",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.",
|
||||
"Automatic upgrades": "Ugrade Otomatis",
|
||||
"Be careful!": "Harap hati-hati!",
|
||||
"Bugs": "Bugs",
|
||||
"CPU Utilization": "Penggunaan CPU",
|
||||
"Changelog": "Log perubahan",
|
||||
"Clean out after": "Bersihkan setelah",
|
||||
"Click to see discovery failures": "Click to see discovery failures",
|
||||
"Close": "Tutup",
|
||||
"Command": "Perintah",
|
||||
"Comment, when used at the start of a line": "Komentar, digunakan saat awal baris",
|
||||
"Compression": "Kompresi",
|
||||
"Configured": "Configured",
|
||||
"Connection Error": "Koneksi Galat",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Tersalin dari tempat lain",
|
||||
"Copied from original": "Tersalin dari asal",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
|
||||
"Danger!": "Bahaya!",
|
||||
"Deleted": "Terhapus",
|
||||
"Device": "Device",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||
"Device ID": "ID Perangkat",
|
||||
"Device Identification": "Identifikasi Perangkat",
|
||||
"Device Name": "Nama Perangkat",
|
||||
"Devices": "Perangkat",
|
||||
"Disconnected": "Terputus",
|
||||
"Discovered": "Discovered",
|
||||
"Discovery": "Discovery",
|
||||
"Discovery Failures": "Discovery Failures",
|
||||
"Documentation": "Dokumentasi",
|
||||
"Download Rate": "Download Rate",
|
||||
"Downloaded": "Terunduh",
|
||||
"Downloading": "Mengunduh",
|
||||
"Edit": "Sunting",
|
||||
"Edit Device": "Edit Device",
|
||||
"Edit Folder": "Edit Folder",
|
||||
"Editing": "Menyunting",
|
||||
"Editing {%path%}.": "Editing {{path}}.",
|
||||
"Enable NAT traversal": "Enable NAT traversal",
|
||||
"Enable Relaying": "Aktifkan Relay",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Masukkan alamat, pisahkan dengan koma (\"tcp://ip:port\", \"tcp://host:port\") atau \"dynamic\" untuk menjalankan penemuan otomatis alamat tersebut.",
|
||||
"Enter ignore patterns, one per line.": "Masukkan pola pengabaian, satu per baris.",
|
||||
"Error": "Galat",
|
||||
"External File Versioning": "Berkas pemversian eksternal",
|
||||
"Failed Items": "Materi yang gagal",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
|
||||
"File Pull Order": "Urutan Penarikan Berkas",
|
||||
"File Versioning": "Versi Berkas",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Bit hak akses berkas diabaikan saat mencari perubahan. Digunakan pada sistem berkas FAT.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Files are moved to .stversions directory when replaced or deleted by Syncthing.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Berkas dipindahkan ke folder .stversions jika digantikan atau dihapus oleh Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Berkas dipindahkan ke versi bertanggal dalam folder .stversions jika digantikan atau dihapus oleh Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Berkas diproteksi dari perubahan oleh perangkat lain, tetapi perubahan yang dikirim dari perangkat ini akan dikirim ke perangkat lain dalam klaster.",
|
||||
"Folder": "Folder",
|
||||
"Folder ID": "ID Folder",
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Path": "Path Folder",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Folder",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Sandi Otentikasi GUI",
|
||||
"GUI Authentication User": "Pengguna Otentikasi GUI",
|
||||
"GUI Listen Address": "GUI Listen Address",
|
||||
"GUI Listen Addresses": "Alamat Listen GUI",
|
||||
"GUI Theme": "GUI Theme",
|
||||
"Generate": "Buat Baru",
|
||||
"Global Changes": "Global Changes",
|
||||
"Global Discovery": "Discovery Global",
|
||||
"Global Discovery Servers": "Global Discovery Servers",
|
||||
"Global State": "Status Global",
|
||||
"Help": "Panduan",
|
||||
"Home page": "Situs",
|
||||
"Ignore": "Abaikan",
|
||||
"Ignore Patterns": "Pola Pengabaian",
|
||||
"Ignore Permissions": "Hak Akses Pengabaian",
|
||||
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Konfigurasi salah bisa merusak isi folder dan membuat Syncthing tidak bisa dijalankan.",
|
||||
"Introduced By": "Introduced By",
|
||||
"Introducer": "Pengenal",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
|
||||
"Keep Versions": "Keep Versions",
|
||||
"Largest First": "Largest First",
|
||||
"Last File Received": "Last File Received",
|
||||
"Last Scan": "Last Scan",
|
||||
"Last seen": "Last seen",
|
||||
"Later": "Later",
|
||||
"Latest Change": "Latest Change",
|
||||
"Learn more": "Learn more",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
"Move to top of queue": "Move to top of queue",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
|
||||
"Never": "Never",
|
||||
"New Device": "New Device",
|
||||
"New Folder": "New Folder",
|
||||
"Newest First": "Newest First",
|
||||
"No": "No",
|
||||
"No File Versioning": "No File Versioning",
|
||||
"No upgrades": "No upgrades",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Notice",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
"Oldest First": "Oldest First",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||
"Options": "Options",
|
||||
"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": "Path",
|
||||
"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",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
|
||||
"Pause": "Pause",
|
||||
"Pause All": "Pause All",
|
||||
"Paused": "Paused",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Please set a GUI Authentication User and Password in the Settings dialog.",
|
||||
"Please wait": "Please wait",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
|
||||
"Preview": "Preview",
|
||||
"Preview Usage Report": "Preview Usage Report",
|
||||
"Quick guide to supported patterns": "Quick guide to supported patterns",
|
||||
"RAM Utilization": "RAM Utilization",
|
||||
"Random": "Random",
|
||||
"Reduced by ignore patterns": "Reduced by ignore patterns",
|
||||
"Release Notes": "Release Notes",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.",
|
||||
"Remote Devices": "Remote Devices",
|
||||
"Remove": "Remove",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.",
|
||||
"Rescan": "Rescan",
|
||||
"Rescan All": "Rescan All",
|
||||
"Rescan Interval": "Rescan Interval",
|
||||
"Restart": "Restart",
|
||||
"Restart Needed": "Restart Needed",
|
||||
"Restarting": "Restarting",
|
||||
"Resume": "Resume",
|
||||
"Resume All": "Resume All",
|
||||
"Reused": "Reused",
|
||||
"Save": "Save",
|
||||
"Scan Time Remaining": "Scan Time Remaining",
|
||||
"Scanning": "Scanning",
|
||||
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
|
||||
"Select the folders to share with this device.": "Select the folders to share with this device.",
|
||||
"Send & Receive": "Send & Receive",
|
||||
"Send Only": "Send Only",
|
||||
"Settings": "Settings",
|
||||
"Share": "Share",
|
||||
"Share Folder": "Share Folder",
|
||||
"Share Folders With Device": "Share Folders With Device",
|
||||
"Share With Devices": "Share With Devices",
|
||||
"Share this folder?": "Share this folder?",
|
||||
"Shared With": "Shared With",
|
||||
"Show ID": "Show ID",
|
||||
"Show QR": "Show QR",
|
||||
"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)",
|
||||
"Smallest First": "Smallest First",
|
||||
"Source Code": "Source Code",
|
||||
"Stable releases and release candidates": "Stable releases and release candidates",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.",
|
||||
"Stable releases only": "Stable releases only",
|
||||
"Staggered File Versioning": "Staggered File Versioning",
|
||||
"Start Browser": "Start Browser",
|
||||
"Statistics": "Statistics",
|
||||
"Stopped": "Stopped",
|
||||
"Support": "Support",
|
||||
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
|
||||
"Syncing": "Syncing",
|
||||
"Syncthing has been shut down.": "Syncthing has been shut down.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
|
||||
"Syncthing is restarting.": "Syncthing is restarting.",
|
||||
"Syncthing is upgrading.": "Syncthing is upgrading.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "The Syncthing admin interface is configured to allow remote access without a password.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "The aggregated statistics are publicly available at the URL below.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
|
||||
"The device ID cannot be blank.": "The device ID cannot be blank.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "The first command line parameter is the folder path and the second parameter is the relative path in the folder.",
|
||||
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
|
||||
"The folder ID must be unique.": "The folder ID must be unique.",
|
||||
"The folder path cannot be blank.": "The folder path cannot be blank.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.",
|
||||
"The following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"The maximum age must be a number and cannot be blank.": "The maximum age must be a number and cannot be blank.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The number of old versions to keep, per file.": "The number of old versions to keep, per file.",
|
||||
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
|
||||
"The path cannot be blank.": "The path cannot be blank.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This Device": "This Device",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"Time": "Time",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Type": "Type",
|
||||
"Unknown": "Unknown",
|
||||
"Unshared": "Unshared",
|
||||
"Unused": "Unused",
|
||||
"Up to Date": "Up to Date",
|
||||
"Updated": "Updated",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
||||
"Upgrading": "Upgrading",
|
||||
"Upload Rate": "Upload Rate",
|
||||
"Uptime": "Uptime",
|
||||
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
|
||||
"Use HTTPS for GUI": "Use HTTPS for GUI",
|
||||
"Version": "Version",
|
||||
"Versions Path": "Versions Path",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a parent directory of an existing folder \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Warning, this path is a parent directory of an existing folder \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a subdirectory of an existing folder \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Warning, this path is a subdirectory of an existing folder \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
|
||||
"Yes": "Yes",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can change your choice at any time in the Settings dialog.": "You can change your choice at any time in the Settings dialog.",
|
||||
"You can read more about the two release channels at the link below.": "You can read more about the two release channels at the link below.",
|
||||
"You must keep at least one version.": "You must keep at least one version.",
|
||||
"days": "days",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"full documentation": "full documentation",
|
||||
"items": "items",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
|
||||
}
|
||||
@@ -97,7 +97,7 @@
|
||||
"GUI Listen Addresses": "Indirizzi dell'Interfaccia Grafica",
|
||||
"GUI Theme": "Tema GUI",
|
||||
"Generate": "Genera",
|
||||
"Global Changes": "Modificazioni Globali",
|
||||
"Global Changes": "Modifiche Globali",
|
||||
"Global Discovery": "Individuazione Globale",
|
||||
"Global Discovery Servers": "Server di Individuazione Globale",
|
||||
"Global State": "Stato Globale",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Salva",
|
||||
"Scan Time Remaining": "Tempo di Scansione Rimanente",
|
||||
"Scanning": "Scansione in corso",
|
||||
"See external versioner help for supported templated command line parameters.": "Consultare la guida al controllo di versione per i modelli dei parametri di riga di comando supportati.",
|
||||
"Select the devices to share this folder with.": "Seleziona i dispositivi con i quali condividere questa cartella.",
|
||||
"Select the folders to share with this device.": "Seleziona le cartelle da condividere con questo dispositivo.",
|
||||
"Send & Receive": "Invia & Ricevi",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "保存",
|
||||
"Scan Time Remaining": "スキャン残り時間",
|
||||
"Scanning": "スキャン中",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "このフォルダーを共有するデバイスを選択してください。",
|
||||
"Select the folders to share with this device.": "このデバイスと共有するフォルダーを選択してください。",
|
||||
"Send & Receive": "送受信",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "저장",
|
||||
"Scan Time Remaining": "탐색 남은 시간",
|
||||
"Scanning": "탐색중",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "이 폴더를 공유할 장치를 선택합니다.",
|
||||
"Select the folders to share with this device.": "이 장치와 공유할 폴더를 선택합니다.",
|
||||
"Send & Receive": "송신 & 수신",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Išsaugoti",
|
||||
"Scan Time Remaining": "Likęs nuskaitymo laikas",
|
||||
"Scanning": "Skenuojama",
|
||||
"See external versioner help for supported templated command line parameters.": "Palaikomiems šabloniniams komandų eilutės parametrams, žiūrėkite išorinį versijų valdymo žinyną.",
|
||||
"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.",
|
||||
"Send & Receive": "Siųsti ir gauti",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"Add Device": "Legg til enhet",
|
||||
"Add Folder": "Legg til mappe",
|
||||
"Add Remote Device": "Legg til ekstern enhet",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Legg til enheter fra introdusøren til vår enhetsliste, for innbyrdes delte mapper.",
|
||||
"Add new folder?": "Legg til ny mappe?",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adresser",
|
||||
@@ -19,7 +19,7 @@
|
||||
"Advanced settings": "Avanserte innstillinger ",
|
||||
"All Data": "Alle data",
|
||||
"Allow Anonymous Usage Reporting?": "Tillat anonym innsamling av brukerdata?",
|
||||
"Allowed Networks": "Allowed Networks",
|
||||
"Allowed Networks": "Tillatte nettverk",
|
||||
"Alphabetic": "Alfabetisk",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "En ekstern kommando håndterer versjonkontrollen. Den må fjerne filen fra den delte mappa.",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "En ekstern kommando håndterer versjonkontrollen. Den må fjerne filen fra den synkroniserte mappa.",
|
||||
@@ -44,7 +44,7 @@
|
||||
"Copied from original": "Kopiert fra original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Opphavsrett © 2014-2016 for følgende bidragsytere:",
|
||||
"Copyright © 2014-2017 the following Contributors:": "Opphavsrett © 2014-2017 for følgende bidragsytere:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Oppretter ignoreringsmønster, overskriver eksisterende fil i {{path}}.",
|
||||
"Danger!": "Fare!",
|
||||
"Deleted": "Slettet",
|
||||
"Device": "Enhet",
|
||||
@@ -65,11 +65,11 @@
|
||||
"Edit Device": "Rediger enhet",
|
||||
"Edit Folder": "Rediger mappe",
|
||||
"Editing": "Redigerer",
|
||||
"Editing {%path%}.": "Editing {{path}}.",
|
||||
"Editing {%path%}.": "Redigerer {{path}}.",
|
||||
"Enable NAT traversal": "Slå på NAT-traversering",
|
||||
"Enable Relaying": "Aktiver reléforsendelse",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Skriv inn et ikke-negativt nummer (f.eks. \"2.35\") og velg en enhet. Prosenter er deler av total diskstørrelse.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Skriv inn et ikke-priviligert portnummer (1024-65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn kommaseparerte (\"tcp://ip:port\", \"tcp://host:port\") adresser, eller ordet \"dynamic\" for å gjøre automatisk oppslag i adressen.",
|
||||
"Enter ignore patterns, one per line.": "Skriv inn mønster som skal utelates, ett per linje.",
|
||||
"Error": "Feilmelding",
|
||||
@@ -93,7 +93,7 @@
|
||||
"GUI": "grafisk brukergrensesnitt",
|
||||
"GUI Authentication Password": "Passord for GUI-autenisering",
|
||||
"GUI Authentication User": "Bruker for GUI-autenisering",
|
||||
"GUI Listen Address": "GUI Listen Address",
|
||||
"GUI Listen Address": "Lytteadresse for grafisk brukergrensesnitt",
|
||||
"GUI Listen Addresses": "GUI-lytteadresse",
|
||||
"GUI Theme": "GUI-tema",
|
||||
"Generate": "Generer",
|
||||
@@ -158,8 +158,8 @@
|
||||
"Please consult the release notes before performing a major upgrade.": "Sjekk utgivelsesnotatene før en storoppgradering utføres.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Vennligst angi bruker og passord for GUI-autentisering i innstillingsvinduet.",
|
||||
"Please wait": "Vent",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefiks som indikerer at fila kan slettes hvis den forhindrer fjerning av mappe",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefiks som indikerer at mønsteret skal samsvare uten versalsensitivitet",
|
||||
"Preview": "Forhåndsvisning",
|
||||
"Preview Usage Report": "Forhåndsvisning av datainnsamling",
|
||||
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Lagre",
|
||||
"Scan Time Remaining": "Gjenstående tid for gjennomsøking",
|
||||
"Scanning": "Gjennomsøker",
|
||||
"See external versioner help for supported templated command line parameters.": "Se ekstern versjoneringshjelp for støttede mal-baserte kommandolinjeparameter.",
|
||||
"Select the devices to share this folder with.": "Velg enhetene du vil dele denne mappen med.",
|
||||
"Select the folders to share with this device.": "Velg hvilke mapper som skal deles med denne enheten.",
|
||||
"Send & Receive": "Sende og motta",
|
||||
@@ -247,7 +248,7 @@
|
||||
"This Device": "Denne enheten",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Dette kan lett gi hackere tilgang til å lese og endre alle filer på datamaskinen din.",
|
||||
"This is a major version upgrade.": "Dette er en storoppgradering",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Denne innstillingen kontrollerer ledig diskplass krevd på hjemme- (f.eks. indekseringsdatabase-) disken.",
|
||||
"Time": "Klokkeslett",
|
||||
"Trash Can File Versioning": "Papirkurv versjonskontroll",
|
||||
"Type": "Type",
|
||||
@@ -270,10 +271,10 @@
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel, denne stien er en foreldremappe for en eksisterende mappe \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel, denne stien er en undermappe i en eksisterende mappe \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel, denne stien er en undermappe for en eksisterende mappe \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når du legger til en ny enhet, husk at enheten må legges til på andre siden også.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Når en ny mappe blir lagt til, husk at Mappe-ID blir brukt til å binde sammen mapper mellom enheter. Det er forskjell på store og små bokstaver, så IDene må være identiske på alle enhetene.",
|
||||
"Yes": "Ja",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "Du kan også velge en av disse enhetene i nærheten:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Du kan endre ditt valg når som helst i innstillingene.",
|
||||
"You can read more about the two release channels at the link below.": "Du kan lese mer om de to nye utgivelseskanalene i lenken nedenfor.",
|
||||
"You must keep at least one version.": "Du må beholde minst én versjon",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"CPU Utilization": "CPU-gebruik",
|
||||
"Changelog": "Logboek",
|
||||
"Clean out after": "Schoon op na",
|
||||
"Click to see discovery failures": "Klik om ontdekkingsproblemen weer te geven",
|
||||
"Click to see discovery failures": "Klikken om ontdekkingsproblemen weer te geven",
|
||||
"Close": "Sluiten",
|
||||
"Command": "Commando",
|
||||
"Comment, when used at the start of a line": "Reageer indien gebruikt aan het begin van een lijn.",
|
||||
@@ -68,8 +68,8 @@
|
||||
"Editing {%path%}.": "Bezig met bewerken van {{path}}.",
|
||||
"Enable NAT traversal": "NAT traversal inschakelen",
|
||||
"Enable Relaying": "Doorsturen inschakelen",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Voer een positief nummer in (bijv. \"2.35\") en selecteer een eenheid. Percentages zijn een onderdeel van de totale schijfgrootte.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Voer een niet-geprivilegieerd poortnummer in (1024 - 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen in of voer \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
|
||||
"Enter ignore patterns, one per line.": "Voer negeerpatronen in, één per regel.",
|
||||
"Error": "Fout",
|
||||
@@ -158,8 +158,8 @@
|
||||
"Please consult the release notes before performing a major upgrade.": "Lees eerst de release notes voordat u een grote update uitvoert.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Stel een gebruikersnaam en wachtwoord in bij 'Instellingen'.",
|
||||
"Please wait": "Even geduld",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Voorvoegsel dat aangeeft dat het bestand kan worden verwijderd als het bestand het verwijderen van een map voorkomt",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Voorvoegsel dat aangeeft dat het patroon hoofdletterongevoelig moet worden vergeleken",
|
||||
"Preview": "Preview",
|
||||
"Preview Usage Report": "Preview gebruiksstatistieken",
|
||||
"Quick guide to supported patterns": "Snelgids voor ondersteunde patronen",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Bewaar",
|
||||
"Scan Time Remaining": "Resterende scantijd",
|
||||
"Scanning": "Aan het zoeken",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Selecteer de apparaten om deze map mee te delen.",
|
||||
"Select the folders to share with this device.": "Selecteer de mappen om met dit apparaat te delen.",
|
||||
"Send & Receive": "Verzenden & Ontvangen",
|
||||
@@ -247,7 +248,7 @@
|
||||
"This Device": "Dit apparaat",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Dit kan kwaadwilligen eenvoudig toegang geven tot het lezen en wijzigen van bestanden op jouw computer.",
|
||||
"This is a major version upgrade.": "Dit is een grote update.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Deze instelling beheert de benodigde vrije ruimte op de home (index database) schijf.",
|
||||
"Time": "Tijd",
|
||||
"Trash Can File Versioning": "Versiebeheer bestanden prullenbak",
|
||||
"Type": "Type",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Wanneer een nieuw toestel wordt toegevoegd, houd er dan rekening mee dat dit toestel ook aan de andere kant moet worden toegevoegd.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Houd er bij het toevoegen van nieuwe mappen rekening mee dat het map-ID gebruikt wordt om mappen tussen apparaten te verbinden. Dit ID is hoofdlettergevoelig en moet identiek zijn op andere apparaten.",
|
||||
"Yes": "Ja",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "U kunt ook een van de apparaten die dichtbij zijn selecteren:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Je kan je keuze op elk moment aanpassen in de Instellingen.",
|
||||
"You can read more about the two release channels at the link below.": "Je kan meer te weten komen over de twee uitgavekanalen via de link hieronder.",
|
||||
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Lagre",
|
||||
"Scan Time Remaining": "Gjenståande skannetid",
|
||||
"Scanning": "Skannar",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Vel einingane du vil dela denne mappa med.",
|
||||
"Select the folders to share with this device.": "Vel mappene du vil dela med denne eininga.",
|
||||
"Send & Receive": "Sende og motta",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"A device with that ID is already added.": "Urządzenie o tym ID jest już dodane.",
|
||||
"A device with that ID is already added.": "Urządzenie o tym ID już istnieje.",
|
||||
"A negative number of days doesn't make sense.": "Ujemna ilość dni nie ma sensu.",
|
||||
"A new major version may not be compatible with previous versions.": "Nowa wersja może być niekompatybilna z poprzednimi wersjami.",
|
||||
"API Key": "Klucz API",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Zapisz",
|
||||
"Scan Time Remaining": "Pozostały czas skanowania",
|
||||
"Scanning": "Skanowanie",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Wybierz urządzenie, któremu udostępnić folder.",
|
||||
"Select the folders to share with this device.": "Wybierz foldery do współdzielenia z tym urządzeniem.",
|
||||
"Send & Receive": "Wyślij i odbierz",
|
||||
|
||||
@@ -79,10 +79,10 @@
|
||||
"File Pull Order": "Ordem de retirada do arquivo",
|
||||
"File Versioning": "Versionamento de arquivos",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Os bits de permissão de um arquivo são ignorados durante as verificações. Use em sistemas de arquivo FAT.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Os arquivos são movidos para o diretório .stversions quando são substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Os arquivos são movidos para o diretório .stversions quando substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são movidos para a pasta .stversions quando substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Os arquivos são renomeados com datas e movidos para o diretório .stversions quando são substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions após serem substituídos ou removidos pelo Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas e movidos para o diretório .stversions quando substituídos ou apagados pelo Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando substituídos ou removidos pelo Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto dos dispositivos.",
|
||||
"Folder": "Pasta",
|
||||
"Folder ID": "ID da pasta",
|
||||
@@ -150,7 +150,7 @@
|
||||
"Override Changes": "Sobrescrever alterações",
|
||||
"Path": "Caminho",
|
||||
"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": "Caminho para a pasta na máquina local. Será criado caso não exista. O caractere til (~) pode ser usado como um atalho para",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Caminho do diretório onde as versões são salvas (deixe em branco para o diretório padrão .stversions dentro da pasta compartilhada). ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Caminho do diretório onde as versões são salvas (deixe em branco para que seja o diretório padrão .stversions dentro da pasta compartilhada). ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "O caminho onde as versões serão salvas (deixe vazio para usar a pasta padrão .stversions dentro desta pasta).",
|
||||
"Pause": "Pausar",
|
||||
"Pause All": "Pausar Todas",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Salvar",
|
||||
"Scan Time Remaining": "Tempo de verificação restante",
|
||||
"Scanning": "Verificando",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Selecione os dispositivos com os quais esta pasta será compartilhada.",
|
||||
"Select the folders to share with this device.": "Selecione as pastas a serem compartilhadas com este dispositivo.",
|
||||
"Send & Receive": "Envia e recebe",
|
||||
@@ -264,7 +265,7 @@
|
||||
"Usage reporting is always enabled for candidate releases.": "O relatório de uso está sempre habilitado em versões candidatas ao lançamento",
|
||||
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
|
||||
"Version": "Versão",
|
||||
"Versions Path": "Caminho das versões",
|
||||
"Versions Path": "Caminho do versionamento",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "As versões são automaticamente apagadas se elas são mais antigas do que a idade máxima ou excederem o número de arquivos permitido em um intervalo.",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Aviso: este caminho é o diretório pai da pasta \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Aviso: este caminho é o diretório pai da pasta \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando estiver adicionando um dispositivo, lembre-se de que este dispositivo deve ser adicionado do outro lado também.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e deve ser o mesmo em todos os dispositivos.",
|
||||
"Yes": "Sim",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "Vocẽ também pode selecionar um destes dispositivos próximos:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Você pode mudar de ideia a qualquer momento na tela de configurações.",
|
||||
"You can read more about the two release channels at the link below.": "Você pode se informar melhor sobre os dois canais de lançamento no link abaixo.",
|
||||
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Gravar",
|
||||
"Scan Time Remaining": "Tempo restante da verificação",
|
||||
"Scanning": "Verificando",
|
||||
"See external versioner help for supported templated command line parameters.": "Veja a ajuda do gestor de versões externo para saber que parâmetros da linha de comandos são suportados.",
|
||||
"Select the devices to share this folder with.": "Seleccione os dispositivos com os quais vai partilhar esta pasta.",
|
||||
"Select the folders to share with this device.": "Seleccione as pastas a partilhar com este dispositivo.",
|
||||
"Send & Receive": "Envia e recebe",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Сохранить",
|
||||
"Scan Time Remaining": "Оставшееся время сканирования",
|
||||
"Scanning": "Сканирование",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
|
||||
"Select the folders to share with this device.": "Выберите папки, которые будут доступны этому устройству.",
|
||||
"Send & Receive": "Отправить и получить",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Uložiť",
|
||||
"Scan Time Remaining": "Zostávajúci čas skenovania",
|
||||
"Scanning": "Skenovanie",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Vyberte zariadenia s ktorými chcete zdieľať tento adresár.",
|
||||
"Select the folders to share with this device.": "Vyberte adresáre ktoré chcete zdieľať s týmto zariadením.",
|
||||
"Send & Receive": "Prijímať a odosielať",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
|
||||
"Yes": "Áno",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "Môžete tiež vybrať jedno z týchto blízkych zariadení:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Voľbu môžete kedykoľvek zmeniť v dialógu Nastavenia.",
|
||||
"You can read more about the two release channels at the link below.": "O dvoch vydávacích kanáloch si môžete viacej prečítať v odkaze nižšie.",
|
||||
"You must keep at least one version.": "Musíte ponechať aspoň jednu verziu",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Spara",
|
||||
"Scan Time Remaining": "Återstående skanningstid",
|
||||
"Scanning": "Skannar",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Ange enheterna som den här mappen ska delas med.",
|
||||
"Select the folders to share with this device.": "Välj mapparna som ska delas med den här enheten.",
|
||||
"Send & Receive": "Skicka & ta emot",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Kaydet",
|
||||
"Scan Time Remaining": "Kalan Tarama Zamanı",
|
||||
"Scanning": "Taranıyor",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Bu klasörü paylaşacağın aygıtları seç.",
|
||||
"Select the folders to share with this device.": "Bu aygıtla paylaşılacak klasörleri seç.",
|
||||
"Send & Receive": "Gönder & Al",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"Add Device": "Додати пристрій",
|
||||
"Add Folder": "Додати директорію",
|
||||
"Add Remote Device": "Додати віддалений пристрій",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Додати пристрої від пристрою-рекомендувача до нашого списку пристроїв для спільно розділених директорій.",
|
||||
"Add new folder?": "Додати нову директорію?",
|
||||
"Address": "Адреса",
|
||||
"Addresses": "Адреси",
|
||||
@@ -32,7 +32,7 @@
|
||||
"CPU Utilization": "Навантаження CPU",
|
||||
"Changelog": "Перелік змін",
|
||||
"Clean out after": "Очистити після",
|
||||
"Click to see discovery failures": "Click to see discovery failures",
|
||||
"Click to see discovery failures": "Клікніть, щоб переглянути помилки виявлення",
|
||||
"Close": "Закрити",
|
||||
"Command": "Команда",
|
||||
"Comment, when used at the start of a line": "Коментар, якщо використовується на початку рядка",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Зберегти",
|
||||
"Scan Time Remaining": "Час до кінця сканування",
|
||||
"Scanning": "Сканування",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Оберіть пристрої, які матимуть доступ до цієї директорії.",
|
||||
"Select the folders to share with this device.": "Оберіть директорії до яких матиме доступ цей пристрій.",
|
||||
"Send & Receive": "Відправити та отримати",
|
||||
@@ -247,7 +248,7 @@
|
||||
"This Device": "Локальний пристрій",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Це легко може дати хакерам доступ до читання та зміни будь-яких файлів на вашому комп'ютері.",
|
||||
"This is a major version upgrade.": "Це оновлення мажорної версії",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Це налаштування визначає необхідний вільний простір на домашньому (тобто той, що містить базу даних) диску.",
|
||||
"Time": "Час",
|
||||
"Trash Can File Versioning": "Версіонування файлів у кошику ",
|
||||
"Type": "Тип",
|
||||
@@ -273,7 +274,7 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Коли додаєте новий вузол, пам’ятайте, що цей вузол повинен бути доданий і на іншій стороні.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між пристроями. Назви повинні точно співпадати між усіма пристроями, регістр символів має значення.",
|
||||
"Yes": "Так",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can also select one of these nearby devices:": "Ви також можете обрати один із сусідніх пристроїв:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Ви завжди можете змінити свій вибір у Налаштуваннях.",
|
||||
"You can read more about the two release channels at the link below.": "Ви можете прочитати більше про два канали випусків за посиланням нижче.",
|
||||
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "Lưu",
|
||||
"Scan Time Remaining": "Thời gian quét còn lại",
|
||||
"Scanning": "Đang quét",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "Chọn các thiết bị để chia sẻ thư mục này.",
|
||||
"Select the folders to share with this device.": "Chọn các thư mục để chia sẻ với thiết bị này.",
|
||||
"Send & Receive": "Send & Receive",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"Add Device": "添加设备",
|
||||
"Add Folder": "添加文件夹",
|
||||
"Add Remote Device": "添加远程设备",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "添加介绍人中的设备到我们的设备列表,以互相共享文件夹。",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "将此新设备上拥有的“远程设备”都自动添加到您这边的“远程设备”列表中(如果它们跟您存在相同的文件夹的话)",
|
||||
"Add new folder?": "添加新文件夹?",
|
||||
"Address": "地址",
|
||||
"Addresses": "地址列表",
|
||||
@@ -24,7 +24,7 @@
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder.": "使用外部命令接管版本控制。该命令必须自行从共享文件夹中删除该文件。",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "使用外部命令接管版本控制。该命令必须自行从同步文件夹中删除该文件。",
|
||||
"Anonymous Usage Reporting": "匿名使用报告",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "在介绍人设备上被添加的其它设备,也将会被添加到本机。",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "在中介设备上添加的任何“远程设备”,也会被自动添加到本机的“远程设备”列表。",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自动升级现提供了稳定版本和发布候选版之间的选择。",
|
||||
"Automatic upgrades": "自动升级",
|
||||
"Be careful!": "小心!",
|
||||
@@ -32,7 +32,7 @@
|
||||
"CPU Utilization": "CPU使用率",
|
||||
"Changelog": "更新日志",
|
||||
"Clean out after": "在该时间后清除",
|
||||
"Click to see discovery failures": "点击查看发现错误",
|
||||
"Click to see discovery failures": "点击查看设备发现错误",
|
||||
"Close": "关闭",
|
||||
"Command": "命令",
|
||||
"Comment, when used at the start of a line": "注释,在行首使用",
|
||||
@@ -55,27 +55,27 @@
|
||||
"Devices": "设备",
|
||||
"Disconnected": "连接已断开",
|
||||
"Discovered": "已发现",
|
||||
"Discovery": "发现",
|
||||
"Discovery Failures": "发现错误",
|
||||
"Discovery": "设备发现",
|
||||
"Discovery Failures": "设备发现错误",
|
||||
"Documentation": "文档",
|
||||
"Download Rate": "下载速度",
|
||||
"Downloaded": "已下载",
|
||||
"Downloading": "下载中",
|
||||
"Edit": "选项",
|
||||
"Edit Device": "编辑设备",
|
||||
"Edit Folder": "编辑文件夹",
|
||||
"Edit Device": "修改设备选项",
|
||||
"Edit Folder": "修改文件夹选项",
|
||||
"Editing": "正在编辑",
|
||||
"Editing {%path%}.": "正在编辑 {{path}}。",
|
||||
"Enable NAT traversal": "启用 NAT 遍历",
|
||||
"Enable Relaying": "开启中继",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "输入一个非负数(例如“2.35”)并选择单位。百分比是磁盘总大小的一部分。",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "输入一个非负数(例如“2.35”)并选择单位。%表示占磁盘总容量的百分比。",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "输入一个非特权的端口号 (1024 - 65535)。",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "输入以半角逗号分隔的 (\"tcp://ip:port\", \"tcp://host:port\") 设置可用地址列表,或者输入 \"dynamic\" 表示自动发现地址。",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "输入以半角逗号分隔的 (\"tcp://ip:port\", \"tcp://host:port\") 设备地址列表,或者输入 \"dynamic\" 以自动发现设备地址。",
|
||||
"Enter ignore patterns, one per line.": "请输入忽略表达式,每行一条。",
|
||||
"Error": "错误",
|
||||
"External File Versioning": "外部版本控制",
|
||||
"Failed Items": "失败的项目",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "如果无 IPv6 连接则预期连接到 IPv6 服务器会失败。",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "如果本机没有配置IPv6,则无法连接IPv6服务器是正常的。",
|
||||
"File Pull Order": "文件拉取顺序",
|
||||
"File Versioning": "版本控制",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "当查找文件更改时,忽略文件权限位。用在 FAT 文件系统上。",
|
||||
@@ -97,7 +97,7 @@
|
||||
"GUI Listen Addresses": "图形管理界面监听地址",
|
||||
"GUI Theme": "GUI 主题",
|
||||
"Generate": "生成",
|
||||
"Global Changes": "全局更改",
|
||||
"Global Changes": "全局变更",
|
||||
"Global Discovery": "全球发现",
|
||||
"Global Discovery Servers": "全球发现服务器",
|
||||
"Global State": "全局状态",
|
||||
@@ -109,7 +109,7 @@
|
||||
"Incoming Rate Limit (KiB/s)": "下载速率限制 (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "错误的配置可能损坏您文件夹内的内容,使得 Syncthing 无法工作。",
|
||||
"Introduced By": "介绍自",
|
||||
"Introducer": "介绍人设备",
|
||||
"Introducer": "作为中介",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "对本条件取反(例如:不要排除某项)",
|
||||
"Keep Versions": "保留版本数量",
|
||||
"Largest First": "大文件优先",
|
||||
@@ -183,6 +183,7 @@
|
||||
"Save": "保存",
|
||||
"Scan Time Remaining": "扫描剩余时间",
|
||||
"Scanning": "扫描中",
|
||||
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
|
||||
"Select the devices to share this folder with.": "选择将本文件夹共享给哪些设备。",
|
||||
"Select the folders to share with this device.": "选择与该设备共享的文件夹。",
|
||||
"Send & Receive": "发送与接收",
|
||||
|
||||
@@ -1 +1 @@
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)"}
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","eo","es","es-ES","eu","fi","fr","fr-CA","fy","hu","id","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","vi","zh-CN"]
|
||||
var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","eo","es","es-ES","eu","fi","fr","fr-CA","fy","hu","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","vi","zh-CN"]
|
||||
|
||||
@@ -645,7 +645,7 @@
|
||||
</tr>
|
||||
<tr ng-if="deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="fa fa-fw fa-folder"></span> <span translate>Folders</span></th>
|
||||
<td class="text-right" title="{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}">{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}</td>
|
||||
<td class="text-right" title="{{deviceFolders(deviceCfg).map(folderLabel).join(', ')}}">{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -477,7 +477,7 @@ angular.module('syncthing.core')
|
||||
$scope.completion[device]._total = 100;
|
||||
$scope.completion[device]._needBytes = 0;
|
||||
} else {
|
||||
$scope.completion[device]._total = 100 * (1 - needed / total);
|
||||
$scope.completion[device]._total = Math.floor(100 * (1 - needed / total));
|
||||
$scope.completion[device]._needBytes = needed
|
||||
}
|
||||
|
||||
|
||||
+31
-1
@@ -32,7 +32,7 @@ import (
|
||||
|
||||
const (
|
||||
OldestHandledVersion = 10
|
||||
CurrentVersion = 22
|
||||
CurrentVersion = 23
|
||||
MaxRescanIntervalS = 365 * 24 * 60 * 60
|
||||
)
|
||||
|
||||
@@ -323,6 +323,9 @@ func (cfg *Configuration) clean() error {
|
||||
if cfg.Version == 21 {
|
||||
convertV21V22(cfg)
|
||||
}
|
||||
if cfg.Version == 22 {
|
||||
convertV22V23(cfg)
|
||||
}
|
||||
|
||||
// Build a list of available devices
|
||||
existingDevices := make(map[protocol.DeviceID]bool)
|
||||
@@ -372,6 +375,33 @@ func (cfg *Configuration) clean() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertV22V23(cfg *Configuration) {
|
||||
permBits := fs.FileMode(0777)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows has no umask so we must chose a safer set of bits to
|
||||
// begin with.
|
||||
permBits = 0700
|
||||
}
|
||||
for i := range cfg.Folders {
|
||||
fs := cfg.Folders[i].Filesystem()
|
||||
// Invalid config posted, or tests.
|
||||
if fs == nil {
|
||||
continue
|
||||
}
|
||||
if stat, err := fs.Stat(".stfolder"); err == nil && !stat.IsDir() {
|
||||
err = fs.Remove(".stfolder")
|
||||
if err == nil {
|
||||
err = fs.Mkdir(".stfolder", permBits)
|
||||
}
|
||||
if err != nil {
|
||||
l.Fatalln("failed to upgrade folder marker:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Version = 23
|
||||
}
|
||||
|
||||
func convertV21V22(cfg *Configuration) {
|
||||
for i := range cfg.Folders {
|
||||
cfg.Folders[i].FilesystemType = fs.FilesystemTypeBasic
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestDefaultValues(t *testing.T) {
|
||||
|
||||
func TestDeviceConfig(t *testing.T) {
|
||||
for i := OldestHandledVersion; i <= CurrentVersion; i++ {
|
||||
os.Remove("testdata/.stfolder")
|
||||
os.RemoveAll("testdata/.stfolder")
|
||||
wr, err := Load(fmt.Sprintf("testdata/v%d.xml", i), device1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -81,12 +81,17 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
|
||||
|
||||
func (f *FolderConfiguration) CreateMarker() error {
|
||||
if !f.HasMarker() {
|
||||
permBits := fs.FileMode(0777)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows has no umask so we must chose a safer set of bits to
|
||||
// begin with.
|
||||
permBits = 0700
|
||||
}
|
||||
fs := f.Filesystem()
|
||||
fd, err := fs.Create(".stfolder")
|
||||
err := fs.Mkdir(".stfolder", permBits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
if dir, err := fs.Open("."); err == nil {
|
||||
if serr := dir.Sync(); err != nil {
|
||||
l.Infof("fsync %q failed: %v", ".", serr)
|
||||
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
<configuration version="22">
|
||||
<folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
|
||||
<filesystemType>basic</filesystemType>
|
||||
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
|
||||
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
|
||||
<minDiskFree unit="%">1</minDiskFree>
|
||||
<maxConflicts>-1</maxConflicts>
|
||||
<fsync>true</fsync>
|
||||
</folder>
|
||||
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
|
||||
<address>tcp://a</address>
|
||||
</device>
|
||||
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
|
||||
<address>tcp://b</address>
|
||||
</device>
|
||||
</configuration>
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/AudriusButkevicius/kcp-go"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/xtaci/kcp-go"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AudriusButkevicius/kcp-go"
|
||||
"github.com/AudriusButkevicius/pfilter"
|
||||
"github.com/ccding/go-stun/stun"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/nat"
|
||||
"github.com/xtaci/kcp-go"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ func newBasicFilesystem(root string) *BasicFilesystem {
|
||||
|
||||
// rooted expands the relative path to the full path that is then used with os
|
||||
// package. If the relative path somehow causes the final path to escape the root
|
||||
// directoy, this returns an error, to prevent accessing files that are not in the
|
||||
// directory, this returns an error, to prevent accessing files that are not in the
|
||||
// shared directory.
|
||||
func (f *BasicFilesystem) rooted(rel string) (string, error) {
|
||||
// The root must not be empty.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -133,3 +134,20 @@ func NewFilesystem(fsType FilesystemType, uri string) Filesystem {
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// IsInternal returns true if the file, as a path relative to the folder
|
||||
// root, represents an internal file that should always be ignored. The file
|
||||
// path must be clean (i.e., in canonical shortest form).
|
||||
func IsInternal(file string) bool {
|
||||
internals := []string{".stfolder", ".stignore", ".stversions"}
|
||||
pathSep := string(PathSeparator)
|
||||
for _, internal := range internals {
|
||||
if file == internal {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(file, internal+pathSep) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2017 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsInternal(t *testing.T) {
|
||||
cases := []struct {
|
||||
file string
|
||||
internal bool
|
||||
}{
|
||||
{".stfolder", true},
|
||||
{".stignore", true},
|
||||
{".stversions", true},
|
||||
{".stfolder/foo", true},
|
||||
{".stignore/foo", true},
|
||||
{".stversions/foo", true},
|
||||
|
||||
{".stfolderfoo", false},
|
||||
{".stignorefoo", false},
|
||||
{".stversionsfoo", false},
|
||||
{"foo.stfolder", false},
|
||||
{"foo.stignore", false},
|
||||
{"foo.stversions", false},
|
||||
{"foo/.stfolder", false},
|
||||
{"foo/.stignore", false},
|
||||
{"foo/.stversions", false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := IsInternal(filepath.FromSlash(tc.file))
|
||||
if res != tc.internal {
|
||||
t.Errorf("Unexpected result: IsInteral(%q): %v should be %v", tc.file, res, tc.internal)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package ignore
|
||||
package fs
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
@@ -4,7 +4,7 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package ignore
|
||||
package fs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
+64
-64
@@ -70,8 +70,8 @@ func (r Result) IsCaseFolded() bool {
|
||||
// called on it) and if any of the files have Changed(). To forget all
|
||||
// files, call Reset().
|
||||
type ChangeDetector interface {
|
||||
Remember(name string, modtime time.Time)
|
||||
Seen(name string) bool
|
||||
Remember(fs fs.Filesystem, name string, modtime time.Time)
|
||||
Seen(fs fs.Filesystem, name string) bool
|
||||
Changed() bool
|
||||
Reset()
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func New(fs fs.Filesystem, opts ...Option) *Matcher {
|
||||
opt(m)
|
||||
}
|
||||
if m.changeDetector == nil {
|
||||
m.changeDetector = newModtimeChecker(fs)
|
||||
m.changeDetector = newModtimeChecker()
|
||||
}
|
||||
if m.withCache {
|
||||
go m.clean(2 * time.Hour)
|
||||
@@ -128,25 +128,19 @@ func (m *Matcher) Load(file string) error {
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
if m.changeDetector.Seen(file) && !m.changeDetector.Changed() {
|
||||
if m.changeDetector.Seen(m.fs, file) && !m.changeDetector.Changed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd, err := m.fs.Open(file)
|
||||
fd, info, err := loadIgnoreFile(m.fs, file, m.changeDetector)
|
||||
if err != nil {
|
||||
m.parseLocked(&bytes.Buffer{}, file)
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
info, err := fd.Stat()
|
||||
if err != nil {
|
||||
m.parseLocked(&bytes.Buffer{}, file)
|
||||
return err
|
||||
}
|
||||
|
||||
m.changeDetector.Reset()
|
||||
m.changeDetector.Remember(file, info.ModTime())
|
||||
m.changeDetector.Remember(m.fs, file, info.ModTime())
|
||||
|
||||
return m.parseLocked(fd, file)
|
||||
}
|
||||
@@ -158,7 +152,7 @@ func (m *Matcher) Parse(r io.Reader, file string) error {
|
||||
}
|
||||
|
||||
func (m *Matcher) parseLocked(r io.Reader, file string) error {
|
||||
lines, patterns, err := parseIgnoreFile(m.fs, r, file, m.changeDetector)
|
||||
lines, patterns, err := parseIgnoreFile(m.fs, r, file, m.changeDetector, make(map[string]struct{}))
|
||||
// Error is saved and returned at the end. We process the patterns
|
||||
// (possibly blank) anyway.
|
||||
|
||||
@@ -179,7 +173,7 @@ func (m *Matcher) parseLocked(r io.Reader, file string) error {
|
||||
}
|
||||
|
||||
func (m *Matcher) Match(file string) (result Result) {
|
||||
if m == nil || file == "." {
|
||||
if file == "." {
|
||||
return resultNotMatched
|
||||
}
|
||||
|
||||
@@ -234,10 +228,6 @@ func (m *Matcher) Lines() []string {
|
||||
|
||||
// Patterns return a list of the loaded patterns, as they've been parsed
|
||||
func (m *Matcher) Patterns() []string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
@@ -278,10 +268,10 @@ func (m *Matcher) clean(d time.Duration) {
|
||||
// ShouldIgnore returns true when a file is temporary, internal or ignored
|
||||
func (m *Matcher) ShouldIgnore(filename string) bool {
|
||||
switch {
|
||||
case IsTemporary(filename):
|
||||
case fs.IsTemporary(filename):
|
||||
return true
|
||||
|
||||
case IsInternal(filename):
|
||||
case fs.IsInternal(filename):
|
||||
return true
|
||||
|
||||
case m.Match(filename).IsIgnored():
|
||||
@@ -300,28 +290,48 @@ func hashPatterns(patterns []Pattern) string {
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func loadIgnoreFile(fs fs.Filesystem, file string, cd ChangeDetector) ([]string, []Pattern, error) {
|
||||
if cd.Seen(file) {
|
||||
func loadIgnoreFile(fs fs.Filesystem, file string, cd ChangeDetector) (fs.File, fs.FileInfo, error) {
|
||||
fd, err := fs.Open(file)
|
||||
if err != nil {
|
||||
return fd, nil, err
|
||||
}
|
||||
|
||||
info, err := fd.Stat()
|
||||
if err != nil {
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
return fd, info, err
|
||||
}
|
||||
|
||||
func loadParseIncludeFile(filesystem fs.Filesystem, file string, cd ChangeDetector, linesSeen map[string]struct{}) ([]string, []Pattern, error) {
|
||||
// Allow escaping the folders filesystem.
|
||||
// TODO: Deprecate, somehow?
|
||||
if filesystem.Type() == fs.FilesystemTypeBasic {
|
||||
uri := filesystem.URI()
|
||||
joined := filepath.Join(uri, file)
|
||||
if !strings.HasPrefix(joined, uri) {
|
||||
filesystem = fs.NewFilesystem(filesystem.Type(), filepath.Dir(joined))
|
||||
file = filepath.Base(joined)
|
||||
}
|
||||
}
|
||||
|
||||
if cd.Seen(filesystem, file) {
|
||||
return nil, nil, fmt.Errorf("multiple include of ignore file %q", file)
|
||||
}
|
||||
|
||||
fd, err := fs.Open(file)
|
||||
fd, info, err := loadIgnoreFile(filesystem, file, cd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
info, err := fd.Stat()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cd.Remember(filesystem, file, info.ModTime())
|
||||
|
||||
cd.Remember(file, info.ModTime())
|
||||
|
||||
return parseIgnoreFile(fs, fd, file, cd)
|
||||
return parseIgnoreFile(filesystem, fd, file, cd, linesSeen)
|
||||
}
|
||||
|
||||
func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd ChangeDetector) ([]string, []Pattern, error) {
|
||||
func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd ChangeDetector, linesSeen map[string]struct{}) ([]string, []Pattern, error) {
|
||||
var lines []string
|
||||
var patterns []Pattern
|
||||
|
||||
@@ -386,9 +396,9 @@ func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd Chan
|
||||
}
|
||||
patterns = append(patterns, pattern)
|
||||
} else if strings.HasPrefix(line, "#include ") {
|
||||
includeRel := line[len("#include "):]
|
||||
includeRel := strings.TrimSpace(line[len("#include "):])
|
||||
includeFile := filepath.Join(filepath.Dir(currentFile), includeRel)
|
||||
_, includePatterns, err := loadIgnoreFile(fs, includeFile, cd)
|
||||
_, includePatterns, err := loadParseIncludeFile(fs, includeFile, cd, linesSeen)
|
||||
if err != nil {
|
||||
return fmt.Errorf("include of %q: %v", includeRel, err)
|
||||
}
|
||||
@@ -418,6 +428,10 @@ func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd Chan
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
lines = append(lines, line)
|
||||
if _, ok := linesSeen[line]; ok {
|
||||
continue
|
||||
}
|
||||
linesSeen[line] = struct{}{}
|
||||
switch {
|
||||
case line == "":
|
||||
continue
|
||||
@@ -447,23 +461,6 @@ func parseIgnoreFile(fs fs.Filesystem, fd io.Reader, currentFile string, cd Chan
|
||||
return lines, patterns, nil
|
||||
}
|
||||
|
||||
// IsInternal returns true if the file, as a path relative to the folder
|
||||
// root, represents an internal file that should always be ignored. The file
|
||||
// path must be clean (i.e., in canonical shortest form).
|
||||
func IsInternal(file string) bool {
|
||||
internals := []string{".stfolder", ".stignore", ".stversions"}
|
||||
pathSep := string(fs.PathSeparator)
|
||||
for _, internal := range internals {
|
||||
if file == internal {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(file, internal+pathSep) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WriteIgnores is a convenience function to avoid code duplication
|
||||
func WriteIgnores(filesystem fs.Filesystem, path string, content []string) error {
|
||||
fd, err := osutil.CreateAtomicFilesystem(filesystem, path)
|
||||
@@ -483,35 +480,38 @@ func WriteIgnores(filesystem fs.Filesystem, path string, content []string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// modtimeChecker is the default implementation of ChangeDetector
|
||||
type modtimeChecker struct {
|
||||
fs fs.Filesystem
|
||||
modtimes map[string]time.Time
|
||||
type modtimeCheckerKey struct {
|
||||
fs fs.Filesystem
|
||||
name string
|
||||
}
|
||||
|
||||
func newModtimeChecker(fs fs.Filesystem) *modtimeChecker {
|
||||
// modtimeChecker is the default implementation of ChangeDetector
|
||||
type modtimeChecker struct {
|
||||
modtimes map[modtimeCheckerKey]time.Time
|
||||
}
|
||||
|
||||
func newModtimeChecker() *modtimeChecker {
|
||||
return &modtimeChecker{
|
||||
fs: fs,
|
||||
modtimes: map[string]time.Time{},
|
||||
modtimes: map[modtimeCheckerKey]time.Time{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *modtimeChecker) Remember(name string, modtime time.Time) {
|
||||
c.modtimes[name] = modtime
|
||||
func (c *modtimeChecker) Remember(fs fs.Filesystem, name string, modtime time.Time) {
|
||||
c.modtimes[modtimeCheckerKey{fs, name}] = modtime
|
||||
}
|
||||
|
||||
func (c *modtimeChecker) Seen(name string) bool {
|
||||
_, ok := c.modtimes[name]
|
||||
func (c *modtimeChecker) Seen(fs fs.Filesystem, name string) bool {
|
||||
_, ok := c.modtimes[modtimeCheckerKey{fs, name}]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (c *modtimeChecker) Reset() {
|
||||
c.modtimes = map[string]time.Time{}
|
||||
c.modtimes = map[modtimeCheckerKey]time.Time{}
|
||||
}
|
||||
|
||||
func (c *modtimeChecker) Changed() bool {
|
||||
for name, modtime := range c.modtimes {
|
||||
info, err := c.fs.Stat(name)
|
||||
for key, modtime := range c.modtimes {
|
||||
info, err := key.fs.Stat(key.name)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
+30
-31
@@ -837,37 +837,6 @@ func TestGobwasGlobIssue18(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInternal(t *testing.T) {
|
||||
cases := []struct {
|
||||
file string
|
||||
internal bool
|
||||
}{
|
||||
{".stfolder", true},
|
||||
{".stignore", true},
|
||||
{".stversions", true},
|
||||
{".stfolder/foo", true},
|
||||
{".stignore/foo", true},
|
||||
{".stversions/foo", true},
|
||||
|
||||
{".stfolderfoo", false},
|
||||
{".stignorefoo", false},
|
||||
{".stversionsfoo", false},
|
||||
{"foo.stfolder", false},
|
||||
{"foo.stignore", false},
|
||||
{"foo.stversions", false},
|
||||
{"foo/.stfolder", false},
|
||||
{"foo/.stignore", false},
|
||||
{"foo/.stversions", false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := IsInternal(filepath.FromSlash(tc.file))
|
||||
if res != tc.internal {
|
||||
t.Errorf("Unexpected result: IsInteral(%q): %v should be %v", tc.file, res, tc.internal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoot(t *testing.T) {
|
||||
stignore := `
|
||||
!/a
|
||||
@@ -903,6 +872,7 @@ func TestLines(t *testing.T) {
|
||||
|
||||
!/a
|
||||
/*
|
||||
!/a
|
||||
`
|
||||
|
||||
pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."), WithCache(true))
|
||||
@@ -917,6 +887,7 @@ func TestLines(t *testing.T) {
|
||||
"",
|
||||
"!/a",
|
||||
"/*",
|
||||
"!/a",
|
||||
"",
|
||||
}
|
||||
|
||||
@@ -929,5 +900,33 @@ func TestLines(t *testing.T) {
|
||||
t.Fatalf("Lines()[%d] == %s, expected %s", i, lines[i], expectedLines[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateLines(t *testing.T) {
|
||||
stignore := `
|
||||
!/a
|
||||
/*
|
||||
!/a
|
||||
`
|
||||
stignoreFiltered := `
|
||||
!/a
|
||||
/*
|
||||
`
|
||||
|
||||
pats := New(fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"), WithCache(true))
|
||||
|
||||
err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
patsLen := len(pats.patterns)
|
||||
|
||||
err = pats.Parse(bytes.NewBufferString(stignoreFiltered), ".stignore")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if patsLen != len(pats.patterns) {
|
||||
t.Fatalf("Parsed patterns differ when manually removing duplicate lines")
|
||||
}
|
||||
}
|
||||
|
||||
+35
-1
@@ -9,10 +9,13 @@ package model
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
)
|
||||
|
||||
type folder struct {
|
||||
stateTracker
|
||||
config.FolderConfiguration
|
||||
|
||||
scan folderScanner
|
||||
model *Model
|
||||
@@ -21,9 +24,23 @@ type folder struct {
|
||||
initialScanFinished chan struct{}
|
||||
}
|
||||
|
||||
func (f *folder) IndexUpdated() {
|
||||
func newFolder(model *Model, cfg config.FolderConfiguration) folder {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return folder{
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
FolderConfiguration: cfg,
|
||||
|
||||
scan: newFolderScanner(cfg),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
model: model,
|
||||
initialScanFinished: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *folder) IndexUpdated() {
|
||||
}
|
||||
func (f *folder) DelayScan(next time.Duration) {
|
||||
f.scan.Delay(next)
|
||||
}
|
||||
@@ -54,3 +71,20 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *folder) scanTimerFired() {
|
||||
err := f.scanSubdirs(nil)
|
||||
|
||||
select {
|
||||
case <-f.initialScanFinished:
|
||||
default:
|
||||
status := "Completed"
|
||||
if err != nil {
|
||||
status = "Failed"
|
||||
}
|
||||
l.Infoln(status, "initial scan of", f.Type.String(), "folder", f.Description())
|
||||
close(f.initialScanFinished)
|
||||
}
|
||||
|
||||
f.scan.Reschedule()
|
||||
}
|
||||
|
||||
@@ -57,7 +57,3 @@ func (f *folderScanner) Scan(subdirs []string) error {
|
||||
func (f *folderScanner) Delay(next time.Duration) {
|
||||
f.delay <- next
|
||||
}
|
||||
|
||||
func (f *folderScanner) HasNoInterval() bool {
|
||||
return f.interval == 0
|
||||
}
|
||||
|
||||
+19
-20
@@ -336,7 +336,7 @@ func (m *Model) RemoveFolder(folder string) {
|
||||
// Delete syncthing specific files
|
||||
folderCfg := m.folderCfgs[folder]
|
||||
fs := folderCfg.Filesystem()
|
||||
fs.Remove(".stfolder")
|
||||
fs.RemoveAll(".stfolder")
|
||||
|
||||
m.tearDownFolderLocked(folder)
|
||||
// Remove it from the database
|
||||
@@ -1156,7 +1156,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
|
||||
// acceptable relative to "folderPath" and in canonical form, so we can
|
||||
// trust it.
|
||||
|
||||
if ignore.IsInternal(name) {
|
||||
if fs.IsInternal(name) {
|
||||
l.Debugf("%v REQ(in) for internal file: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, len(buf))
|
||||
return protocol.ErrNoSuchFile
|
||||
}
|
||||
@@ -1174,7 +1174,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
|
||||
// Only check temp files if the flag is set, and if we are set to advertise
|
||||
// the temp indexes.
|
||||
if fromTemporary && !folderCfg.DisableTempIndexes {
|
||||
tempFn := ignore.TempName(name)
|
||||
tempFn := fs.TempName(name)
|
||||
|
||||
if info, err := folderFs.Lstat(tempFn); err != nil || !info.IsRegular() {
|
||||
// Reject reads for anything that doesn't exist or is something
|
||||
@@ -1250,25 +1250,28 @@ func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
|
||||
defer m.fmut.RUnlock()
|
||||
|
||||
cfg, ok := m.folderCfgs[folder]
|
||||
if ok {
|
||||
if !cfg.HasMarker() {
|
||||
return nil, nil, fmt.Errorf("Folder %s stopped", folder)
|
||||
if !ok {
|
||||
cfg, ok = m.cfg.Folders()[folder]
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("Folder %s does not exist", folder)
|
||||
}
|
||||
}
|
||||
|
||||
ignores := m.folderIgnores[folder]
|
||||
if err := m.checkFolderPath(cfg); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ignores, ok := m.folderIgnores[folder]
|
||||
if ok {
|
||||
return ignores.Lines(), ignores.Patterns(), nil
|
||||
}
|
||||
|
||||
if cfg, ok := m.cfg.Folders()[folder]; ok {
|
||||
matcher := ignore.New(cfg.Filesystem())
|
||||
if err := matcher.Load(".stignore"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return matcher.Lines(), matcher.Patterns(), nil
|
||||
ignores = ignore.New(fs.NewFilesystem(cfg.FilesystemType, cfg.Path))
|
||||
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("Folder %s does not exist", folder)
|
||||
return ignores.Lines(), ignores.Patterns(), nil
|
||||
}
|
||||
|
||||
func (m *Model) SetIgnores(folder string, content []string) error {
|
||||
@@ -2299,12 +2302,8 @@ func (m *Model) checkFreeSpace(req config.Size, fs fs.Filesystem) error {
|
||||
}
|
||||
|
||||
usage, err := fs.Usage(".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check available storage space")
|
||||
}
|
||||
|
||||
if req.Percentage() {
|
||||
freePct := (1 - float64(usage.Free)/float64(usage.Total)) * 100
|
||||
freePct := (float64(usage.Free) / float64(usage.Total)) * 100
|
||||
if err == nil && freePct < val {
|
||||
return fmt.Errorf("insufficient space in %v %v: %f %% < %v", fs.Type(), fs.URI(), freePct, req)
|
||||
}
|
||||
@@ -2553,7 +2552,7 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string {
|
||||
func trimUntilParentKnown(dirs []string, exists func(dir string) bool) []string {
|
||||
var subs []string
|
||||
for _, sub := range dirs {
|
||||
for sub != "" && !ignore.IsInternal(sub) {
|
||||
for sub != "" && !fs.IsInternal(sub) {
|
||||
sub = filepath.Clean(sub)
|
||||
parent := filepath.Dir(sub)
|
||||
if parent == "." || exists(parent) {
|
||||
|
||||
+19
-8
@@ -1026,7 +1026,8 @@ func changeIgnores(t *testing.T, m *Model, expected []string) {
|
||||
|
||||
func TestIgnores(t *testing.T) {
|
||||
// Assure a clean start state
|
||||
ioutil.WriteFile("testdata/.stfolder", nil, 0644)
|
||||
os.RemoveAll("testdata/.stfolder")
|
||||
os.MkdirAll("testdata/.stfolder", 0644)
|
||||
ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644)
|
||||
|
||||
db := db.OpenMemory()
|
||||
@@ -1083,6 +1084,11 @@ func TestIgnores(t *testing.T) {
|
||||
// added to the model and thus there is no initial scan happening.
|
||||
|
||||
changeIgnores(t, m, expected)
|
||||
|
||||
// Make sure no .stignore file is considered valid
|
||||
os.Rename("testdata/.stignore", "testdata/.stignore.bak")
|
||||
changeIgnores(t, m, []string{})
|
||||
os.Rename("testdata/.stignore.bak", "testdata/.stignore")
|
||||
}
|
||||
|
||||
func TestROScanRecovery(t *testing.T) {
|
||||
@@ -2382,27 +2388,32 @@ func (fakeAddr) String() string {
|
||||
return "address"
|
||||
}
|
||||
|
||||
type alwaysChangedKey struct {
|
||||
fs fs.Filesystem
|
||||
name string
|
||||
}
|
||||
|
||||
// alwaysChanges is an ignore.ChangeDetector that always returns true on Changed()
|
||||
type alwaysChanged struct {
|
||||
seen map[string]struct{}
|
||||
seen map[alwaysChangedKey]struct{}
|
||||
}
|
||||
|
||||
func newAlwaysChanged() *alwaysChanged {
|
||||
return &alwaysChanged{
|
||||
seen: make(map[string]struct{}),
|
||||
seen: make(map[alwaysChangedKey]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *alwaysChanged) Remember(name string, _ time.Time) {
|
||||
c.seen[name] = struct{}{}
|
||||
func (c *alwaysChanged) Remember(fs fs.Filesystem, name string, _ time.Time) {
|
||||
c.seen[alwaysChangedKey{fs, name}] = struct{}{}
|
||||
}
|
||||
|
||||
func (c *alwaysChanged) Reset() {
|
||||
c.seen = make(map[string]struct{})
|
||||
c.seen = make(map[alwaysChangedKey]struct{})
|
||||
}
|
||||
|
||||
func (c *alwaysChanged) Seen(name string) bool {
|
||||
_, ok := c.seen[name]
|
||||
func (c *alwaysChanged) Seen(fs fs.Filesystem, name string) bool {
|
||||
_, ok := c.seen[alwaysChangedKey{fs, name}]
|
||||
return ok
|
||||
}
|
||||
|
||||
|
||||
+2
-33
@@ -7,7 +7,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
@@ -21,23 +20,10 @@ func init() {
|
||||
|
||||
type sendOnlyFolder struct {
|
||||
folder
|
||||
config.FolderConfiguration
|
||||
}
|
||||
|
||||
func newSendOnlyFolder(model *Model, cfg config.FolderConfiguration, _ versioner.Versioner, _ fs.Filesystem) service {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &sendOnlyFolder{
|
||||
folder: folder{
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
scan: newFolderScanner(cfg),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
model: model,
|
||||
initialScanFinished: make(chan struct{}),
|
||||
},
|
||||
FolderConfiguration: cfg,
|
||||
}
|
||||
return &sendOnlyFolder{folder: newFolder(model, cfg)}
|
||||
}
|
||||
|
||||
func (f *sendOnlyFolder) Serve() {
|
||||
@@ -55,24 +41,7 @@ func (f *sendOnlyFolder) Serve() {
|
||||
|
||||
case <-f.scan.timer.C:
|
||||
l.Debugln(f, "Scanning subdirectories")
|
||||
err := f.scanSubdirs(nil)
|
||||
|
||||
select {
|
||||
case <-f.initialScanFinished:
|
||||
default:
|
||||
status := "Completed"
|
||||
if err != nil {
|
||||
status = "Failed"
|
||||
}
|
||||
l.Infoln(status, "initial scan (ro) of", f.Description())
|
||||
close(f.initialScanFinished)
|
||||
}
|
||||
|
||||
if f.scan.HasNoInterval() {
|
||||
continue
|
||||
}
|
||||
|
||||
f.scan.Reschedule()
|
||||
f.scanTimerFired()
|
||||
|
||||
case req := <-f.scan.now:
|
||||
req.err <- f.scanSubdirs(req.subdirs)
|
||||
|
||||
+67
-76
@@ -7,7 +7,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@@ -81,7 +80,6 @@ type dbUpdateJob struct {
|
||||
|
||||
type sendReceiveFolder struct {
|
||||
folder
|
||||
config.FolderConfiguration
|
||||
|
||||
fs fs.Filesystem
|
||||
versioner versioner.Versioner
|
||||
@@ -98,18 +96,8 @@ type sendReceiveFolder struct {
|
||||
}
|
||||
|
||||
func newSendReceiveFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
f := &sendReceiveFolder{
|
||||
folder: folder{
|
||||
stateTracker: newStateTracker(cfg.ID),
|
||||
scan: newFolderScanner(cfg),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
model: model,
|
||||
initialScanFinished: make(chan struct{}),
|
||||
},
|
||||
FolderConfiguration: cfg,
|
||||
folder: newFolder(model, cfg),
|
||||
|
||||
fs: fs,
|
||||
versioner: ver,
|
||||
@@ -251,21 +239,24 @@ func (f *sendReceiveFolder) Serve() {
|
||||
break
|
||||
}
|
||||
|
||||
if tries > 10 {
|
||||
if tries > 2 {
|
||||
// We've tried a bunch of times to get in sync, but
|
||||
// we're not making it. Probably there are write
|
||||
// errors preventing us. Flag this with a warning and
|
||||
// wait a bit longer before retrying.
|
||||
l.Infof("Folder %q isn't making progress. Pausing puller for %v.", f.folderID, f.pause)
|
||||
l.Debugln(f, "next pull in", f.pause)
|
||||
|
||||
if folderErrors := f.currentErrors(); len(folderErrors) > 0 {
|
||||
for path, err := range folderErrors {
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.Description(), path, err)
|
||||
}
|
||||
events.Default.Log(events.FolderErrors, map[string]interface{}{
|
||||
"folder": f.folderID,
|
||||
"errors": folderErrors,
|
||||
})
|
||||
}
|
||||
|
||||
l.Infof("Folder %q isn't making progress. Pausing puller for %v.", f.folderID, f.pause)
|
||||
l.Debugln(f, "next pull in", f.pause)
|
||||
|
||||
f.pullTimer.Reset(f.pause)
|
||||
break
|
||||
}
|
||||
@@ -277,18 +268,7 @@ func (f *sendReceiveFolder) Serve() {
|
||||
// same time.
|
||||
case <-f.scan.timer.C:
|
||||
l.Debugln(f, "Scanning subdirectories")
|
||||
err := f.scanSubdirs(nil)
|
||||
f.scan.Reschedule()
|
||||
select {
|
||||
case <-f.initialScanFinished:
|
||||
default:
|
||||
close(f.initialScanFinished)
|
||||
status := "Completed"
|
||||
if err != nil {
|
||||
status = "Failed"
|
||||
}
|
||||
l.Infoln(status, "initial scan (rw) of", f.Description())
|
||||
}
|
||||
f.scanTimerFired()
|
||||
|
||||
case req := <-f.scan.now:
|
||||
req.err <- f.scanSubdirs(req.subdirs)
|
||||
@@ -625,7 +605,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo) {
|
||||
case err == nil && (!info.IsDir() || info.IsSymlink()):
|
||||
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
return
|
||||
}
|
||||
@@ -656,14 +636,14 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo) {
|
||||
if err = osutil.InWritableDir(mkdir, f.fs, file.Name); err == nil {
|
||||
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
}
|
||||
return
|
||||
// Weird error when stat()'ing the dir. Probably won't work to do
|
||||
// anything else with it if we can't even stat() it.
|
||||
case err != nil:
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
return
|
||||
}
|
||||
@@ -676,7 +656,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo) {
|
||||
} else if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err == nil {
|
||||
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -713,7 +693,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo) {
|
||||
// Index entry from a Syncthing predating the support for including
|
||||
// the link target in the index entry. We log this as an error.
|
||||
err = errors.New("incompatible symlink entry; rescan with newer Syncthing on source")
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
return
|
||||
}
|
||||
@@ -724,7 +704,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo) {
|
||||
// path.
|
||||
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
return
|
||||
}
|
||||
@@ -739,7 +719,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo) {
|
||||
if err = osutil.InWritableDir(createLink, f.fs, file.Name); err == nil {
|
||||
f.dbUpdates <- dbUpdateJob{file, dbUpdateHandleSymlink}
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -772,7 +752,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, matcher *ignore.Ma
|
||||
files, _ := f.fs.DirNames(file.Name)
|
||||
for _, dirFile := range files {
|
||||
fullDirFile := filepath.Join(file.Name, dirFile)
|
||||
if ignore.IsTemporary(dirFile) || (matcher != nil && matcher.Match(fullDirFile).IsDeletable()) {
|
||||
if fs.IsTemporary(dirFile) || (matcher != nil && matcher.Match(fullDirFile).IsDeletable()) {
|
||||
f.fs.RemoveAll(fullDirFile)
|
||||
}
|
||||
}
|
||||
@@ -788,7 +768,7 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, matcher *ignore.Ma
|
||||
// file and not a directory etc) and that the delete is handled.
|
||||
f.dbUpdates <- dbUpdateJob{file, dbUpdateDeleteDir}
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): delete: %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, dir %q): delete: %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -841,7 +821,7 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo) {
|
||||
// not a directory etc) and that the delete is handled.
|
||||
f.dbUpdates <- dbUpdateJob{file, dbUpdateDeleteFile}
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, file %q): delete: %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, file %q): delete: %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -903,7 +883,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
|
||||
|
||||
err = f.shortcutFile(target)
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): rename from %q metadata: %v", f.folderID, target.Name, source.Name, err)
|
||||
l.Debugf("Puller (folder %q, file %q): rename from %q metadata: %v", f.folderID, target.Name, source.Name, err)
|
||||
f.newError(target.Name, err)
|
||||
return
|
||||
}
|
||||
@@ -916,7 +896,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo) {
|
||||
|
||||
err = osutil.InWritableDir(f.fs.Remove, f.fs, source.Name)
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): delete %q after failed rename: %v", f.folderID, target.Name, source.Name, err)
|
||||
l.Debugf("Puller (folder %q, file %q): delete %q after failed rename: %v", f.folderID, target.Name, source.Name, err)
|
||||
f.newError(target.Name, err)
|
||||
return
|
||||
}
|
||||
@@ -991,7 +971,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
l.Infoln("Puller: shortcut:", err)
|
||||
l.Debugln("Puller: shortcut:", err)
|
||||
f.newError(file.Name, err)
|
||||
} else {
|
||||
f.dbUpdates <- dbUpdateJob{file, dbUpdateShortcutFile}
|
||||
@@ -1000,28 +980,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
return
|
||||
}
|
||||
|
||||
tempName := ignore.TempName(file.Name)
|
||||
|
||||
if hasCurFile && !curFile.IsDirectory() && !curFile.IsSymlink() {
|
||||
// Check that the file on disk is what we expect it to be according to
|
||||
// the database. If there's a mismatch here, there might be local
|
||||
// changes that we don't know about yet and we should scan before
|
||||
// touching the file. If we can't stat the file we'll just pull it.
|
||||
if info, err := f.fs.Lstat(file.Name); err == nil {
|
||||
if !info.ModTime().Equal(curFile.ModTime()) || info.Size() != curFile.Size {
|
||||
l.Debugln("file modified but not rescanned; not pulling:", file.Name)
|
||||
// Scan() is synchronous (i.e. blocks until the scan is
|
||||
// completed and returns an error), but a scan can't happen
|
||||
// while we're in the puller routine. Request the scan in the
|
||||
// background and it'll be handled when the current pulling
|
||||
// sweep is complete. As we do retries, we'll queue the scan
|
||||
// for this file up to ten times, but the last nine of those
|
||||
// scans will be cheap...
|
||||
go f.Scan([]string{file.Name})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
tempName := fs.TempName(file.Name)
|
||||
|
||||
scanner.PopulateOffsets(file.Blocks)
|
||||
|
||||
@@ -1101,7 +1060,8 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
available: reused,
|
||||
availableUpdated: time.Now(),
|
||||
ignorePerms: f.ignorePermissions(file),
|
||||
version: curFile.Version,
|
||||
hasCurFile: hasCurFile,
|
||||
curFile: curFile,
|
||||
mut: sync.NewRWMutex(),
|
||||
sparse: !f.DisableSparseFiles,
|
||||
created: time.Now(),
|
||||
@@ -1122,7 +1082,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo) error {
|
||||
if !f.ignorePermissions(file) {
|
||||
if err := f.fs.Chmod(file.Name, fs.FileMode(file.Permissions&0777)); err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: chmod: %v", f.folderID, file.Name, err)
|
||||
l.Debugf("Puller (folder %q, file %q): shortcut: chmod: %v", f.folderID, file.Name, err)
|
||||
f.newError(file.Name, err)
|
||||
return err
|
||||
}
|
||||
@@ -1386,6 +1346,36 @@ func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
|
||||
// There is an old file or directory already in place. We need to
|
||||
// handle that.
|
||||
|
||||
curMode := uint32(stat.Mode())
|
||||
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(state.file.Name) {
|
||||
curMode |= 0111
|
||||
}
|
||||
|
||||
// Check that the file on disk is what we expect it to be according to
|
||||
// the database. If there's a mismatch here, there might be local
|
||||
// changes that we don't know about yet and we should scan before
|
||||
// touching the file.
|
||||
// There is also a case where we think the file should be there, but
|
||||
// it was removed, which is a conflict, yet creations always wins when
|
||||
// competing with a deletion, so no need to handle that specially.
|
||||
switch {
|
||||
// The file reappeared from nowhere, or mtime/size has changed, fallthrough -> rescan.
|
||||
case !state.hasCurFile || !stat.ModTime().Equal(state.curFile.ModTime()) || stat.Size() != state.curFile.Size:
|
||||
fallthrough
|
||||
// Permissions have changed, means the file has changed, rescan.
|
||||
case !f.ignorePermissions(state.curFile) && state.curFile.HasPermissionBits() && !scanner.PermsEqual(state.curFile.Permissions, curMode):
|
||||
l.Debugln("file modified but not rescanned; not finishing:", state.curFile.Name)
|
||||
// Scan() is synchronous (i.e. blocks until the scan is
|
||||
// completed and returns an error), but a scan can't happen
|
||||
// while we're in the puller routine. Request the scan in the
|
||||
// background and it'll be handled when the current pulling
|
||||
// sweep is complete. As we do retries, we'll queue the scan
|
||||
// for this file up to ten times, but the last nine of those
|
||||
// scans will be cheap...
|
||||
go f.Scan([]string{state.curFile.Name})
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case stat.IsDir() || stat.IsSymlink():
|
||||
// It's a directory or a symlink. These are not versioned or
|
||||
@@ -1400,13 +1390,13 @@ func (f *sendReceiveFolder) performFinish(state *sharedPullerState) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case f.inConflict(state.version, state.file.Version):
|
||||
case f.inConflict(state.curFile.Version, state.file.Version):
|
||||
// The new file has been changed in conflict with the existing one. We
|
||||
// should file it away as a conflict instead of just removing or
|
||||
// archiving. Also merge with the version vector we had, to indicate
|
||||
// we have resolved the conflict.
|
||||
|
||||
state.file.Version = state.file.Version.Merge(state.version)
|
||||
state.file.Version = state.file.Version.Merge(state.curFile.Version)
|
||||
err = osutil.InWritableDir(func(name string) error {
|
||||
return f.moveForConflict(name, state.file.ModifiedBy.String())
|
||||
}, f.fs, state.file.Name)
|
||||
@@ -1451,7 +1441,7 @@ func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
l.Infoln("Puller: final:", err)
|
||||
l.Debugln("Puller: final:", err)
|
||||
f.newError(state.file.Name, err)
|
||||
}
|
||||
events.Default.Log(events.ItemFinished, map[string]interface{}{
|
||||
@@ -1481,13 +1471,10 @@ func (f *sendReceiveFolder) Jobs() ([]string, []string) {
|
||||
// dbUpdaterRoutine aggregates db updates and commits them in batches no
|
||||
// larger than 1000 items, and no more delayed than 2 seconds.
|
||||
func (f *sendReceiveFolder) dbUpdaterRoutine() {
|
||||
const (
|
||||
maxBatchSize = 1000
|
||||
maxBatchTime = 2 * time.Second
|
||||
)
|
||||
const maxBatchTime = 2 * time.Second
|
||||
|
||||
batch := make([]dbUpdateJob, 0, maxBatchSize)
|
||||
files := make([]protocol.FileInfo, 0, maxBatchSize)
|
||||
batch := make([]dbUpdateJob, 0, maxBatchSizeFiles)
|
||||
files := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
|
||||
tick := time.NewTicker(maxBatchTime)
|
||||
defer tick.Stop()
|
||||
|
||||
@@ -1547,6 +1534,7 @@ func (f *sendReceiveFolder) dbUpdaterRoutine() {
|
||||
files = files[:0]
|
||||
}
|
||||
|
||||
batchSizeBytes := 0
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
@@ -1558,13 +1546,16 @@ loop:
|
||||
job.file.Sequence = 0
|
||||
batch = append(batch, job)
|
||||
|
||||
if len(batch) == maxBatchSize {
|
||||
batchSizeBytes += job.file.ProtoSize()
|
||||
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||
handleBatch()
|
||||
batchSizeBytes = 0
|
||||
}
|
||||
|
||||
case <-tick.C:
|
||||
if len(batch) > 0 {
|
||||
handleBatch()
|
||||
batchSizeBytes = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/scanner"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
@@ -27,15 +26,15 @@ func TestMain(m *testing.M) {
|
||||
// We do this to make sure that the temp file required for the tests
|
||||
// does not get removed during the tests. Also set the prefix so it's
|
||||
// found correctly regardless of platform.
|
||||
if ignore.TempPrefix != ignore.WindowsTempPrefix {
|
||||
originalPrefix := ignore.TempPrefix
|
||||
ignore.TempPrefix = ignore.WindowsTempPrefix
|
||||
if fs.TempPrefix != fs.WindowsTempPrefix {
|
||||
originalPrefix := fs.TempPrefix
|
||||
fs.TempPrefix = fs.WindowsTempPrefix
|
||||
defer func() {
|
||||
ignore.TempPrefix = originalPrefix
|
||||
fs.TempPrefix = originalPrefix
|
||||
}()
|
||||
}
|
||||
future := time.Now().Add(time.Hour)
|
||||
err := os.Chtimes(filepath.Join("testdata", ignore.TempName("file")), future, future)
|
||||
err := os.Chtimes(filepath.Join("testdata", fs.TempName("file")), future, future)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -191,14 +190,14 @@ func TestCopierFinder(t *testing.T) {
|
||||
// After dropping out blocks found locally:
|
||||
// Pull: 1, 5, 6, 8
|
||||
|
||||
tempFile := filepath.Join("testdata", ignore.TempName("file2"))
|
||||
tempFile := filepath.Join("testdata", fs.TempName("file2"))
|
||||
err := os.Remove(tempFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
existingBlocks := []int{0, 2, 3, 4, 0, 0, 7, 0}
|
||||
existingFile := setUpFile(ignore.TempName("file"), existingBlocks)
|
||||
existingFile := setUpFile(fs.TempName("file"), existingBlocks)
|
||||
requiredFile := existingFile
|
||||
requiredFile.Blocks = blocks[1:]
|
||||
requiredFile.Name = "file2"
|
||||
@@ -261,7 +260,7 @@ func TestCopierFinder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestWeakHash(t *testing.T) {
|
||||
tempFile := filepath.Join("testdata", ignore.TempName("weakhash"))
|
||||
tempFile := filepath.Join("testdata", fs.TempName("weakhash"))
|
||||
var shift int64 = 10
|
||||
var size int64 = 1 << 20
|
||||
expectBlocks := int(size / protocol.BlockSize)
|
||||
@@ -466,12 +465,12 @@ func TestLastResortPulling(t *testing.T) {
|
||||
}
|
||||
|
||||
(<-finisherChan).fd.Close()
|
||||
os.Remove(filepath.Join("testdata", ignore.TempName("newfile")))
|
||||
os.Remove(filepath.Join("testdata", fs.TempName("newfile")))
|
||||
}
|
||||
|
||||
func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
file := setUpFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
|
||||
defer os.Remove("testdata/" + ignore.TempName("filex"))
|
||||
defer os.Remove("testdata/" + fs.TempName("filex"))
|
||||
|
||||
db := db.OpenMemory()
|
||||
|
||||
@@ -545,7 +544,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
|
||||
func TestDeregisterOnFailInPull(t *testing.T) {
|
||||
file := setUpFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
|
||||
defer os.Remove("testdata/" + ignore.TempName("filex"))
|
||||
defer os.Remove("testdata/" + fs.TempName("filex"))
|
||||
|
||||
db := db.OpenMemory()
|
||||
m := NewModel(defaultConfig, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
|
||||
|
||||
@@ -27,7 +27,8 @@ type sharedPullerState struct {
|
||||
realName string
|
||||
reused int // Number of blocks reused from temporary file
|
||||
ignorePerms bool
|
||||
version protocol.Vector // The current (old) version
|
||||
hasCurFile bool // Whether curFile is set
|
||||
curFile protocol.FileInfo // The file as it exists now in our database
|
||||
sparse bool
|
||||
created time.Time
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/AudriusButkevicius/kcp-go"
|
||||
"github.com/syncthing/syncthing/lib/dialer"
|
||||
"github.com/xtaci/kcp-go"
|
||||
)
|
||||
|
||||
func BenchmarkRequestsRawTCP(b *testing.B) {
|
||||
|
||||
+18
-12
@@ -8,7 +8,6 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/luhn"
|
||||
@@ -155,16 +154,17 @@ func luhnify(s string) (string, error) {
|
||||
panic("unsupported string length")
|
||||
}
|
||||
|
||||
res := make([]string, 0, 4)
|
||||
res := make([]byte, 4*(13+1))
|
||||
for i := 0; i < 4; i++ {
|
||||
p := s[i*13 : (i+1)*13]
|
||||
copy(res[i*(13+1):], p)
|
||||
l, err := luhn.Base32.Generate(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res = append(res, fmt.Sprintf("%s%c", p, l))
|
||||
res[(i+1)*(13)+i] = byte(l)
|
||||
}
|
||||
return res[0] + res[1] + res[2] + res[3], nil
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
func unluhnify(s string) (string, error) {
|
||||
@@ -172,25 +172,31 @@ func unluhnify(s string) (string, error) {
|
||||
return "", fmt.Errorf("%q: unsupported string length %d", s, len(s))
|
||||
}
|
||||
|
||||
res := make([]string, 0, 4)
|
||||
res := make([]byte, 52)
|
||||
for i := 0; i < 4; i++ {
|
||||
p := s[i*14 : (i+1)*14-1]
|
||||
p := s[i*(13+1) : (i+1)*(13+1)-1]
|
||||
copy(res[i*13:], p)
|
||||
l, err := luhn.Base32.Generate(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
|
||||
if s[(i+1)*14-1] != byte(l) {
|
||||
return "", fmt.Errorf("%q: check digit incorrect", s)
|
||||
}
|
||||
res = append(res, p)
|
||||
}
|
||||
return res[0] + res[1] + res[2] + res[3], nil
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
func chunkify(s string) string {
|
||||
s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
|
||||
s = strings.Trim(s, "-")
|
||||
return s
|
||||
chunks := len(s) / 7
|
||||
res := make([]byte, chunks*(7+1)-1)
|
||||
for i := 0; i < chunks; i++ {
|
||||
if i > 0 {
|
||||
res[i*(7+1)-1] = '-'
|
||||
}
|
||||
copy(res[i*(7+1):], s[i*7:(i+1)*7])
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
|
||||
func unchunkify(s string) string {
|
||||
|
||||
@@ -150,3 +150,41 @@ func TestNewDeviceIDMarshalling(t *testing.T) {
|
||||
t.Error("Mismatch in old -> new direction")
|
||||
}
|
||||
}
|
||||
|
||||
var resStr string
|
||||
|
||||
func BenchmarkLuhnify(b *testing.B) {
|
||||
str := "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB"
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr, err = luhnify(str)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnluhnify(b *testing.B) {
|
||||
str, _ := luhnify("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB")
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr, err = unluhnify(str)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChunkify(b *testing.B) {
|
||||
str := "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB"
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr = chunkify(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnchunkify(b *testing.B) {
|
||||
str := chunkify("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB")
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr = unchunkify(str)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -85,6 +85,9 @@ func Walk(ctx context.Context, cfg Config) (chan protocol.FileInfo, error) {
|
||||
if w.Filesystem == nil {
|
||||
panic("no filesystem specified")
|
||||
}
|
||||
if w.Matcher == nil {
|
||||
w.Matcher = ignore.New(w.Filesystem)
|
||||
}
|
||||
|
||||
return w.walk(ctx)
|
||||
}
|
||||
@@ -230,7 +233,7 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
return skip
|
||||
}
|
||||
|
||||
if ignore.IsTemporary(path) {
|
||||
if fs.IsTemporary(path) {
|
||||
l.Debugln("temporary:", path)
|
||||
if info.IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
|
||||
w.Filesystem.Remove(path)
|
||||
@@ -239,7 +242,7 @@ func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protoco
|
||||
return nil
|
||||
}
|
||||
|
||||
if ignore.IsInternal(path) {
|
||||
if fs.IsInternal(path) {
|
||||
l.Debugln("ignored (internal):", path)
|
||||
return skip
|
||||
}
|
||||
|
||||
@@ -55,17 +55,13 @@ func SelectAlgo() {
|
||||
}
|
||||
|
||||
case "minio":
|
||||
// When set to "minio", use that. Benchmark anyway to be able to
|
||||
// present the difference.
|
||||
benchmark()
|
||||
// When set to "minio", use that.
|
||||
selectMinio()
|
||||
|
||||
default:
|
||||
// When set to anything else, such as "standard", use the default Go
|
||||
// implementation. Benchmark that anyway, so we can report something
|
||||
// useful in Report(). Make sure not to touch the minio
|
||||
// implementation. Make sure not to touch the minio
|
||||
// implementation as it may be disabled for incompatibility reasons.
|
||||
cryptoPerf = cpuBenchOnce(benchmarkingIterations*benchmarkingDuration, cryptoSha256.New)
|
||||
}
|
||||
|
||||
verifyCorrectness()
|
||||
@@ -89,6 +85,10 @@ func Report() {
|
||||
otherImpl = defaultImpl
|
||||
}
|
||||
|
||||
if selectedRate == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
l.Infof("Single thread SHA256 performance is %s using %s (%s using %s).", formatRate(selectedRate), selectedImpl, formatRate(otherRate), otherImpl)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright (C) 2016 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// The existence of this file means we get 0% test coverage rather than no
|
||||
// test coverage at all. Remove when implementing an actual test.
|
||||
|
||||
package tlsutil
|
||||
+15
-8
@@ -7,7 +7,6 @@
|
||||
package tlsutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
@@ -16,7 +15,6 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
@@ -130,27 +128,36 @@ func (l *DowngradingListener) AcceptNoWrapTLS() (net.Conn, bool, error) {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
var first [1]byte
|
||||
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
bs, err := br.Peek(1)
|
||||
n, err := conn.Read(first[:])
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
if err != nil || n == 0 {
|
||||
// We hit a read error here, but the Accept() call succeeded so we must not return an error.
|
||||
// We return the connection as is with a special error which handles this
|
||||
// special case in Accept().
|
||||
return conn, false, ErrIdentificationFailed
|
||||
}
|
||||
|
||||
return &UnionedConnection{br, conn}, bs[0] == 0x16, nil
|
||||
return &UnionedConnection{&first, conn}, first[0] == 0x16, nil
|
||||
}
|
||||
|
||||
type UnionedConnection struct {
|
||||
io.Reader
|
||||
first *[1]byte
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *UnionedConnection) Read(b []byte) (n int, err error) {
|
||||
return c.Reader.Read(b)
|
||||
if c.first != nil {
|
||||
if len(b) == 0 {
|
||||
// this probably doesn't happen, but handle it anyway
|
||||
return 0, nil
|
||||
}
|
||||
b[0] = c.first[0]
|
||||
c.first = nil
|
||||
return 1, nil
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func publicKey(priv interface{}) interface{} {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (C) 2016 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// The existence of this file means we get 0% test coverage rather than no
|
||||
// test coverage at all. Remove when implementing an actual test.
|
||||
|
||||
package tlsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUnionedConnection(t *testing.T) {
|
||||
cases := []struct {
|
||||
data []byte
|
||||
isTLS bool
|
||||
}{
|
||||
{[]byte{0}, false},
|
||||
{[]byte{0x16}, true},
|
||||
{[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, false},
|
||||
{[]byte{0x16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, true},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
fc := &fakeAccepter{tc.data}
|
||||
dl := DowngradingListener{fc, nil}
|
||||
|
||||
conn, isTLS, err := dl.AcceptNoWrapTLS()
|
||||
if err != nil {
|
||||
t.Fatalf("%d: %v", i, err)
|
||||
}
|
||||
if conn == nil {
|
||||
t.Fatalf("%d: unexpected nil conn", i)
|
||||
}
|
||||
if isTLS != tc.isTLS {
|
||||
t.Errorf("%d: isTLS=%v, expected %v", i, isTLS, tc.isTLS)
|
||||
}
|
||||
|
||||
// Read all the data, check it's the same
|
||||
var bs []byte
|
||||
buf := make([]byte, 128)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("%d: read error: %v", i, err)
|
||||
}
|
||||
if len(bs) == 0 {
|
||||
// first read; should return just one byte
|
||||
if n != 1 {
|
||||
t.Errorf("%d: first read returned %d bytes, not 1", i, n)
|
||||
}
|
||||
// Check that we've nilled out the "first" thing
|
||||
if conn.(*UnionedConnection).first != nil {
|
||||
t.Errorf("%d: expected first read to clear out the `first` attribute", i)
|
||||
}
|
||||
}
|
||||
bs = append(bs, buf[:n]...)
|
||||
}
|
||||
if !bytes.Equal(bs, tc.data) {
|
||||
t.Errorf("%d: got wrong data", i)
|
||||
}
|
||||
|
||||
t.Logf("%d: %v, %x", i, isTLS, bs)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAccepter struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (f *fakeAccepter) Accept() (net.Conn, error) {
|
||||
return &fakeConn{f.data}, nil
|
||||
}
|
||||
|
||||
func (f *fakeAccepter) Addr() net.Addr { return nil }
|
||||
func (f *fakeAccepter) Close() error { return nil }
|
||||
|
||||
type fakeConn struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (f *fakeConn) Read(b []byte) (int, error) {
|
||||
if len(f.data) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(b, f.data)
|
||||
f.data = f.data[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *fakeConn) Write(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (f *fakeConn) Close() error { return nil }
|
||||
func (f *fakeConn) LocalAddr() net.Addr { return nil }
|
||||
func (f *fakeConn) RemoteAddr() net.Addr { return nil }
|
||||
func (f *fakeConn) SetDeadline(time.Time) error { return nil }
|
||||
func (f *fakeConn) SetReadDeadline(time.Time) error { return nil }
|
||||
func (f *fakeConn) SetWriteDeadline(time.Time) error { return nil }
|
||||
+3
-7
@@ -42,7 +42,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -168,7 +167,9 @@ USER-AGENT: syncthing/1.0
|
||||
|
||||
_, err = socket.WriteTo(search, ssdp)
|
||||
if err != nil {
|
||||
l.Infoln(err)
|
||||
if e, ok := err.(net.Error); !ok || !e.Timeout() {
|
||||
l.Infoln(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -226,11 +227,6 @@ func parseResponse(deviceType string, resp []byte) (IGD, error) {
|
||||
}
|
||||
|
||||
deviceUUID := strings.TrimPrefix(strings.Split(deviceUSN, "::")[0], "uuid:")
|
||||
matched, _ := regexp.MatchString("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}", deviceUUID)
|
||||
if !matched {
|
||||
l.Infoln("Invalid IGD response: invalid device UUID", deviceUUID, "(continuing anyway)")
|
||||
}
|
||||
|
||||
response, err = http.Get(deviceDescriptionLocation)
|
||||
if err != nil {
|
||||
return IGD{}, err
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "STDISCOSRV" "1" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "STDISCOSRV" "1" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
stdiscosrv \- Syncthing Discovery Server
|
||||
.
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "STRELAYSRV" "1" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "STRELAYSRV" "1" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
strelaysrv \- Syncthing Relay Server
|
||||
.
|
||||
@@ -144,6 +144,7 @@ An optional description about who provides the relay.
|
||||
.TP
|
||||
.B \-status\-srv=<listen addr>
|
||||
Listen address for status service (blank to disable) (default ":22070").
|
||||
Status service is used by the relay pool server UI for displaying stats (data transfered, number of clients, etc.)
|
||||
.UNINDENT
|
||||
.SH SETTING UP
|
||||
.sp
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-BEP" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-BEP" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-bep \- Block Exchange Protocol v1
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-CONFIG" "5" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-CONFIG" "5" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-config \- Syncthing Configuration
|
||||
.
|
||||
@@ -139,6 +139,7 @@ The following shows an example of the default configuration file (IDs will diffe
|
||||
<releasesURL>https://api.github.com/repos/syncthing/syncthing/releases?per_page=30</releasesURL>
|
||||
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
|
||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||
<defaultFolderPath>~</defaultFolderPath>
|
||||
</options>
|
||||
</configuration>
|
||||
.ft P
|
||||
@@ -522,6 +523,7 @@ If set, this is the API key that enables usage of the REST interface.
|
||||
<releasesURL>https://api.github.com/repos/syncthing/syncthing/releases?per_page=30</releasesURL>
|
||||
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
|
||||
<tempIndexMinBlocks>10</tempIndexMinBlocks>
|
||||
<defaultFolderPath>~</defaultFolderPath>
|
||||
</options>
|
||||
.ft P
|
||||
.fi
|
||||
@@ -655,7 +657,7 @@ slow response time (slow connection/cpu) and large index exchanges.
|
||||
Ping interval in seconds. Don\(aqt change it unless you feel it\(aqs necessary.
|
||||
.TP
|
||||
.B minHomeDiskFree
|
||||
The minimum required free space that should be available on the the
|
||||
The minimum required free space that should be available on the
|
||||
partition holding the configuration and index. Accepted units are \fB%\fP, \fBkB\fP,
|
||||
\fBMB\fP, \fBGB\fP and \fBTB\fP\&.
|
||||
.TP
|
||||
@@ -670,6 +672,10 @@ announces will only be adopted when a name has not already been set.
|
||||
.B tempIndexMinBlocks
|
||||
When exchanging index information for incomplete transfers, only take
|
||||
into account files that have at least this many blocks.
|
||||
.TP
|
||||
.B defaultFolderPath
|
||||
The UI will propose to create new folders at this path. This can be disabled by
|
||||
setting this to an empty string.
|
||||
.UNINDENT
|
||||
.SS Listen Addresses
|
||||
.sp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-device-ids \- Understanding Device IDs
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-EVENT-API" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-EVENT-API" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-event-api \- Event API
|
||||
.
|
||||
|
||||
+23
-1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-FAQ" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-FAQ" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-faq \- Frequently Asked Questions
|
||||
.
|
||||
@@ -424,6 +424,28 @@ Syncthing logs to stdout by default. On Windows Syncthing by default also
|
||||
creates \fBsyncthing.log\fP in Syncthing\(aqs home directory (run \fBsyncthing
|
||||
\-paths\fP to see where that is). Command line option \fB\-logfile\fP can be used
|
||||
to specify a user\-defined logfile.
|
||||
.SS How can I view the history of changes?
|
||||
.sp
|
||||
The web GUI contains a \fBGlobal Changes\fP button under the device list which
|
||||
displays changes since the last (re)start of Syncthing. With the \fB\-audit\fP
|
||||
option you can enable a persistent, detailed log of changes and most
|
||||
activities, which contains a \fBJSON\fP formatted sequence of events in the
|
||||
\fB~/.config/syncthing/audit\-_date_\-_time_.log\fP file.
|
||||
.SS Does the audit log contain every change?
|
||||
.sp
|
||||
The audit log (and the \fBGlobal Changes\fP window) sees the changes that your
|
||||
Syncthing sees. When Syncthing is continuously connected it usually sees every change
|
||||
happening immediately and thus knows which node initiated the change.
|
||||
When topology gets complex or when your node reconnects after some time offline,
|
||||
Syncthing synchronises with its neighbours: It gets the latest synchronised state
|
||||
from the neighbour, which is the \fIresult\fP of all the changes between the last
|
||||
known state (before disconnect or network delay) and the current state at the
|
||||
neighbour, and if there were updates, deletes, creates, conflicts, which were
|
||||
overlapping we only see the \fIlatest change\fP for a given file or directory (and
|
||||
the node where that latest change occurred). When we connect to multiple neighbours
|
||||
Syncthing decides which neighbor has the latest state, or if the states conflict
|
||||
it initiates the conflict resolution procedure, which in the end results in a consistent
|
||||
up\-to\-date state with all the neighbours.
|
||||
.SS How do I upgrade Syncthing?
|
||||
.sp
|
||||
If you use a package manager such as Debian\(aqs apt\-get, you should upgrade
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-globaldisco \- Global Discovery Protocol v3
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-localdisco \- Local Discovery Protocol v4
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-NETWORKING" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-NETWORKING" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-networking \- Firewall Setup
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-RELAY" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-RELAY" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-relay \- Relay Protocol v1
|
||||
.
|
||||
@@ -99,7 +99,7 @@ which then can be used to establish a connection in session mode.
|
||||
.sp
|
||||
If the client fails to send a JoinRelayRequest message within the first ping
|
||||
interval, the connection is terminated.
|
||||
If the client fails to send a message (even if its a ping message) every minute
|
||||
If the client fails to send a message (even if it\(aqs a ping message) every minute
|
||||
(by default), the connection is terminated.
|
||||
.SS Temporary protocol submode
|
||||
.sp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-REST-API" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-REST-API" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-rest-api \- REST API
|
||||
.
|
||||
@@ -45,6 +45,49 @@ the configuration file. To use an API key, set the request header
|
||||
"X\-API\-Key: abc123" http://localhost:8384/rest/...\fP can be used to invoke
|
||||
with \fBcurl\fP\&.
|
||||
.SH SYSTEM ENDPOINTS
|
||||
.SS GET /rest/system/browse
|
||||
.sp
|
||||
Returns a list of directories matching the path given by the optional parameter
|
||||
\fBcurrent\fP\&. The path can use \fI\%patterns as described in Go\(aqs filepath package\fP <\fBhttps://golang.org/pkg/path/filepath/#Match\fP>\&. A \(aq*\(aq will always be appended
|
||||
to the given path (e.g. \fB/tmp/\fP matches all its subdirectories). If the option
|
||||
\fBcurrent\fP is not given, filesystem root paths are returned.
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
$ curl \-H "X\-API\-Key: yourkey" localhost:8384/rest/system/browse | json_pp
|
||||
[
|
||||
"/"
|
||||
]
|
||||
|
||||
$ curl \-H "X\-API\-Key: yourkey" localhost:8384/rest/system/browse?current=/var/ | json_pp
|
||||
[
|
||||
"/var/backups/",
|
||||
"/var/cache/",
|
||||
"/var/lib/",
|
||||
"/var/local/",
|
||||
"/var/lock/",
|
||||
"/var/log/",
|
||||
"/var/mail/",
|
||||
"/var/opt/",
|
||||
"/var/run/",
|
||||
"/var/spool/",
|
||||
"/var/tmp/"
|
||||
]
|
||||
|
||||
$ curl \-H "X\-API\-Key: yourkey" localhost:8384/rest/system/browse?current=/var/*o | json_pp
|
||||
[
|
||||
"/var/local/",
|
||||
"/var/lock/",
|
||||
"/var/log/",
|
||||
"/var/opt/",
|
||||
"/var/spool/"
|
||||
]
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS GET /rest/system/config
|
||||
.sp
|
||||
Returns the current configuration.
|
||||
@@ -407,7 +450,7 @@ Returns the list of recent log entries.
|
||||
.sp
|
||||
Pause the given device or all devices.
|
||||
.sp
|
||||
Takes the optional parameter \fBdevice\fP (device ID). When ommitted,
|
||||
Takes the optional parameter \fBdevice\fP (device ID). When omitted,
|
||||
pauses all devices. Returns status 200 and no content upon success, or status
|
||||
500 and a plain text error on failure.
|
||||
.SS GET /rest/system/ping
|
||||
@@ -451,7 +494,7 @@ Post with empty body to immediately restart Syncthing.
|
||||
.sp
|
||||
Resume the given device or all devices.
|
||||
.sp
|
||||
Takes the optional parameter \fBdevice\fP (device ID). When ommitted,
|
||||
Takes the optional parameter \fBdevice\fP (device ID). When omitted,
|
||||
resumes all devices. Returns status 200 and no content upon success, or status
|
||||
500 and a plain text error on failure.
|
||||
.SS POST /rest/system/shutdown
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-SECURITY" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-SECURITY" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-security \- Security Principles
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-STIGNORE" "5" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-STIGNORE" "5" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
||||
.
|
||||
@@ -116,7 +116,8 @@ Windows does not support escaping \fB\e[foo \- bar\e]\fP\&.
|
||||
\fBNOTE:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
Prefixes can be specified in any order.
|
||||
Prefixes can be specified in any order (e.g. "(?d)(?i)"), but cannot be in a
|
||||
single pair of parentheses (not "(?di)").
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH EXAMPLE
|
||||
|
||||
+16
-10
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-VERSIONING" "7" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-VERSIONING" "7" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
|
||||
.
|
||||
@@ -35,8 +35,8 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
Syncthing supports archiving the old version of a file when it is deleted or
|
||||
replaced with a newer version from the cluster. This is called "file
|
||||
versioning" and uses one of the available \fIversioning strategies\fP described
|
||||
below. File versioning is configured per folder and defaults to "no file
|
||||
versioning", i.e. no old copies of files are kept.
|
||||
below. File versioning is configured per folder, on a per\-device basis, and
|
||||
defaults to "no file versioning", i.e. no old copies of files are kept.
|
||||
.SH TRASH CAN FILE VERSIONING
|
||||
.sp
|
||||
This versioning strategy emulates the common "trash can" approach. When a file
|
||||
@@ -97,10 +97,17 @@ only for 10 days, use 10. \fBNote: Set to 0 to keep versions forever.\fP
|
||||
.SH EXTERNAL FILE VERSIONING
|
||||
.sp
|
||||
This versioning method delegates the decision on what to do to an external
|
||||
command (program or script). The only configuration option is the name of the
|
||||
command. This should be an absolute path name. Just prior to a file being
|
||||
replaced, the command will be run with two parameters: the path to the folder,
|
||||
and the path to the file within the folder.
|
||||
command (program or script).
|
||||
Just prior to a file being replaced, the command will be run.
|
||||
The command should be specified as an absolute path, and can use the following templated arguments:
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B %FOLDER_PATH%
|
||||
Path to the folder
|
||||
.TP
|
||||
.B %FILE_PATH%
|
||||
Path to the file within the folder
|
||||
.UNINDENT
|
||||
.SS Example for Unixes
|
||||
.sp
|
||||
Lets say I want to keep the latest version of each file as they are replaced
|
||||
@@ -133,8 +140,7 @@ mv \-f "$folderpath/$filepath" "$versionspath/$filepath"
|
||||
.UNINDENT
|
||||
.sp
|
||||
I must ensure that the script has execute permissions (\fBchmod 755
|
||||
onlylatest.sh\fP), then configure Syncthing with the above path as the command
|
||||
name.
|
||||
onlylatest.sh\fP), then configure Syncthing with command \fB/Users/jb/bin/onlylatest.sh %FOLDER_PATH% %FILE_PATH%\fP
|
||||
.sp
|
||||
Lets assume I have a folder "default" in ~/Sync, and that within that folder
|
||||
there is a file \fBdocs/letter.txt\fP that is being replaced or deleted. The
|
||||
@@ -186,7 +192,7 @@ move /Y "%FOLDER_PATH%\e%FILE_PATH%" "%VERSIONS_PATH%\e%FILE_PATH%"
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
Finally, I set \fBC:\eUsers\emfrnd\eScripts\eonlylatest.bat\fP as command name in
|
||||
Finally, I set \fBC:\eUsers\emfrnd\eScripts\eonlylatest.bat %FOLDER_PATH% %FILE_PATH%\fP as command name in
|
||||
Syncthing.
|
||||
.SH AUTHOR
|
||||
The Syncthing Authors
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING" "1" "July 03, 2017" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING" "1" "September 04, 2017" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing \- Syncthing
|
||||
.
|
||||
|
||||
Generated
Vendored
Generated
Vendored
+8
-6
@@ -6,6 +6,8 @@ import (
|
||||
"crypto/des"
|
||||
"crypto/sha1"
|
||||
|
||||
"github.com/templexxx/xor"
|
||||
|
||||
"golang.org/x/crypto/blowfish"
|
||||
"golang.org/x/crypto/cast5"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
@@ -218,8 +220,8 @@ func NewSimpleXORBlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *simpleXORBlockCrypt) Encrypt(dst, src []byte) { xorBytes(dst, src, c.xortbl) }
|
||||
func (c *simpleXORBlockCrypt) Decrypt(dst, src []byte) { xorBytes(dst, src, c.xortbl) }
|
||||
func (c *simpleXORBlockCrypt) Encrypt(dst, src []byte) { xor.Bytes(dst, src, c.xortbl) }
|
||||
func (c *simpleXORBlockCrypt) Decrypt(dst, src []byte) { xor.Bytes(dst, src, c.xortbl) }
|
||||
|
||||
type noneBlockCrypt struct{}
|
||||
|
||||
@@ -239,11 +241,11 @@ func encrypt(block cipher.Block, dst, src, buf []byte) {
|
||||
n := len(src) / blocksize
|
||||
base := 0
|
||||
for i := 0; i < n; i++ {
|
||||
xorWords(dst[base:], src[base:], tbl)
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
}
|
||||
xorBytes(dst[base:], src[base:], tbl)
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
|
||||
func decrypt(block cipher.Block, dst, src, buf []byte) {
|
||||
@@ -255,9 +257,9 @@ func decrypt(block cipher.Block, dst, src, buf []byte) {
|
||||
base := 0
|
||||
for i := 0; i < n; i++ {
|
||||
block.Encrypt(next, src[base:])
|
||||
xorWords(dst[base:], src[base:], tbl)
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
}
|
||||
xorBytes(dst[base:], src[base:], tbl)
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
Generated
Vendored
+17
-17
@@ -22,8 +22,8 @@ type (
|
||||
data []byte
|
||||
}
|
||||
|
||||
// FECDecoder for decoding incoming packets
|
||||
FECDecoder struct {
|
||||
// fecDecoder for decoding incoming packets
|
||||
fecDecoder struct {
|
||||
rxlimit int // queue size limit
|
||||
dataShards int
|
||||
parityShards int
|
||||
@@ -39,7 +39,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func newFECDecoder(rxlimit, dataShards, parityShards int) *FECDecoder {
|
||||
func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
||||
if dataShards <= 0 || parityShards <= 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *FECDecoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
fec := new(FECDecoder)
|
||||
fec := new(fecDecoder)
|
||||
fec.rxlimit = rxlimit
|
||||
fec.dataShards = dataShards
|
||||
fec.parityShards = parityShards
|
||||
@@ -63,7 +63,7 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *FECDecoder {
|
||||
}
|
||||
|
||||
// decodeBytes a fec packet
|
||||
func (dec *FECDecoder) decodeBytes(data []byte) fecPacket {
|
||||
func (dec *fecDecoder) decodeBytes(data []byte) fecPacket {
|
||||
var pkt fecPacket
|
||||
pkt.seqid = binary.LittleEndian.Uint32(data)
|
||||
pkt.flag = binary.LittleEndian.Uint16(data[4:])
|
||||
@@ -74,8 +74,8 @@ func (dec *FECDecoder) decodeBytes(data []byte) fecPacket {
|
||||
return pkt
|
||||
}
|
||||
|
||||
// Decode a fec packet
|
||||
func (dec *FECDecoder) Decode(pkt fecPacket) (recovered [][]byte) {
|
||||
// decode a fec packet
|
||||
func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
// insertion
|
||||
n := len(dec.rx) - 1
|
||||
insertIdx := 0
|
||||
@@ -179,7 +179,7 @@ func (dec *FECDecoder) Decode(pkt fecPacket) (recovered [][]byte) {
|
||||
}
|
||||
|
||||
// free a range of fecPacket, and zero for GC recycling
|
||||
func (dec *FECDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
||||
func (dec *fecDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
||||
for i := first; i < first+n; i++ { // free
|
||||
xmitBuf.Put(q[i].data)
|
||||
}
|
||||
@@ -191,8 +191,8 @@ func (dec *FECDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
||||
}
|
||||
|
||||
type (
|
||||
// FECEncoder for encoding outgoing packets
|
||||
FECEncoder struct {
|
||||
// fecEncoder for encoding outgoing packets
|
||||
fecEncoder struct {
|
||||
dataShards int
|
||||
parityShards int
|
||||
shardSize int
|
||||
@@ -214,11 +214,11 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func newFECEncoder(dataShards, parityShards, offset int) *FECEncoder {
|
||||
func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
||||
if dataShards <= 0 || parityShards <= 0 {
|
||||
return nil
|
||||
}
|
||||
fec := new(FECEncoder)
|
||||
fec := new(fecEncoder)
|
||||
fec.dataShards = dataShards
|
||||
fec.parityShards = parityShards
|
||||
fec.shardSize = dataShards + parityShards
|
||||
@@ -241,9 +241,9 @@ func newFECEncoder(dataShards, parityShards, offset int) *FECEncoder {
|
||||
return fec
|
||||
}
|
||||
|
||||
// Encode the packet, output parity shards if we have enough datashards
|
||||
// the content of returned parityshards will change in next Encode
|
||||
func (enc *FECEncoder) Encode(b []byte) (ps [][]byte) {
|
||||
// encode the packet, output parity shards if we have enough datashards
|
||||
// the content of returned parityshards will change in next encode
|
||||
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
enc.markData(b[enc.headerOffset:])
|
||||
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
|
||||
|
||||
@@ -290,13 +290,13 @@ func (enc *FECEncoder) Encode(b []byte) (ps [][]byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func (enc *FECEncoder) markData(data []byte) {
|
||||
func (enc *fecEncoder) markData(data []byte) {
|
||||
binary.LittleEndian.PutUint32(data, enc.next)
|
||||
binary.LittleEndian.PutUint16(data[4:], typeData)
|
||||
enc.next++
|
||||
}
|
||||
|
||||
func (enc *FECEncoder) markFEC(data []byte) {
|
||||
func (enc *fecEncoder) markFEC(data []byte) {
|
||||
binary.LittleEndian.PutUint32(data, enc.next)
|
||||
binary.LittleEndian.PutUint16(data[4:], typeFEC)
|
||||
enc.next = (enc.next + 1) % enc.paws
|
||||
Generated
Vendored
+22
-22
@@ -30,8 +30,8 @@ const (
|
||||
IKCP_PROBE_LIMIT = 120000 // up to 120 secs to probe window
|
||||
)
|
||||
|
||||
// Output is a closure which captures conn and calls conn.Write
|
||||
type Output func(buf []byte, size int)
|
||||
// output_callback is a prototype which ought capture conn and call conn.Write
|
||||
type output_callback func(buf []byte, size int)
|
||||
|
||||
/* encode 8 bits unsigned int */
|
||||
func ikcp_encode8u(p []byte, c byte) []byte {
|
||||
@@ -91,8 +91,8 @@ func _itimediff(later, earlier uint32) int32 {
|
||||
return (int32)(later - earlier)
|
||||
}
|
||||
|
||||
// Segment defines a KCP segment
|
||||
type Segment struct {
|
||||
// segment defines a KCP segment
|
||||
type segment struct {
|
||||
conv uint32
|
||||
cmd uint8
|
||||
frg uint8
|
||||
@@ -108,11 +108,11 @@ type Segment struct {
|
||||
}
|
||||
|
||||
// encode a segment into buffer
|
||||
func (seg *Segment) encode(ptr []byte) []byte {
|
||||
func (seg *segment) encode(ptr []byte) []byte {
|
||||
ptr = ikcp_encode32u(ptr, seg.conv)
|
||||
ptr = ikcp_encode8u(ptr, uint8(seg.cmd))
|
||||
ptr = ikcp_encode8u(ptr, uint8(seg.frg))
|
||||
ptr = ikcp_encode16u(ptr, uint16(seg.wnd))
|
||||
ptr = ikcp_encode8u(ptr, seg.cmd)
|
||||
ptr = ikcp_encode8u(ptr, seg.frg)
|
||||
ptr = ikcp_encode16u(ptr, seg.wnd)
|
||||
ptr = ikcp_encode32u(ptr, seg.ts)
|
||||
ptr = ikcp_encode32u(ptr, seg.sn)
|
||||
ptr = ikcp_encode32u(ptr, seg.una)
|
||||
@@ -137,15 +137,15 @@ type KCP struct {
|
||||
fastresend int32
|
||||
nocwnd, stream int32
|
||||
|
||||
snd_queue []Segment
|
||||
rcv_queue []Segment
|
||||
snd_buf []Segment
|
||||
rcv_buf []Segment
|
||||
snd_queue []segment
|
||||
rcv_queue []segment
|
||||
snd_buf []segment
|
||||
rcv_buf []segment
|
||||
|
||||
acklist []ackItem
|
||||
|
||||
buffer []byte
|
||||
output Output
|
||||
output output_callback
|
||||
}
|
||||
|
||||
type ackItem struct {
|
||||
@@ -155,7 +155,7 @@ type ackItem struct {
|
||||
|
||||
// NewKCP create a new kcp control object, 'conv' must equal in two endpoint
|
||||
// from the same connection.
|
||||
func NewKCP(conv uint32, output Output) *KCP {
|
||||
func NewKCP(conv uint32, output output_callback) *KCP {
|
||||
kcp := new(KCP)
|
||||
kcp.conv = conv
|
||||
kcp.snd_wnd = IKCP_WND_SND
|
||||
@@ -175,13 +175,13 @@ func NewKCP(conv uint32, output Output) *KCP {
|
||||
}
|
||||
|
||||
// newSegment creates a KCP segment
|
||||
func (kcp *KCP) newSegment(size int) (seg Segment) {
|
||||
func (kcp *KCP) newSegment(size int) (seg segment) {
|
||||
seg.data = xmitBuf.Get().([]byte)[:size]
|
||||
return
|
||||
}
|
||||
|
||||
// delSegment recycles a KCP segment
|
||||
func (kcp *KCP) delSegment(seg Segment) {
|
||||
func (kcp *KCP) delSegment(seg segment) {
|
||||
xmitBuf.Put(seg.data)
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ func (kcp *KCP) parse_ack(sn uint32) {
|
||||
if sn == seg.sn {
|
||||
kcp.delSegment(*seg)
|
||||
copy(kcp.snd_buf[k:], kcp.snd_buf[k+1:])
|
||||
kcp.snd_buf[len(kcp.snd_buf)-1] = Segment{}
|
||||
kcp.snd_buf[len(kcp.snd_buf)-1] = segment{}
|
||||
kcp.snd_buf = kcp.snd_buf[:len(kcp.snd_buf)-1]
|
||||
break
|
||||
}
|
||||
@@ -430,7 +430,7 @@ func (kcp *KCP) ack_push(sn, ts uint32) {
|
||||
kcp.acklist = append(kcp.acklist, ackItem{sn, ts})
|
||||
}
|
||||
|
||||
func (kcp *KCP) parse_data(newseg Segment) {
|
||||
func (kcp *KCP) parse_data(newseg segment) {
|
||||
sn := newseg.sn
|
||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) >= 0 ||
|
||||
_itimediff(sn, kcp.rcv_nxt) < 0 {
|
||||
@@ -458,7 +458,7 @@ func (kcp *KCP) parse_data(newseg Segment) {
|
||||
if insert_idx == n+1 {
|
||||
kcp.rcv_buf = append(kcp.rcv_buf, newseg)
|
||||
} else {
|
||||
kcp.rcv_buf = append(kcp.rcv_buf, Segment{})
|
||||
kcp.rcv_buf = append(kcp.rcv_buf, segment{})
|
||||
copy(kcp.rcv_buf[insert_idx+1:], kcp.rcv_buf[insert_idx:])
|
||||
kcp.rcv_buf[insert_idx] = newseg
|
||||
}
|
||||
@@ -625,7 +625,7 @@ func (kcp *KCP) wnd_unused() uint16 {
|
||||
|
||||
// flush pending data
|
||||
func (kcp *KCP) flush(ackOnly bool) {
|
||||
var seg Segment
|
||||
var seg segment
|
||||
seg.conv = kcp.conv
|
||||
seg.cmd = IKCP_CMD_ACK
|
||||
seg.wnd = kcp.wnd_unused()
|
||||
@@ -989,10 +989,10 @@ func (kcp *KCP) WaitSnd() int {
|
||||
}
|
||||
|
||||
// remove front n elements from queue
|
||||
func (kcp *KCP) remove_front(q []Segment, n int) []Segment {
|
||||
func (kcp *KCP) remove_front(q []segment, n int) []segment {
|
||||
newn := copy(q, q[n:])
|
||||
for i := newn; i < len(q); i++ {
|
||||
q[i] = Segment{} // manual set nil for GC
|
||||
q[i] = segment{} // manual set nil for GC
|
||||
}
|
||||
return q[:newn]
|
||||
}
|
||||
Generated
Vendored
+128
-114
@@ -3,6 +3,7 @@ package kcp
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net"
|
||||
@@ -54,9 +55,6 @@ var (
|
||||
// global packet buffer
|
||||
// shared among sending/receiving/FEC
|
||||
xmitBuf sync.Pool
|
||||
|
||||
// monotonic session id
|
||||
sid uint32
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -68,11 +66,11 @@ func init() {
|
||||
type (
|
||||
// UDPSession defines a KCP session implemented by UDP
|
||||
UDPSession struct {
|
||||
sid uint32 // session id(monotonic)
|
||||
conn net.PacketConn // the underlying packet connection
|
||||
kcp *KCP // KCP ARQ protocol
|
||||
l *Listener // point to the Listener if it's accepted by Listener
|
||||
block BlockCrypt // block encryption
|
||||
updaterIdx int // record slice index in updater
|
||||
conn net.PacketConn // the underlying packet connection
|
||||
kcp *KCP // KCP ARQ protocol
|
||||
l *Listener // point to the Listener if it's accepted by Listener
|
||||
block BlockCrypt // block encryption
|
||||
|
||||
// kcp receiving is based on packets
|
||||
// recvbuf turns packets into stream
|
||||
@@ -82,22 +80,23 @@ type (
|
||||
ext []byte
|
||||
|
||||
// FEC
|
||||
fecDecoder *FECDecoder
|
||||
fecEncoder *FECEncoder
|
||||
fecDecoder *fecDecoder
|
||||
fecEncoder *fecEncoder
|
||||
|
||||
// settings
|
||||
remote net.Addr // remote peer address
|
||||
rd time.Time // read deadline
|
||||
wd time.Time // write deadline
|
||||
headerSize int // the overall header size added before KCP frame
|
||||
updateInterval time.Duration // interval in seconds to call kcp.flush()
|
||||
ackNoDelay bool // send ack immediately for each incoming packet
|
||||
writeDelay bool // delay kcp.flush() for Write() for bulk transfer
|
||||
remote net.Addr // remote peer address
|
||||
rd time.Time // read deadline
|
||||
wd time.Time // write deadline
|
||||
headerSize int // the overall header size added before KCP frame
|
||||
ackNoDelay bool // send ack immediately for each incoming packet
|
||||
writeDelay bool // delay kcp.flush() for Write() for bulk transfer
|
||||
dup int // duplicate udp packets
|
||||
|
||||
// notifications
|
||||
die chan struct{} // notify session has Closed
|
||||
chReadEvent chan struct{} // notify Read() can be called without blocking
|
||||
chWriteEvent chan struct{} // notify Write() can be called without blocking
|
||||
chErrorEvent chan error // notify Read() have an error
|
||||
|
||||
isClosed bool // flag the session has Closed
|
||||
mu sync.Mutex
|
||||
@@ -115,10 +114,10 @@ type (
|
||||
// newUDPSession create a new udp session for client or server
|
||||
func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn net.PacketConn, remote net.Addr, block BlockCrypt) *UDPSession {
|
||||
sess := new(UDPSession)
|
||||
sess.sid = atomic.AddUint32(&sid, 1)
|
||||
sess.die = make(chan struct{})
|
||||
sess.chReadEvent = make(chan struct{}, 1)
|
||||
sess.chWriteEvent = make(chan struct{}, 1)
|
||||
sess.chErrorEvent = make(chan error, 1)
|
||||
sess.remote = remote
|
||||
sess.conn = conn
|
||||
sess.l = l
|
||||
@@ -232,6 +231,11 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
case <-s.chReadEvent:
|
||||
case <-c:
|
||||
case <-s.die:
|
||||
case err = <-s.chErrorEvent:
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
if timeout != nil {
|
||||
@@ -299,9 +303,11 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
|
||||
// Close closes the connection.
|
||||
func (s *UDPSession) Close() error {
|
||||
// remove this session from updater & listener(if necessary)
|
||||
updater.removeSession(s)
|
||||
if s.l != nil { // notify listener
|
||||
s.l.closeSession(s.remote)
|
||||
key := fmt.Sprintf("%s/%d", s.remote.String(), s.kcp.conv)
|
||||
s.l.closeSession(key)
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
@@ -393,12 +399,19 @@ func (s *UDPSession) SetACKNoDelay(nodelay bool) {
|
||||
s.ackNoDelay = nodelay
|
||||
}
|
||||
|
||||
// SetDUP duplicates udp packets for kcp output, for testing purpose only
|
||||
func (s *UDPSession) SetDUP(dup int) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.dup = dup
|
||||
}
|
||||
|
||||
// SetNoDelay calls nodelay() of kcp
|
||||
// https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration
|
||||
func (s *UDPSession) SetNoDelay(nodelay, interval, resend, nc int) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.kcp.NoDelay(nodelay, interval, resend, nc)
|
||||
s.updateInterval = time.Duration(interval) * time.Millisecond
|
||||
}
|
||||
|
||||
// SetDSCP sets the 6bit DSCP field of IP header, no effect if it's accepted from Listener
|
||||
@@ -406,8 +419,8 @@ func (s *UDPSession) SetDSCP(dscp int) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.l == nil {
|
||||
if nc, ok := s.conn.(*ConnectedUDPConn); ok {
|
||||
return ipv4.NewConn(nc.Conn).SetTOS(dscp << 2)
|
||||
if nc, ok := s.conn.(*connectedUDPConn); ok {
|
||||
return ipv4.NewConn(nc.UDPConn).SetTOS(dscp << 2)
|
||||
} else if nc, ok := s.conn.(net.Conn); ok {
|
||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
||||
}
|
||||
@@ -449,51 +462,47 @@ func (s *UDPSession) SetWriteBuffer(bytes int) error {
|
||||
func (s *UDPSession) output(buf []byte) {
|
||||
var ecc [][]byte
|
||||
|
||||
// extend buf's header space
|
||||
// 0. extend buf's header space(if necessary)
|
||||
ext := buf
|
||||
if s.headerSize > 0 {
|
||||
ext = s.ext[:s.headerSize+len(buf)]
|
||||
copy(ext[s.headerSize:], buf)
|
||||
}
|
||||
|
||||
// FEC stage
|
||||
// 1. FEC encoding
|
||||
if s.fecEncoder != nil {
|
||||
ecc = s.fecEncoder.Encode(ext)
|
||||
ecc = s.fecEncoder.encode(ext)
|
||||
}
|
||||
|
||||
// encryption stage
|
||||
// 2&3. crc32 & encryption
|
||||
if s.block != nil {
|
||||
io.ReadFull(rand.Reader, ext[:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
||||
s.block.Encrypt(ext, ext)
|
||||
|
||||
if ecc != nil {
|
||||
for k := range ecc {
|
||||
io.ReadFull(rand.Reader, ecc[k][:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
|
||||
s.block.Encrypt(ecc[k], ecc[k])
|
||||
}
|
||||
for k := range ecc {
|
||||
io.ReadFull(rand.Reader, ecc[k][:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
|
||||
s.block.Encrypt(ecc[k], ecc[k])
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo kernel
|
||||
// 4. WriteTo kernel
|
||||
nbytes := 0
|
||||
npkts := 0
|
||||
// if mrand.Intn(100) < 50 {
|
||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
for i := 0; i < s.dup+1; i++ {
|
||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
if ecc != nil {
|
||||
for k := range ecc {
|
||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
}
|
||||
for k := range ecc {
|
||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
}
|
||||
}
|
||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||
@@ -507,15 +516,13 @@ func (s *UDPSession) update() (interval time.Duration) {
|
||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
interval = s.updateInterval
|
||||
interval = time.Duration(s.kcp.interval) * time.Millisecond
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// GetConv gets conversation id of a session
|
||||
func (s *UDPSession) GetConv() uint32 {
|
||||
return s.kcp.conv
|
||||
}
|
||||
func (s *UDPSession) GetConv() uint32 { return s.kcp.conv }
|
||||
|
||||
func (s *UDPSession) notifyReadEvent() {
|
||||
select {
|
||||
@@ -548,22 +555,21 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
fecParityShards++
|
||||
}
|
||||
|
||||
if recovers := s.fecDecoder.Decode(f); recovers != nil {
|
||||
for _, r := range recovers {
|
||||
if len(r) >= 2 { // must be larger than 2bytes
|
||||
sz := binary.LittleEndian.Uint16(r)
|
||||
if int(sz) <= len(r) && sz >= 2 {
|
||||
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
||||
fecRecovered++
|
||||
} else {
|
||||
kcpInErrors++
|
||||
}
|
||||
recovers := s.fecDecoder.decode(f)
|
||||
for _, r := range recovers {
|
||||
if len(r) >= 2 { // must be larger than 2bytes
|
||||
sz := binary.LittleEndian.Uint16(r)
|
||||
if int(sz) <= len(r) && sz >= 2 {
|
||||
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
||||
fecRecovered++
|
||||
} else {
|
||||
fecErrs++
|
||||
kcpInErrors++
|
||||
}
|
||||
} else {
|
||||
fecErrs++
|
||||
}
|
||||
} else {
|
||||
fecErrs++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -601,7 +607,7 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPSession) receiver(ch chan []byte) {
|
||||
func (s *UDPSession) receiver(ch chan<- []byte) {
|
||||
for {
|
||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
||||
if n, _, err := s.conn.ReadFrom(data); err == nil && n >= s.headerSize+IKCP_OVERHEAD {
|
||||
@@ -611,6 +617,7 @@ func (s *UDPSession) receiver(ch chan []byte) {
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
s.chErrorEvent <- err
|
||||
return
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
@@ -658,12 +665,12 @@ type (
|
||||
block BlockCrypt // block encryption
|
||||
dataShards int // FEC data shard
|
||||
parityShards int // FEC parity shard
|
||||
fecDecoder *FECDecoder // FEC mock initialization
|
||||
fecDecoder *fecDecoder // FEC mock initialization
|
||||
conn net.PacketConn // the underlying packet connection
|
||||
|
||||
sessions map[string]*UDPSession // all sessions accepted by this Listener
|
||||
chAccepts chan *UDPSession // Listen() backlog
|
||||
chSessionClosed chan net.Addr // session close queue
|
||||
chSessionClosed chan string // session close queue
|
||||
headerSize int // the overall header size added before KCP frame
|
||||
die chan struct{} // notify the listener has closed
|
||||
rd atomic.Value // read deadline for Accept()
|
||||
@@ -679,6 +686,10 @@ type (
|
||||
|
||||
// monitor incoming data for all connections of server
|
||||
func (l *Listener) monitor() {
|
||||
// cache last session
|
||||
var lastKey string
|
||||
var lastSession *UDPSession
|
||||
|
||||
chPacket := make(chan inPacket, qlen)
|
||||
go l.receiver(chPacket)
|
||||
for {
|
||||
@@ -703,45 +714,60 @@ func (l *Listener) monitor() {
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
addr := from.String()
|
||||
s, ok := l.sessions[addr]
|
||||
if !ok { // new session
|
||||
if len(l.chAccepts) < cap(l.chAccepts) { // do not let new session overwhelm accept queue
|
||||
var conv uint32
|
||||
convValid := false
|
||||
if l.fecDecoder != nil {
|
||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||
if isfec == typeData {
|
||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||
convValid = true
|
||||
}
|
||||
} else {
|
||||
conv = binary.LittleEndian.Uint32(data)
|
||||
convValid = true
|
||||
}
|
||||
|
||||
if convValid {
|
||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
||||
s.kcpInput(data)
|
||||
l.sessions[addr] = s
|
||||
l.chAccepts <- s
|
||||
}
|
||||
var conv uint32
|
||||
convValid := false
|
||||
if l.fecDecoder != nil {
|
||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||
if isfec == typeData {
|
||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||
convValid = true
|
||||
}
|
||||
} else {
|
||||
s.kcpInput(data)
|
||||
conv = binary.LittleEndian.Uint32(data)
|
||||
convValid = true
|
||||
}
|
||||
|
||||
if convValid {
|
||||
addr := from.String()
|
||||
key := fmt.Sprintf("%s/%d", addr, conv)
|
||||
var s *UDPSession
|
||||
var ok bool
|
||||
|
||||
// packets received from an address always come in batch.
|
||||
// cache the session for next packet, without querying map.
|
||||
if key == lastKey {
|
||||
s, ok = lastSession, true
|
||||
} else if s, ok = l.sessions[key]; ok {
|
||||
lastSession = s
|
||||
lastKey = addr
|
||||
}
|
||||
|
||||
if !ok { // new session
|
||||
if len(l.chAccepts) < cap(l.chAccepts) && len(l.sessions) < 4096 { // do not let new session overwhelm accept queue and connection count
|
||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
||||
s.kcpInput(data)
|
||||
l.sessions[key] = s
|
||||
l.chAccepts <- s
|
||||
}
|
||||
} else {
|
||||
s.kcpInput(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmitBuf.Put(raw)
|
||||
case deadlink := <-l.chSessionClosed:
|
||||
delete(l.sessions, deadlink.String())
|
||||
case key := <-l.chSessionClosed:
|
||||
if key == lastKey {
|
||||
lastKey = ""
|
||||
}
|
||||
delete(l.sessions, key)
|
||||
case <-l.die:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) receiver(ch chan inPacket) {
|
||||
func (l *Listener) receiver(ch chan<- inPacket) {
|
||||
for {
|
||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
||||
if n, from, err := l.conn.ReadFrom(data); err == nil && n >= l.headerSize+IKCP_OVERHEAD {
|
||||
@@ -830,9 +856,9 @@ func (l *Listener) Close() error {
|
||||
}
|
||||
|
||||
// closeSession notify the listener that a session has closed
|
||||
func (l *Listener) closeSession(remote net.Addr) bool {
|
||||
func (l *Listener) closeSession(key string) bool {
|
||||
select {
|
||||
case l.chSessionClosed <- remote:
|
||||
case l.chSessionClosed <- key:
|
||||
return true
|
||||
case <-l.die:
|
||||
return false
|
||||
@@ -840,14 +866,10 @@ func (l *Listener) closeSession(remote net.Addr) bool {
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.conn.LocalAddr()
|
||||
}
|
||||
func (l *Listener) Addr() net.Addr { return l.conn.LocalAddr() }
|
||||
|
||||
// Listen listens for incoming KCP packets addressed to the local address laddr on the network "udp",
|
||||
func Listen(laddr string) (net.Listener, error) {
|
||||
return ListenWithOptions(laddr, nil, 0, 0)
|
||||
}
|
||||
func Listen(laddr string) (net.Listener, error) { return ListenWithOptions(laddr, nil, 0, 0) }
|
||||
|
||||
// ListenWithOptions listens for incoming KCP packets addressed to the local address laddr on the network "udp" with packet encryption,
|
||||
// dataShards, parityShards defines Reed-Solomon Erasure Coding parameters
|
||||
@@ -870,7 +892,7 @@ func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
|
||||
l.conn = conn
|
||||
l.sessions = make(map[string]*UDPSession)
|
||||
l.chAccepts = make(chan *UDPSession, acceptBacklog)
|
||||
l.chSessionClosed = make(chan net.Addr)
|
||||
l.chSessionClosed = make(chan string)
|
||||
l.die = make(chan struct{})
|
||||
l.dataShards = dataShards
|
||||
l.parityShards = parityShards
|
||||
@@ -890,9 +912,7 @@ func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketCo
|
||||
}
|
||||
|
||||
// Dial connects to the remote address "raddr" on the network "udp"
|
||||
func Dial(raddr string) (net.Conn, error) {
|
||||
return DialWithOptions(raddr, nil, 0, 0)
|
||||
}
|
||||
func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0, 0) }
|
||||
|
||||
// DialWithOptions connects to the remote address "raddr" on the network "udp" with packet encryption
|
||||
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
|
||||
@@ -906,7 +926,7 @@ func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards in
|
||||
return nil, errors.Wrap(err, "net.DialUDP")
|
||||
}
|
||||
|
||||
return NewConn(raddr, block, dataShards, parityShards, &ConnectedUDPConn{udpconn, udpconn})
|
||||
return NewConn(raddr, block, dataShards, parityShards, &connectedUDPConn{udpconn})
|
||||
}
|
||||
|
||||
// NewConn establishes a session and talks KCP protocol over a packet connection.
|
||||
@@ -921,19 +941,13 @@ func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn
|
||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, udpaddr, block), nil
|
||||
}
|
||||
|
||||
func currentMs() uint32 {
|
||||
return uint32(time.Now().UnixNano() / int64(time.Millisecond))
|
||||
}
|
||||
// returns current time in milliseconds
|
||||
func currentMs() uint32 { return uint32(time.Now().UnixNano() / int64(time.Millisecond)) }
|
||||
|
||||
// ConnectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
||||
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
||||
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
||||
// used for connections that were produced by a net.Dial* call.
|
||||
type ConnectedUDPConn struct {
|
||||
*net.UDPConn
|
||||
Conn net.Conn // underlying connection if any
|
||||
}
|
||||
type connectedUDPConn struct{ *net.UDPConn }
|
||||
|
||||
// WriteTo redirects all writes to the Write syscall, which is 4 times faster.
|
||||
func (c *ConnectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
return c.Write(b)
|
||||
}
|
||||
func (c *connectedUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { return c.Write(b) }
|
||||
Generated
Vendored
Generated
Vendored
+11
-13
@@ -15,15 +15,13 @@ func init() {
|
||||
|
||||
// entry contains a session update info
|
||||
type entry struct {
|
||||
sid uint32
|
||||
ts time.Time
|
||||
s *UDPSession
|
||||
ts time.Time
|
||||
s *UDPSession
|
||||
}
|
||||
|
||||
// a global heap managed kcp.flush() caller
|
||||
type updateHeap struct {
|
||||
entries []entry
|
||||
indices map[uint32]int
|
||||
mu sync.Mutex
|
||||
chWakeUp chan struct{}
|
||||
}
|
||||
@@ -32,41 +30,40 @@ func (h *updateHeap) Len() int { return len(h.entries) }
|
||||
func (h *updateHeap) Less(i, j int) bool { return h.entries[i].ts.Before(h.entries[j].ts) }
|
||||
func (h *updateHeap) Swap(i, j int) {
|
||||
h.entries[i], h.entries[j] = h.entries[j], h.entries[i]
|
||||
h.indices[h.entries[i].sid] = i
|
||||
h.indices[h.entries[j].sid] = j
|
||||
h.entries[i].s.updaterIdx = i
|
||||
h.entries[j].s.updaterIdx = j
|
||||
}
|
||||
|
||||
func (h *updateHeap) Push(x interface{}) {
|
||||
h.entries = append(h.entries, x.(entry))
|
||||
n := len(h.entries)
|
||||
h.indices[h.entries[n-1].sid] = n - 1
|
||||
h.entries[n-1].s.updaterIdx = n - 1
|
||||
}
|
||||
|
||||
func (h *updateHeap) Pop() interface{} {
|
||||
n := len(h.entries)
|
||||
x := h.entries[n-1]
|
||||
h.entries[n-1].s.updaterIdx = -1
|
||||
h.entries[n-1] = entry{} // manual set nil for GC
|
||||
h.entries = h.entries[0 : n-1]
|
||||
delete(h.indices, x.sid)
|
||||
return x
|
||||
}
|
||||
|
||||
func (h *updateHeap) init() {
|
||||
h.indices = make(map[uint32]int)
|
||||
h.chWakeUp = make(chan struct{}, 1)
|
||||
}
|
||||
|
||||
func (h *updateHeap) addSession(s *UDPSession) {
|
||||
h.mu.Lock()
|
||||
heap.Push(h, entry{s.sid, time.Now(), s})
|
||||
heap.Push(h, entry{time.Now(), s})
|
||||
h.mu.Unlock()
|
||||
h.wakeup()
|
||||
}
|
||||
|
||||
func (h *updateHeap) removeSession(s *UDPSession) {
|
||||
h.mu.Lock()
|
||||
if idx, ok := h.indices[s.sid]; ok {
|
||||
heap.Remove(h, idx)
|
||||
if s.updaterIdx != -1 {
|
||||
heap.Remove(h, s.updaterIdx)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
@@ -99,7 +96,8 @@ func (h *updateHeap) updateTask() {
|
||||
break
|
||||
}
|
||||
}
|
||||
if h.Len() > 0 {
|
||||
|
||||
if hlen > 0 {
|
||||
timer = time.After(h.entries[0].ts.Sub(now))
|
||||
}
|
||||
h.mu.Unlock()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user