mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-03 19:39:20 -05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2641062c17 | ||
|
|
95c738ea28 | ||
|
|
562d2f67a6 | ||
|
|
ba6aff4a1b | ||
|
|
bb23e3940e | ||
|
|
94e4370c7e | ||
|
|
38d28c3f4a | ||
|
|
f60b424d70 | ||
|
|
a1a91d5ef4 | ||
|
|
bfb48b5dde | ||
|
|
2860813a8e | ||
|
|
72538e350d | ||
|
|
59f3d1445f | ||
|
|
0b88cf1d03 | ||
|
|
56e2ba29d0 | ||
|
|
6ec9b84674 | ||
|
|
afd15392b1 | ||
|
|
ae4cc94a9d | ||
|
|
3f9b75b7b3 | ||
|
|
ec2b097313 | ||
|
|
caaab462bc | ||
|
|
da413b823b | ||
|
|
14937e7dd2 | ||
|
|
3418497f3d | ||
|
|
e408f1061a | ||
|
|
c08fe4e2c5 |
25
build.go
25
build.go
@@ -95,6 +95,7 @@ var targets = map[string]target{
|
||||
{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
|
||||
{src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
|
||||
{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
|
||||
{src: "etc/firewall-ufw/syncthing", dst: "deb/etc/ufw/applications.d/syncthing", perm: 0644},
|
||||
},
|
||||
},
|
||||
"stdiscosrv": {
|
||||
@@ -525,6 +526,8 @@ func buildDeb(target target) {
|
||||
}
|
||||
|
||||
func buildSnap(target target) {
|
||||
os.RemoveAll("snap")
|
||||
|
||||
tmpl, err := template.ParseFiles("snapcraft.yaml.template")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -534,7 +537,27 @@ func buildSnap(target target) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = tmpl.Execute(f, map[string]string{"Version": version})
|
||||
|
||||
snaparch := goarch
|
||||
if snaparch == "armhf" {
|
||||
goarch = "arm"
|
||||
}
|
||||
snapver := version
|
||||
if strings.HasPrefix(snapver, "v") {
|
||||
snapver = snapver[1:]
|
||||
}
|
||||
snapgrade := "devel"
|
||||
if matched, _ := regexp.MatchString(`^\d+\.\d+\.\d+$`, snapver); matched {
|
||||
snapgrade = "stable"
|
||||
}
|
||||
err = tmpl.Execute(f, map[string]string{
|
||||
"Version": snapver,
|
||||
"Architecture": snaparch,
|
||||
"Grade": snapgrade,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
runPrint("snapcraft", "clean")
|
||||
build(target, []string{"noupgrade"})
|
||||
runPrint("snapcraft")
|
||||
|
||||
@@ -172,7 +172,7 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
|
||||
if debug {
|
||||
log.Println("Sent invitation from", id, "to", requestedPeer)
|
||||
}
|
||||
default:
|
||||
case <-time.After(time.Second):
|
||||
if debug {
|
||||
log.Println("Could not send invitation from", id, "to", requestedPeer, "as peer disconnected")
|
||||
}
|
||||
@@ -204,7 +204,6 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
|
||||
if debug {
|
||||
log.Printf("Closing connection %s: %s", id, err)
|
||||
}
|
||||
close(outbox)
|
||||
|
||||
// Potentially closing a second time.
|
||||
conn.Close()
|
||||
@@ -260,10 +259,6 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
|
||||
conn.Close()
|
||||
|
||||
case msg := <-outbox:
|
||||
if msg == nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if debug {
|
||||
log.Printf("Sending message %T to %s", msg, id)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -120,6 +121,21 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
laddr, err := net.ResolveTCPAddr(proto, listen)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if laddr.IP != nil && !laddr.IP.IsUnspecified() {
|
||||
laddr.Port = 0
|
||||
transport, ok := http.DefaultTransport.(*http.Transport)
|
||||
if ok {
|
||||
transport.DialContext = (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
LocalAddr: laddr,
|
||||
}).DialContext
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(LongVersion)
|
||||
|
||||
maxDescriptors, err := osutil.MaximizeOpenFileLimit()
|
||||
|
||||
@@ -99,7 +99,7 @@ type modelIntf interface {
|
||||
|
||||
type configIntf interface {
|
||||
GUI() config.GUIConfiguration
|
||||
Raw() config.Configuration
|
||||
RawCopy() config.Configuration
|
||||
Options() config.OptionsConfiguration
|
||||
Replace(cfg config.Configuration) error
|
||||
Subscribe(c config.Committer)
|
||||
@@ -736,7 +736,7 @@ func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, s.cfg.Raw())
|
||||
sendJSON(w, s.cfg.RawCopy())
|
||||
}
|
||||
|
||||
func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -47,6 +47,8 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
|
||||
_ "net/http/pprof" // Need to import this to support STPROFILER.
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -672,7 +674,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Raw().OriginalVersion == 15 {
|
||||
if cfg.RawCopy().OriginalVersion == 15 {
|
||||
// The config version 15->16 migration is about handling ignores and
|
||||
// delta indexes and requires that we drop existing indexes that
|
||||
// have been incorrectly ignore filtered.
|
||||
@@ -871,7 +873,7 @@ func loadOrCreateConfig() *config.Wrapper {
|
||||
l.Fatalln("Config:", err)
|
||||
}
|
||||
|
||||
if cfg.Raw().OriginalVersion != config.CurrentVersion {
|
||||
if cfg.RawCopy().OriginalVersion != config.CurrentVersion {
|
||||
err = archiveAndSaveConfig(cfg)
|
||||
if err != nil {
|
||||
l.Fatalln("Config archive:", err)
|
||||
@@ -883,7 +885,7 @@ func loadOrCreateConfig() *config.Wrapper {
|
||||
|
||||
func archiveAndSaveConfig(cfg *config.Wrapper) error {
|
||||
// Copy the existing config to an archive copy
|
||||
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.Raw().OriginalVersion)
|
||||
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion)
|
||||
l.Infoln("Archiving a copy of old config file format at:", archivePath)
|
||||
if err := copyFile(cfg.ConfigPath(), archivePath); err != nil {
|
||||
return err
|
||||
|
||||
@@ -23,7 +23,7 @@ func (c *mockedConfig) ListenAddresses() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockedConfig) Raw() config.Configuration {
|
||||
func (c *mockedConfig) RawCopy() config.Configuration {
|
||||
return config.Configuration{}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func newUsageReportingManager(cfg *config.Wrapper, m *model.Model) *usageReporti
|
||||
}
|
||||
|
||||
// Start UR if it's enabled.
|
||||
mgr.CommitConfiguration(config.Configuration{}, cfg.Raw())
|
||||
mgr.CommitConfiguration(config.Configuration{}, cfg.RawCopy())
|
||||
|
||||
// Listen to future config changes so that we can start and stop as
|
||||
// appropriate.
|
||||
|
||||
22
etc/firewall-ufw/README.md
Normal file
22
etc/firewall-ufw/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Uncomplicated FireWall application preset
|
||||
===================
|
||||
Installation
|
||||
-----------
|
||||
**Please note:** When you installed syncthing using the official deb package, you can skip the copying.
|
||||
|
||||
Copy the file `syncthing` to your ufw applications directory usually located at `/etc/ufw/applications.d/`. (root permissions required).
|
||||
|
||||
In a terminal run
|
||||
```
|
||||
sudo ufw app update syncthing
|
||||
```
|
||||
to load the preset.
|
||||
To allow the syncthing ports, run
|
||||
```
|
||||
sudo ufw allow syncthing
|
||||
```
|
||||
You can then verify the opened ports
|
||||
```
|
||||
sudo ufw status verbose
|
||||
```
|
||||
|
||||
4
etc/firewall-ufw/syncthing
Normal file
4
etc/firewall-ufw/syncthing
Normal file
@@ -0,0 +1,4 @@
|
||||
[syncthing]
|
||||
title=Syncthing
|
||||
description=Syncthing file synchronisation
|
||||
ports=22000/tcp|21027/udp
|
||||
@@ -31,7 +31,7 @@
|
||||
"Command": "Befehl",
|
||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
|
||||
"Compression": "Komprimierung",
|
||||
"Configured": "Configured",
|
||||
"Configured": "Konfiguriert",
|
||||
"Connection Error": "Verbindungsfehler",
|
||||
"Connection Type": "Verbindungstyp",
|
||||
"Copied from elsewhere": "Von anderer Quelle kopiert",
|
||||
@@ -45,7 +45,7 @@
|
||||
"Device Name": "Gerätename",
|
||||
"Devices": "Geräte",
|
||||
"Disconnected": "Getrennt",
|
||||
"Discovered": "Discovered",
|
||||
"Discovered": "Ermittelt",
|
||||
"Discovery": "Gerätesuche",
|
||||
"Documentation": "Dokumentation",
|
||||
"Download Rate": "Download",
|
||||
@@ -135,7 +135,7 @@
|
||||
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
|
||||
"RAM Utilization": "RAM Auslastung",
|
||||
"Random": "Zufall",
|
||||
"Reduced by ignore patterns": "Reduced by ignore patterns",
|
||||
"Reduced by ignore patterns": "Beschränkt durch Ignoriermuster",
|
||||
"Release Notes": "Veröffentlichungsnotizen",
|
||||
"Remote Devices": "Remote-Geräte",
|
||||
"Remove": "Entfernen",
|
||||
@@ -231,8 +231,8 @@
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
|
||||
"days": "Tage",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"directories": "Verzeichnisse",
|
||||
"files": "Dateien",
|
||||
"full documentation": "Komplette Dokumentation",
|
||||
"items": "Objekte",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen.",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"Connection Type": "Τύπος Σύνδεσης",
|
||||
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
|
||||
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 οι παρακάτω Συνεισφέροντες:",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 για τους παρακάτω συνεισφέροντες:",
|
||||
"Danger!": "Προσοχή!",
|
||||
"Deleted": "Διαγραμμένα",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Η συσκευή \"{{name}}\" ({{device}} στη διεύθυνση {{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής;",
|
||||
@@ -146,7 +146,7 @@
|
||||
"Restart": "Επανεκκίνηση",
|
||||
"Restart Needed": "Απαιτείται επανεκκίνηση",
|
||||
"Restarting": "Επανεκκίνηση",
|
||||
"Resume": "Συνέχιση",
|
||||
"Resume": "Συνέχεια",
|
||||
"Reused": "Χρησιμοποιήθηκε ξανά",
|
||||
"Save": "Αποθήκευση",
|
||||
"Scan Time Remaining": "Εναπομείναντας χρόνος για τον έλεγχο ",
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
|
||||
"RAM Utilization": "Utilización de RAM",
|
||||
"Random": "Aleatorio",
|
||||
"Reduced by ignore patterns": "Restringido por patrones de exclusión",
|
||||
"Reduced by ignore patterns": "(Restringido por patrones de exclusión)",
|
||||
"Release Notes": "Notas de lanzamiento",
|
||||
"Remote Devices": "Otros dispositivos",
|
||||
"Remove": "Eliminar",
|
||||
|
||||
240
gui/default/assets/lang/lang-eu.json
Normal file
240
gui/default/assets/lang/lang-eu.json
Normal file
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"A device with that ID is already added.": "Id horrekin beste talde bat gehitu da.",
|
||||
"A negative number of days doesn't make sense.": "Hemen 0 edo zenbaki positiboa ,mesedez.",
|
||||
"A new major version may not be compatible with previous versions.": "Aldaketa garrantzitsuak dituen bertsio berria beharbada ez da bateragarria izango bertsio zaharragoekin.",
|
||||
"API Key": "API giltza",
|
||||
"About": "Egoki",
|
||||
"Actions": "Egintzak",
|
||||
"Add": "Gaineratu",
|
||||
"Add Device": "Gaineratu makina",
|
||||
"Add Folder": "Gaineratu partekatze",
|
||||
"Add Remote Device": "Gaineratu makinan izan",
|
||||
"Add new folder?": "Gaineratu berri partekatze ?",
|
||||
"Address": "Helbide",
|
||||
"Addresses": "Helbidek",
|
||||
"Advanced": "Aditu",
|
||||
"Advanced Configuration": "Konfigurazio aintzinatua",
|
||||
"Advanced settings": "Ezarpen aurreratuak",
|
||||
"All Data": "Datu guziak",
|
||||
"Allow Anonymous Usage Reporting?": "Izenik gabeko erabiltze erreportak baimendu",
|
||||
"Alphabetic": "Alfabetikoa",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Kanpoko kontrolagailu batek bertsioak erabiltzen ditu. Fitxeroak errepertorio sinkronizatutik desagertaraztea berari doakio.",
|
||||
"Anonymous Usage Reporting": "Izenik gabeko erabiltze erreportak",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Sarrarazle batean sartua izanen edozein tresna dena huntan ere izanen da.",
|
||||
"Automatic upgrades": "Aktualizatze automatikoak",
|
||||
"Be careful!": "Kasu!",
|
||||
"Bugs": "Akatsek",
|
||||
"CPU Utilization": "Prozesadore erabiltze",
|
||||
"Changelog": "Bertsio historia",
|
||||
"Clean out after": "Garbi …. epearen ondotik",
|
||||
"Close": "Ezeztatu",
|
||||
"Command": "Kontrolagailua",
|
||||
"Comment, when used at the start of a line": "Komentarioa, lerro baten hastean delarik",
|
||||
"Compression": "Trinkotze",
|
||||
"Configured": "Konfiguratua",
|
||||
"Connection Error": "Konexio hutsa",
|
||||
"Connection Type": "Konexion mota",
|
||||
"Copied from elsewhere": "Beste nunbaitik kopiatua",
|
||||
"Copied from original": "Kopiatua jatorrizkoatik",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright 2014-2016, ekarle hauk:",
|
||||
"Danger!": "Lanjer !",
|
||||
"Deleted": "Ezeztatu",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Makina \"{{name}}\" ({{device}} izan {{address}}) konektatu nahi du. Gaineratu berri makina ?",
|
||||
"Device ID": "Makina ID",
|
||||
"Device Identification": "Tresnaren identifikazioa",
|
||||
"Device Name": "Makina izen",
|
||||
"Devices": "Makinak",
|
||||
"Disconnected": "Desloturik",
|
||||
"Discovered": "Ziloa",
|
||||
"Discovery": "Aurkikuntza",
|
||||
"Documentation": "Dokumentazio",
|
||||
"Download Rate": "Deskargatze emari",
|
||||
"Downloaded": "Telekargatua",
|
||||
"Downloading": "Deskargatua",
|
||||
"Edit": "Aldatu",
|
||||
"Editing": "Aldaketa",
|
||||
"Enable NAT traversal": "Ahalbidetu NAT",
|
||||
"Enable Relaying": "Ahalbidetu lekua hartu",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": " (\"tcp://ip:port\" ou \"tcp://nom:port\") zuzenbideak sar, krakotx batez separatuak edo bestenaz \"dynamic\", zuzenbidearen xekatze automatikoa aktibatzeko\nTu peux traduire nom et port dans la parenthèse, c'est pas des variables du programme, juste du texte explicatif",
|
||||
"Enter ignore patterns, one per line.": "Ezkluzio filtroak sar, lerro batean bakar bat",
|
||||
"Error": "Huts",
|
||||
"External File Versioning": "Fitxero bertsioen kanpoko kudeaketa",
|
||||
"Failed Items": "Fitxategiken huts",
|
||||
"File Pull Order": "Fitxategiak irekitzeko agindua",
|
||||
"File Versioning": "Artxiboak babesteko metodoa",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Aldaketa bilaketetan fitxero baimenen bitak ez dira kontuan hartuko. Fitxero FAT sistimetan erabilia.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": ".stbersioak azpi karpetan lekutuko dira fitxeroak, Syncthing-ek aldatu edo ezeztatuko dituelarik. Beren helbide errelatiboak hor berean berriz sortuak izanen dira, behar balin bada",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": ".stbersioak azpi karpetan lekutuko eta ordu-markatuko dira fitxeroak, Syncthing-ek aldatu edo ezeztatuko dituelarik. Beren helbide errelatiboak hor berean berriz sortuak izanen dira, behar balin bada",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Beste tresnetan eginak izanen diren aldaketetatik zainduak izanen dira fitxeroak; haatik, tresna huntan egindako aldaketak besteeri hedatuak izanen dira",
|
||||
"Folder": "Partekatze",
|
||||
"Folder ID": "Partekatze ID",
|
||||
"Folder Label": "Partekatze izengoiti",
|
||||
"Folder Path": "Partekatze bidexka",
|
||||
"Folder Type": "Partekatze mota",
|
||||
"Folders": "Partekatzek",
|
||||
"GUI": "Interfaze grafiko",
|
||||
"GUI Authentication Password": "Interfaze grafiko pasahitz",
|
||||
"GUI Authentication User": "Interfaze grafiko erabiltzaile",
|
||||
"GUI Listen Addresses": "Interfaze grafiko helbide",
|
||||
"Generate": "Sortu",
|
||||
"Global Discovery": "Aurkikuntza oso",
|
||||
"Global Discovery Servers": "Aurkikuntza oso zerbitzarik",
|
||||
"Global State": "Oso egoera",
|
||||
"Help": "Aiuta",
|
||||
"Home page": "Errezibitze",
|
||||
"Ignore": "Baztertu",
|
||||
"Ignore Patterns": "Bazterketak arauk",
|
||||
"Ignore Permissions": "Baztertu baimenek",
|
||||
"Incoming Rate Limit (KiB/s)": "Deskargatze emari gehieneko (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Langer !!!!",
|
||||
"Introducer": "Abiarazle",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Emana izan den baldintza alderantziz eman (i.e ez baztertu)",
|
||||
"Keep Versions": "Gorde bertsioak",
|
||||
"Largest First": "Handienak lehenik",
|
||||
"Last File Received": " Fitxategi azken eskuratu",
|
||||
"Last Scan": "Azterketa azken",
|
||||
"Last seen": "Azken agerraldia",
|
||||
"Later": "Berantago",
|
||||
"Listeners": "Entzungailuak",
|
||||
"Local Discovery": "Lekuko aurkikuntza",
|
||||
"Local State": "Lekuko egoera",
|
||||
"Local State (Total)": "Lekuko egoera (Oso)",
|
||||
"Major Upgrade": "Nagusi eguneratu",
|
||||
"Master": "Nagusi",
|
||||
"Maximum Age": "Goren adin",
|
||||
"Metadata Only": "Metadatuak bakarrik",
|
||||
"Minimum Free Disk Space": "Diskoan leku libre gutxieneko",
|
||||
"Move to top of queue": "Igurikatze zerrenda bururat lekuz alda",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Hein askorentzako jokerra (errepertorio eta azpi errepertorioeri dagokiona)",
|
||||
"Never": "Sekulan",
|
||||
"New Device": "Berria makina",
|
||||
"New Folder": "Berri partekatze",
|
||||
"Newest First": "Gehien gertatu berri lehen",
|
||||
"No": "Ez",
|
||||
"No File Versioning": "Ez babestu",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Jakinaraztea",
|
||||
"OK": "Ados",
|
||||
"Off": "Desgaitu",
|
||||
"Oldest First": "Gehien zahar lehen",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Partikatzearen izen hautuzkoa eta atsegina, zure gisa. Tresna bakotxean desberdina izaiten ahal da.",
|
||||
"Options": "Hautuk",
|
||||
"Out of Sync": "Ez sinkronizatua",
|
||||
"Out of Sync Items": "Ez sinkronizatu elementuak",
|
||||
"Outgoing Rate Limit (KiB/s)": "Bidaltze emari gehieneko (KiB/s)",
|
||||
"Override Changes": "Aldaketak desegin",
|
||||
"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": "Bertako tresnaren karpetari buruzko bidea. Ez balin bada, asmatu beharko da bat. Tildea (~, edo ~+Espazioa Windows XP+Azerty-n) erabil litzateke bide motz gisa.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kopiak kontserbatzeko bidea (hutsa utzezazu, .stversioen karpetaren bide ohituan).",
|
||||
"Pause": "Pausa",
|
||||
"Paused": "Geldirik",
|
||||
"Please consult the release notes before performing a major upgrade.": "Aktualizatze garrantzitsu bat egin baino lehen, bertsioaren oharrak begira itzazu.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Konfigurazio leihoan asma itzazu erabiltzale izen bat eta sartzeko giltza bat",
|
||||
"Please wait": "Oraino pazientzia pixka bat har ezazu",
|
||||
"Preview": "Aurrebista",
|
||||
"Preview Usage Report": "Erabiltze aurrebista",
|
||||
"Quick guide to supported patterns": "Filtro ez onartuen txostena",
|
||||
"RAM Utilization": "RAM erabiltze",
|
||||
"Random": "Aleatorio",
|
||||
"Reduced by ignore patterns": "(Mugatu baztertzek patroikez)",
|
||||
"Release Notes": "Bertsioen notak",
|
||||
"Remote Devices": "Besteak makinak",
|
||||
"Remove": "Kendu",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Partikatzearen erabilzaile izena. Diren tresna guzietan berdin berdina izan behar du",
|
||||
"Rescan": "Berreskanea",
|
||||
"Rescan All": "Berreskanea guzia",
|
||||
"Rescan Interval": "Arte berreskanea",
|
||||
"Restart": "Berriz abiatu",
|
||||
"Restart Needed": "Berriz piztea beharrezkoa",
|
||||
"Restarting": "Berriz piztea martxan",
|
||||
"Resume": "Berriz hasi",
|
||||
"Reused": "Berriz erabilia",
|
||||
"Save": "Begiratu",
|
||||
"Scan Time Remaining": "Gelditzen den denbora azterketa",
|
||||
"Scanning": "Etengabeko azterketa",
|
||||
"Select the devices to share this folder with.": "Honekin sinkronizatua:",
|
||||
"Select the folders to share with this device.": "Tresna hunek erabiltzen dituen banaketak hauta",
|
||||
"Settings": "Egokitzek",
|
||||
"Share": "Banatu",
|
||||
"Share Folder": "Banatu",
|
||||
"Share Folders With Device": "Partekatu makinakekin",
|
||||
"Share With Devices": "Partekatuekin makinak",
|
||||
"Share this folder?": "Banatze hau onartzen duzu?",
|
||||
"Shared With": "Partekatuekin",
|
||||
"Show ID": "Erakutsi ene ID",
|
||||
"Show QR": "Erakutsi QR",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Tresnaren ID-aren ordez erakutsia, taldearen egoeran. Beste tresneri erakutsia izanen da, izen erabilgarria bezala",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Tresnaren ID-aren ordez erakutsia, taldearen egoeran. Hutsa utzia balin bada, urrun den tresnak proposatu izenarekin aktualizatua izanen da",
|
||||
"Shutdown": "Utzi",
|
||||
"Shutdown Complete": "Gelditua!",
|
||||
"Simple File Versioning": "Bertsioen segitze sinplifikatuak",
|
||||
"Single level wildcard (matches within a directory only)": "Hein bakar bateko jokerra (karpetaren barnean bakarrik dagokiona)",
|
||||
"Smallest First": "Tipienak lehenik",
|
||||
"Source Code": "Iturri kode",
|
||||
"Staggered File Versioning": "Bertsio eskaleratuak",
|
||||
"Start Browser": "Web nabigatzailea pitz",
|
||||
"Statistics": "Statistikak",
|
||||
"Stopped": "Gelditua!",
|
||||
"Support": "Foroa",
|
||||
"Sync Protocol Listen Addresses": "Sinkronizatu protokoloaren entzun zuzenbideak",
|
||||
"Syncing": "Sinkronizazio joaira",
|
||||
"Syncthing has been shut down.": "Syncthing gelditua izan da",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing-ek programa hauk integratzen ditu (edo programa hauetatik datozten elementuak):",
|
||||
"Syncthing is restarting.": "Syncthing berriz pizten ari",
|
||||
"Syncthing is upgrading.": "Syncthing aktualizatzen ari da",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Iduri luke Syncthing gelditua dela, edo bestenaz arrazo bat bada interneten konekzioarekin. Berriz entsea zaitez…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Iduri luke Syncthing arazo bat duela zure eskaeraren tratatzeko. Otoi, horria freska ezazu edo bestenaz Syncthing berriz pitz arazoak segitzen badu.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administrazio interfazea pentsatua da urrundikako irisbideak sekretu hitz gabe onartzeko !!!",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Estadistikak zuzen bide honetan publikoki ikusgarri dira",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurazioa grabatua izan da bainan ez aktibatua. Syncthing berriz piztu behar da konfigurazio berriaren berriz aktibatzeko.",
|
||||
"The device ID cannot be blank.": "Tresnaren ID-a ez da hutsa izaiten ahal.",
|
||||
"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).": "Tresnaren ID-a atxemaiten ahal da \"Ekintza> Tresna urrunduaren \"ID-a erakuts\" menuan. Espazio eta gioiak ez dira beharrezkoak.",
|
||||
"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.": "Erabileraren zifratu txostena egun guziz igorria da. Erabili diren plataformak, banaketeen neurriak eta aplikazioaren bertsioen zerendatzeko balio du.",
|
||||
"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.": "Sartu den tresnaren ID-ak iduri du ez duela balio. 52 edo 56-ko ezaugarriko kadena baten itxura behar luke, hizkiak, zifrak eta baita ere tarte edo gioiez egina",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Agingailu lerroaren lehen parametroa banatua den karpetaren bidea da, eta bigarrena karpetan den errelatibo bidea",
|
||||
"The folder ID cannot be blank.": "banatzearen ID-a ez da hutsa izaiten ahal",
|
||||
"The folder ID must be unique.": "Banatzearen ID-ak bakarra izan behar du",
|
||||
"The folder path cannot be blank.": "Karpetari buruzko bidea ez da hutsa izaiten ahal",
|
||||
"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.": "Hunako tarteak erabiliak dira: oren bateko denboraldian bertsio bat kontserbatua da 30 segundu guziz. Egun batekoan,bertsio bat egunero. Handik harat, adinaren mugetan egonez, bertsio bat astero.",
|
||||
"The following items could not be synchronized.": "Ondoko fitxero hauk ez dira sinkronizatuak ahal izan",
|
||||
"The maximum age must be a number and cannot be blank.": "Gehieneko adinak zenbaki bat behar du izan eta ez da hutsa izaiten ahal.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Bertsio baten kontserbatzeko epe haundiena (egunez behar du izan. Jar ezazu zerotan bertsioak betirako atxikitzeko)",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Diskoaren ehuneko espazioa hutsak zenbaki positibo bat behar du izan, 0 eta 100-en artekoa (100 barne)",
|
||||
"The number of days must be a number and cannot be blank.": "Egunen kopuruak numerikoa izan behar du eta ez da hutsa izaiten ahal",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Zikin ontziko elgar hizketen kontserbatzeko egun kopurua. Beti 0 erran nahi du.",
|
||||
"The number of old versions to keep, per file.": "Atxikitzeko diren lehenagoko bertsio kopurua,fitxero bakotxarentzat",
|
||||
"The number of versions must be a number and cannot be blank.": "Bertsio kopuruak numerikoa behar du izan eta ez da hutsa izaiten ahal",
|
||||
"The path cannot be blank.": "Bidea ez da hutsa izaiten ahal",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Ixuriaren neurria ez da negatiboa izaiten ahal (0 = mugarik gabekoa)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Ikerketaren tartea ez da segundu kopuru negatiboa izaiten ahal",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Akatsa zuzendua izanen delarik, automatikoki berriz entseatuak et sinkronizatuak izanen dira",
|
||||
"This Device": "Makina hau",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Hunek errexki irakurtzen eta aldatzen uzten ahal du zure ordenagailuko edozein fitxero, nahiz eta sartu denak ez haizu!",
|
||||
"This is a major version upgrade.": "Aktualizatze garrantzitsu bat da",
|
||||
"Trash Can File Versioning": "Zakarrontzia",
|
||||
"Unknown": "Ez ezaguna",
|
||||
"Unshared": "Partekatu ez den",
|
||||
"Unused": "Ez baliatua",
|
||||
"Up to Date": "Egun",
|
||||
"Updated": "Berritu",
|
||||
"Upgrade": "Aktualizatu",
|
||||
"Upgrade To {%version%}": "Egunetaratzea {{version}}-i buruz",
|
||||
"Upgrading": "Syncthing-en egunetaratzea",
|
||||
"Upload Rate": "Bidaltze emari",
|
||||
"Uptime": "Denbora ibiltze",
|
||||
"Use HTTPS for GUI": "HTTPS-a erabil GUI-arentzat",
|
||||
"Version": "Bertsio",
|
||||
"Versions Path": "Bertsioen egon tokia",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Bertsioak automatikoki ezeztatuak izanen dira, kontserbatzeko iraupen denbora pasatua badute edo bitartean onartua den kopurua gainditua balin bada",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Kasu emazu, \"{{otherFolder}}\" banaketa azpi-karpetaren bidea da. Arazoak emaiten ahal ditu, fitxero batzuen ezeztatze edo duplikatzeak batez ere.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Tresna bat gehitzen duzularik, gogoan atxik ezazu zurea bestaldean gehitu behar dela ere",
|
||||
"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.": "Tresna bat gehitzen delarik, gogoan atxik ezazu bere IDa erabilia dela errepertorioak lotzeko tresnen bitartez. ID-a hautskorra da eta banatze huntan parte hartzen duten tresna guzietan berdina izanen da",
|
||||
"Yes": "Bai",
|
||||
"You must keep at least one version.": "Bertsio bat bederen behar duzu atxiki",
|
||||
"days": "Egunak",
|
||||
"directories": "karpetak",
|
||||
"files": "fitxategik",
|
||||
"full documentation": "Dokumentazio osoa",
|
||||
"items": "Elementuak",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} banaketa \"{{folderLabel}}\" gomitatzen zaitu.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} \"{{folderLabel}}\" ({{folder}}) gomitatzen zaitu."
|
||||
}
|
||||
@@ -123,7 +123,7 @@
|
||||
"Out of Sync Items": "Éléments non synchronisés",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
|
||||
"Override Changes": "Écraser les changements",
|
||||
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows+Azerty) peut être utilisé comme raccourci vers",
|
||||
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
|
||||
"Pause": "Pause",
|
||||
"Paused": "En pause",
|
||||
@@ -135,7 +135,7 @@
|
||||
"Quick guide to supported patterns": "Guide rapide des masques supportés",
|
||||
"RAM Utilization": "Utilisation de la RAM",
|
||||
"Random": "Aléatoire",
|
||||
"Reduced by ignore patterns": "Restreint par les masques d'exclusion",
|
||||
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
|
||||
"Release Notes": "Notes de version",
|
||||
"Remote Devices": "Autres appareils",
|
||||
"Remove": "Enlever",
|
||||
@@ -171,7 +171,7 @@
|
||||
"Smallest First": "Les plus petits d'abord",
|
||||
"Source Code": "Code source",
|
||||
"Staggered File Versioning": "Versions échelonnées",
|
||||
"Start Browser": "Démarrer le navigateur web",
|
||||
"Start Browser": "Lancer le navigateur web",
|
||||
"Statistics": "Statistiques",
|
||||
"Stopped": "Arrêté",
|
||||
"Support": "Forum",
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
"Out of Sync Items": "Éléments non synchronisés",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limite du débit d'émission (Ko/s)",
|
||||
"Override Changes": "Écraser les changements",
|
||||
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows+Azerty) peut être utilisé comme raccourci vers",
|
||||
"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": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les copies doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
|
||||
"Pause": "Pause",
|
||||
"Paused": "En pause",
|
||||
@@ -132,10 +132,10 @@
|
||||
"Please wait": "Merci de patienter",
|
||||
"Preview": "Aperçu",
|
||||
"Preview Usage Report": "Aperçu du rapport de statistiques d'utilisation",
|
||||
"Quick guide to supported patterns": "Guide rapide des masques supportés",
|
||||
"Quick guide to supported patterns": "Filtro ez onartuen txostena",
|
||||
"RAM Utilization": "Utilisation de la RAM",
|
||||
"Random": "Aléatoire",
|
||||
"Reduced by ignore patterns": "Restreint par les masques d'exclusion",
|
||||
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
|
||||
"Release Notes": "Notes de version",
|
||||
"Remote Devices": "Autres appareils",
|
||||
"Remove": "Enlever",
|
||||
@@ -171,7 +171,7 @@
|
||||
"Smallest First": "Les plus petits d'abord",
|
||||
"Source Code": "Code source",
|
||||
"Staggered File Versioning": "Versions échelonnées",
|
||||
"Start Browser": "Démarrer le navigateur web",
|
||||
"Start Browser": "Lancer le navigateur web",
|
||||
"Statistics": "Statistiques",
|
||||
"Stopped": "Arrêté",
|
||||
"Support": "Forum",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"Command": "Kommando",
|
||||
"Comment, when used at the start of a line": "Kommentaar, wannear as brûkt by it begjin fan in rige",
|
||||
"Compression": "Kompresje",
|
||||
"Configured": "Configured",
|
||||
"Configured": "Konfigureart",
|
||||
"Connection Error": "Ferbiningsflater",
|
||||
"Connection Type": "Ferbiningstype",
|
||||
"Copied from elsewhere": "Oernommen fan earne oars",
|
||||
@@ -45,7 +45,7 @@
|
||||
"Device Name": "Apparaatnamme",
|
||||
"Devices": "Apparaten",
|
||||
"Disconnected": "Ferbining ferbrutsen",
|
||||
"Discovered": "Discovered",
|
||||
"Discovered": "Untdekt",
|
||||
"Discovery": "Untdekking",
|
||||
"Documentation": "Dokumintaasje",
|
||||
"Download Rate": "Ynlaadfluggens",
|
||||
@@ -135,7 +135,7 @@
|
||||
"Quick guide to supported patterns": "Fluch-paadwizer foar stipe patroanen",
|
||||
"RAM Utilization": "RAM-brûken",
|
||||
"Random": "Willekeurich",
|
||||
"Reduced by ignore patterns": "Reduced by ignore patterns",
|
||||
"Reduced by ignore patterns": "Ferlytse troch negear-patroanen",
|
||||
"Release Notes": "Utjeftenotysjes",
|
||||
"Remote Devices": "Apparaten op Ofstân",
|
||||
"Remove": "Fuortsmite",
|
||||
@@ -231,8 +231,8 @@
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Jo moatte minstens ien ferzje bewarje.",
|
||||
"days": "dagen",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"directories": "triemtafels",
|
||||
"files": "triemmen",
|
||||
"full documentation": "komplete dokumintaasje",
|
||||
"items": "items",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wol map \"{{folder}}\" diele.",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"Command": "커맨드",
|
||||
"Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
|
||||
"Compression": "압축",
|
||||
"Configured": "Configured",
|
||||
"Configured": "설정됨",
|
||||
"Connection Error": "연결 에러",
|
||||
"Connection Type": "연결 종류",
|
||||
"Copied from elsewhere": "다른 곳에서 복사됨",
|
||||
@@ -45,7 +45,7 @@
|
||||
"Device Name": "기기 이름",
|
||||
"Devices": "기기",
|
||||
"Disconnected": "연결 끊김",
|
||||
"Discovered": "Discovered",
|
||||
"Discovered": "탐색됨",
|
||||
"Discovery": "탐색",
|
||||
"Documentation": "문서",
|
||||
"Download Rate": "다운로드 속도",
|
||||
@@ -231,8 +231,8 @@
|
||||
"Yes": "예",
|
||||
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
|
||||
"days": "일",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"directories": "디렉토리",
|
||||
"files": "파일",
|
||||
"full documentation": "전체 문서",
|
||||
"items": "항목",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다.",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"Command": "Komanda",
|
||||
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
|
||||
"Compression": "Kompresija",
|
||||
"Configured": "Configured",
|
||||
"Configured": "Sukonfigūruotas",
|
||||
"Connection Error": "Susijungimo klaida",
|
||||
"Connection Type": "Ryšio tipas",
|
||||
"Copied from elsewhere": "Nukopijuota iš kitur",
|
||||
@@ -45,7 +45,7 @@
|
||||
"Device Name": "Įrenginio pavadinimas",
|
||||
"Devices": "Įrenginiai",
|
||||
"Disconnected": "Atsijungęs",
|
||||
"Discovered": "Discovered",
|
||||
"Discovered": "Atrastas",
|
||||
"Discovery": "Lokacija",
|
||||
"Documentation": "Aprašymas",
|
||||
"Download Rate": "Parsisiuntimo greitis",
|
||||
@@ -231,8 +231,8 @@
|
||||
"Yes": "Taip",
|
||||
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
|
||||
"days": "dienos",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"directories": "papkės",
|
||||
"files": "failai",
|
||||
"full documentation": "pilna dokumentacija",
|
||||
"items": "įrašai",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\"",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
|
||||
"Compression": "Compressão",
|
||||
"Configured": "Configured",
|
||||
"Configured": "Configurado",
|
||||
"Connection Error": "Erro de ligação",
|
||||
"Connection Type": "Tipo de ligação",
|
||||
"Copied from elsewhere": "Copiado doutro sítio",
|
||||
@@ -45,7 +45,7 @@
|
||||
"Device Name": "Nome do dispositivo",
|
||||
"Devices": "Dispositivos",
|
||||
"Disconnected": "Desconectado",
|
||||
"Discovered": "Discovered",
|
||||
"Discovered": "Descoberto",
|
||||
"Discovery": "Pesquisa",
|
||||
"Documentation": "Documentação",
|
||||
"Download Rate": "Velocidade de recepção",
|
||||
@@ -72,7 +72,7 @@
|
||||
"Folder Path": "Caminho da pasta",
|
||||
"Folder Type": "Tipo de pasta",
|
||||
"Folders": "Pastas",
|
||||
"GUI": "GUI",
|
||||
"GUI": "Interface gráfica",
|
||||
"GUI Authentication Password": "Senha da autenticação na interface gráfica",
|
||||
"GUI Authentication User": "Utilizador da autenticação na interface gráfica",
|
||||
"GUI Listen Addresses": "Endereço de escuta da interface gráfica",
|
||||
@@ -135,7 +135,7 @@
|
||||
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
|
||||
"RAM Utilization": "Utilização da RAM",
|
||||
"Random": "Aleatória",
|
||||
"Reduced by ignore patterns": "Reduced by ignore patterns",
|
||||
"Reduced by ignore patterns": "Reduzido pelos padrões de exclusão",
|
||||
"Release Notes": "Notas de lançamento",
|
||||
"Remote Devices": "Dispositivos remotos",
|
||||
"Remove": "Remover",
|
||||
@@ -231,8 +231,8 @@
|
||||
"Yes": "Sim",
|
||||
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
|
||||
"days": "dias",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"directories": "pastas",
|
||||
"files": "ficheiros",
|
||||
"full documentation": "documentação completa",
|
||||
"items": "itens",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\".",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"A device with that ID is already added.": "En enhet med det ID:t är redan tillagt.",
|
||||
"A device with that ID is already added.": "En enhet med det ID är redan tillagt.",
|
||||
"A negative number of days doesn't make sense.": "Ett negativt antal dagar är inte rimligt.",
|
||||
"A new major version may not be compatible with previous versions.": "En ny huvudversion kan eventuellt vara inkompatibel med tidigare versioner.",
|
||||
"API Key": "API-nyckel",
|
||||
@@ -24,7 +24,7 @@
|
||||
"Automatic upgrades": "Automatiska uppgraderingar",
|
||||
"Be careful!": "Var aktsam!",
|
||||
"Bugs": "Buggar",
|
||||
"CPU Utilization": "CPU-användning",
|
||||
"CPU Utilization": "CPU användning",
|
||||
"Changelog": "Ändringslogg",
|
||||
"Clean out after": "Rensa efteråt",
|
||||
"Close": "Stäng",
|
||||
@@ -40,8 +40,8 @@
|
||||
"Danger!": "Fara!",
|
||||
"Deleted": "Borttaget",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
|
||||
"Device ID": "Enhets ID",
|
||||
"Device Identification": "Enhets identifikation",
|
||||
"Device ID": "Enhet-ID",
|
||||
"Device Identification": "Enhet identifikation",
|
||||
"Device Name": "Enhets namn",
|
||||
"Devices": "Enheter",
|
||||
"Disconnected": "Frånkopplad",
|
||||
@@ -62,9 +62,9 @@
|
||||
"Failed Items": "Misslyckade objekt",
|
||||
"File Pull Order": "Filhämtningsprioritering",
|
||||
"File Versioning": "Filversionshantering",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras vid sökning efter förändringar. Används på FAT-filsystem.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till katalogen .stversions om de ersätts eller raderas av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersatts eller raderats av Syncthing.",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras under sökning efter förändringar. Används på FAT-filsystem.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till .stversions katalogen när de ersätts eller raderas av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions mapp när de ersätts eller raderas av Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
|
||||
"Folder": "Katalog",
|
||||
"Folder ID": "Katalog-ID",
|
||||
@@ -133,7 +133,7 @@
|
||||
"Preview": "Förhandsgranska",
|
||||
"Preview Usage Report": "Förhandsgranska statistik",
|
||||
"Quick guide to supported patterns": "Snabb handledning till mönster som stöds",
|
||||
"RAM Utilization": "RAM-användning",
|
||||
"RAM Utilization": "RAM användning",
|
||||
"Random": "Slumpmässig",
|
||||
"Reduced by ignore patterns": "Minskas med ignorera mönster",
|
||||
"Release Notes": "Versionsanteckningar",
|
||||
@@ -162,8 +162,8 @@
|
||||
"Shared With": "Delad med",
|
||||
"Show ID": "Visa ID",
|
||||
"Show QR": "Visa QR",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visas i stället för enhets ID i samlingsstatusen. Skickas till andra enheter som namn på denna enhet.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhets ID i samlingsstatusen. Sätts till namnet på den andra enheten vid första anslutning om det lämnas tomt.",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visas istället för enhet-ID i klusterstatusen. Skickas till andra enheter som ett alternativt förvalt namn.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhet-ID i klusterstatusen. Kommer att uppdateras till namnet enheten annonserar om det lämnas tomt.",
|
||||
"Shutdown": "Stäng av",
|
||||
"Shutdown Complete": "Avstängning klar",
|
||||
"Simple File Versioning": "Enkel filversionshantering",
|
||||
@@ -186,13 +186,13 @@
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administratör gränssnittet är konfigurerat för att tillåta fjärrtillträde utan ett lösenord.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Den aggregerade statistiken är offentligt tillgängliga på webbadressen nedan.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
|
||||
"The device ID cannot be blank.": "Enhets ID:t kan inte vara tomt.",
|
||||
"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).": "Enhets ID:t som behövs här kan du hitta i \"Åtgärder > Visa ID\"-dialogrutan på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
|
||||
"The device ID cannot be blank.": "Enhet-ID kan inte vara tomt.",
|
||||
"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).": "Enhet-ID som behövs här kan du hitta i \"Åtgärder > Visa ID\"-dialogrutan på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets ID:t verkar inte korrekt. Det ska vara en 52 eller 56 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhet-ID verkar inte vara korrekt. Det ska vara en 52 eller 56 teckensträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den första kommandoparametern är sökvägen till mappen och den andra parametern är den relativa sökvägen i mappen.",
|
||||
"The folder ID cannot be blank.": "Katalogens ID får inte vara tomt.",
|
||||
"The folder ID must be unique.": "Katalogens ID måste vara unikt.",
|
||||
"The folder ID cannot be blank.": "Katalog-ID får inte vara tomt.",
|
||||
"The folder ID must be unique.": "Katalog-ID måste vara unikt.",
|
||||
"The folder path cannot be blank.": "Katalogsökvägen kan inte vara tom.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De följande intervallen används: varje 30 sekunder under den första timmen; varje timme under den första dagen; varje dag för de första 30 dagarna; varje vecka tills den maximala åldersgränsen uppnås.",
|
||||
"The following items could not be synchronized.": "Följande objekt kunde inte synkroniseras.",
|
||||
@@ -227,7 +227,7 @@
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner tas bort automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i sitt interval.",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en underkatalog till en befintlig katalog \"{{otherFolder}}\".",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet, kom ihåg att den här enheten måste läggas till på den andra enheten också.",
|
||||
"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 du lägger till ny katalog, tänk på att katalogens ID knyter ihop katalogen mellan olika noder. De måste vara exakt desamma mellan noder och stora eller små bokstäver har betydelse.",
|
||||
"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 du lägger till ny katalog, tänk på att katalog-ID knyter ihop kataloger mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
|
||||
"Yes": "Ja",
|
||||
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
|
||||
"days": "dagar",
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"GUI Listen Addresses": "图形管理界面监听地址",
|
||||
"Generate": "生成",
|
||||
"Global Discovery": "在互联网上寻找设备\n",
|
||||
"Global Discovery Servers": "全球发现服务器",
|
||||
"Global Discovery Servers": "全局发现服务器",
|
||||
"Global State": "全局状态",
|
||||
"Help": "帮助",
|
||||
"Home page": "主页",
|
||||
|
||||
@@ -1 +1 @@
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","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","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
|
||||
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","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","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
var validLangs = ["bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","es-ES","fi","fr","fr-CA","fy","hu","id","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sv","tr","uk","vi","zh-CN","zh-TW"]
|
||||
var validLangs = ["bg","ca","ca@valencia","cs","da","de","el","en","en-GB","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","sv","tr","uk","vi","zh-CN","zh-TW"]
|
||||
|
||||
@@ -311,18 +311,22 @@
|
||||
<tr>
|
||||
<th><span class="fa fa-fw fa-globe"></span> <span translate>Global State</span></th>
|
||||
<td class="text-right">
|
||||
{{model[folder.id].globalFiles | alwaysNumber}} <span translate>files</span>,
|
||||
{{model[folder.id].globalDirectories | alwaysNumber}} <span translate>directories</span>,
|
||||
~{{model[folder.id].globalBytes | binary}}B
|
||||
<span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
|
||||
<span class="fa fa-files-o"></span> {{model[folder.id].globalFiles | alwaysNumber}} 
|
||||
<span class="fa fa-folder-o"></span> {{model[folder.id].globalDirectories | alwaysNumber}} 
|
||||
<span class="fa fa-hdd-o"></span> ~{{model[folder.id].globalBytes | binary}}B
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fa fa-fw fa-home"></span> <span translate>Local State</span></th>
|
||||
<td class="text-right">
|
||||
{{model[folder.id].localFiles | alwaysNumber}} <span translate>files</span>,
|
||||
{{model[folder.id].localDirectories | alwaysNumber}} <span translate>directories</span>,
|
||||
~{{model[folder.id].localBytes | binary}}B
|
||||
<span ng-if="model[folder.id].ignorePatterns"><br/><i><small translate class="text-muted">Reduced by ignore patterns</small></i></span>
|
||||
<span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
|
||||
<span class="fa fa-files-o"></span> {{model[folder.id].localFiles | alwaysNumber}} 
|
||||
<span class="fa fa-folder-o"></span> {{model[folder.id].localDirectories | alwaysNumber}} 
|
||||
<span class="fa fa-hdd-o"></span> ~{{model[folder.id].localBytes | binary}}B
|
||||
<span ng-if="model[folder.id].ignorePatterns"><br/><i><small translate class="text-muted">Reduced by ignore patterns</small></i></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="neededItems(folder.id) > 0">
|
||||
@@ -337,7 +341,7 @@
|
||||
<span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="hasFailedFiles(folder.id))">
|
||||
<tr ng-if="hasFailedFiles(folder.id)">
|
||||
<th><span class="fa fa-fw fa-exclamation-circle"></span> <span translate>Failed Items</span></th>
|
||||
<!-- Show the number of failed items as a link to bring up the list. -->
|
||||
<td class="text-right">
|
||||
|
||||
@@ -51,6 +51,7 @@ go run build.go -goarch armhf deb
|
||||
mv *.deb "$WORKSPACE"
|
||||
|
||||
go run build.go -goarch amd64 snap
|
||||
go run build.go -goarch armhf snap
|
||||
go run build.go -goarch arm64 snap
|
||||
|
||||
mv *.snap "$WORKSPACE"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ function init {
|
||||
export GOPATH=$(pwd)
|
||||
export WORKSPACE="${WORKSPACE:-$GOPATH}"
|
||||
go version
|
||||
rm -f *.tar.gz *.zip *.deb
|
||||
rm -f *.tar.gz *.zip *.deb *.snap
|
||||
cd src/github.com/syncthing/syncthing
|
||||
|
||||
version=$(go run build.go version)
|
||||
|
||||
@@ -43,7 +43,7 @@ func (validationError) String() string {
|
||||
|
||||
func TestReplaceCommit(t *testing.T) {
|
||||
w := Wrap("/dev/null", Configuration{Version: 0})
|
||||
if w.Raw().Version != 0 {
|
||||
if w.RawCopy().Version != 0 {
|
||||
t.Fatal("Config incorrect")
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
if w.RequiresRestart() {
|
||||
t.Fatal("Should not require restart")
|
||||
}
|
||||
if w.Raw().Version != CurrentVersion {
|
||||
if w.RawCopy().Version != CurrentVersion {
|
||||
t.Fatal("Config should have changed")
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
if !w.RequiresRestart() {
|
||||
t.Fatal("Should require restart")
|
||||
}
|
||||
if w.Raw().Version != CurrentVersion {
|
||||
if w.RawCopy().Version != CurrentVersion {
|
||||
t.Fatal("Config should have changed")
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestReplaceCommit(t *testing.T) {
|
||||
if !w.RequiresRestart() {
|
||||
t.Fatal("Should still require restart")
|
||||
}
|
||||
if w.Raw().Version != CurrentVersion {
|
||||
if w.RawCopy().Version != CurrentVersion {
|
||||
t.Fatal("Config should not have changed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if diff, equal := messagediff.PrettyDiff(cfg.Raw(), cfg2.Raw()); !equal {
|
||||
if diff, equal := messagediff.PrettyDiff(cfg.RawCopy(), cfg2.RawCopy()); !equal {
|
||||
t.Errorf("Configs are not equal. Diff:\n%s", diff)
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ func TestCopy(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := wrapper.Raw()
|
||||
cfg := wrapper.RawCopy()
|
||||
|
||||
bsOrig, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
@@ -548,7 +548,7 @@ func TestPullOrder(t *testing.T) {
|
||||
// Serialize and deserialize again to verify it survives the transformation
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cfg := wrapper.Raw()
|
||||
cfg := wrapper.RawCopy()
|
||||
cfg.WriteXML(buf)
|
||||
|
||||
t.Logf("%s", buf.Bytes())
|
||||
@@ -611,7 +611,7 @@ func TestDuplicateDevices(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if l := len(wrapper.Raw().Devices); l != 3 {
|
||||
if l := len(wrapper.RawCopy().Devices); l != 3 {
|
||||
t.Errorf("Incorrect number of devices, %d != 3", l)
|
||||
}
|
||||
|
||||
@@ -755,7 +755,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
|
||||
t.Errorf("Failed: %s", err)
|
||||
}
|
||||
|
||||
raw := wrapper.Raw()
|
||||
raw := wrapper.RawCopy()
|
||||
raw.Devices = raw.Devices[:len(raw.Devices)-1]
|
||||
|
||||
if len(raw.Folders[0].Devices) <= len(raw.Devices) {
|
||||
@@ -767,7 +767,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
|
||||
t.Errorf("Failed: %s", err)
|
||||
}
|
||||
|
||||
raw = wrapper.Raw()
|
||||
raw = wrapper.RawCopy()
|
||||
if len(raw.Folders[0].Devices) > len(raw.Devices) {
|
||||
t.Error("Unexpected extra device")
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ package config
|
||||
import "github.com/syncthing/syncthing/lib/protocol"
|
||||
|
||||
type DeviceConfiguration struct {
|
||||
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
|
||||
Name string `xml:"name,attr,omitempty" json:"name"`
|
||||
Addresses []string `xml:"address,omitempty" json:"addresses"`
|
||||
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
|
||||
CertName string `xml:"certName,attr,omitempty" json:"certName"`
|
||||
Introducer bool `xml:"introducer,attr" json:"introducer"`
|
||||
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
|
||||
Name string `xml:"name,attr,omitempty" json:"name"`
|
||||
Addresses []string `xml:"address,omitempty" json:"addresses"`
|
||||
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
|
||||
CertName string `xml:"certName,attr,omitempty" json:"certName"`
|
||||
Introducer bool `xml:"introducer,attr" json:"introducer"`
|
||||
SkipIntroductionRemovals bool `xml:"skipIntroductionRemovals,attr" json:"skipIntroductionRemovals"`
|
||||
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
|
||||
}
|
||||
|
||||
func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {
|
||||
|
||||
@@ -45,7 +45,8 @@ type FolderConfiguration struct {
|
||||
}
|
||||
|
||||
type FolderDeviceConfiguration struct {
|
||||
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
|
||||
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
|
||||
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
|
||||
}
|
||||
|
||||
func NewFolderConfiguration(id, path string) FolderConfiguration {
|
||||
|
||||
@@ -120,9 +120,11 @@ func (w *Wrapper) Unsubscribe(c Committer) {
|
||||
w.mut.Unlock()
|
||||
}
|
||||
|
||||
// Raw returns the currently wrapped Configuration object.
|
||||
func (w *Wrapper) Raw() Configuration {
|
||||
return w.cfg
|
||||
// RawCopy returns a copy of the currently wrapped Configuration object.
|
||||
func (w *Wrapper) RawCopy() Configuration {
|
||||
w.mut.Lock()
|
||||
defer w.mut.Unlock()
|
||||
return w.cfg.Copy()
|
||||
}
|
||||
|
||||
// Replace swaps the current configuration object for the given one.
|
||||
@@ -159,7 +161,7 @@ func (w *Wrapper) replaceLocked(to Configuration) error {
|
||||
|
||||
func (w *Wrapper) notifyListeners(from, to Configuration) {
|
||||
for _, sub := range w.subs {
|
||||
go w.notifyListener(sub, from, to)
|
||||
go w.notifyListener(sub, from.Copy(), to.Copy())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ func (t *relayListener) Serve() {
|
||||
t.mut.Unlock()
|
||||
|
||||
clnt, err := client.NewClient(t.uri, t.tlsCfg.Certificates, nil, 10*time.Second)
|
||||
invitations := clnt.Invitations()
|
||||
if err != nil {
|
||||
t.mut.Lock()
|
||||
t.err = err
|
||||
@@ -62,7 +63,7 @@ func (t *relayListener) Serve() {
|
||||
|
||||
for {
|
||||
select {
|
||||
case inv, ok := <-t.client.Invitations():
|
||||
case inv, ok := <-invitations:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
|
||||
service.Add(serviceFunc(service.connect))
|
||||
service.Add(serviceFunc(service.handle))
|
||||
|
||||
raw := cfg.Raw()
|
||||
raw := cfg.RawCopy()
|
||||
// Actually starts the listeners and NAT service
|
||||
service.CommitConfiguration(raw, raw)
|
||||
|
||||
@@ -276,7 +276,7 @@ func (s *Service) connect() {
|
||||
var sleep time.Duration
|
||||
|
||||
for {
|
||||
cfg := s.cfg.Raw()
|
||||
cfg := s.cfg.RawCopy()
|
||||
|
||||
bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
|
||||
for _, df := range dialers {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:generate go run ../../script/protofmt.go structs.proto
|
||||
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. structs.proto
|
||||
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. structs.proto
|
||||
|
||||
package db
|
||||
|
||||
|
||||
@@ -912,32 +912,32 @@ var (
|
||||
)
|
||||
|
||||
var fileDescriptorStructs = []byte{
|
||||
// 419 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0xcd, 0xaa, 0xd3, 0x40,
|
||||
0x18, 0x4d, 0xda, 0xdc, 0x36, 0xfd, 0x62, 0xaf, 0x3a, 0xc8, 0x25, 0x14, 0x4c, 0x2f, 0x05, 0x41,
|
||||
0x04, 0x53, 0xbd, 0xe2, 0xc6, 0x65, 0x17, 0x05, 0x41, 0x44, 0x46, 0xa9, 0xcb, 0xd2, 0x64, 0xa6,
|
||||
0xe9, 0x40, 0x32, 0x13, 0x33, 0x93, 0x42, 0x7d, 0x12, 0x97, 0x7d, 0x9c, 0x2e, 0x7d, 0x02, 0xd1,
|
||||
0xfa, 0x12, 0x2e, 0x9d, 0x4e, 0x7e, 0xcc, 0xd2, 0x45, 0xe0, 0x3b, 0x73, 0xce, 0xf9, 0xce, 0x99,
|
||||
0x0c, 0x8c, 0xa5, 0x2a, 0xca, 0x58, 0xc9, 0x30, 0x2f, 0x84, 0x12, 0xa8, 0x47, 0xa2, 0xc9, 0xf3,
|
||||
0x84, 0xa9, 0x5d, 0x19, 0x85, 0xb1, 0xc8, 0xe6, 0x89, 0x48, 0xc4, 0xdc, 0x50, 0x51, 0xb9, 0x35,
|
||||
0xc8, 0x00, 0x33, 0x55, 0x96, 0xc9, 0xeb, 0x8e, 0x5c, 0x1e, 0x78, 0xac, 0x76, 0x8c, 0x27, 0x9d,
|
||||
0x29, 0x65, 0x51, 0xb5, 0x21, 0x16, 0xe9, 0x3c, 0xa2, 0x79, 0x65, 0x9b, 0x7d, 0x06, 0x6f, 0xc9,
|
||||
0x52, 0xba, 0xa2, 0x85, 0x64, 0x82, 0xa3, 0x17, 0x30, 0xdc, 0x57, 0xa3, 0x6f, 0xdf, 0xda, 0x4f,
|
||||
0xbd, 0xbb, 0x07, 0x61, 0x63, 0x0a, 0x57, 0x34, 0x56, 0xa2, 0x58, 0x38, 0xa7, 0x1f, 0x53, 0x0b,
|
||||
0x37, 0x32, 0x74, 0x03, 0x03, 0x42, 0xf7, 0x2c, 0xa6, 0x7e, 0x4f, 0x1b, 0xee, 0xe1, 0x1a, 0xcd,
|
||||
0x96, 0xe0, 0xd5, 0x4b, 0xdf, 0x31, 0xa9, 0xd0, 0x4b, 0x70, 0x6b, 0x87, 0xd4, 0x9b, 0xfb, 0x7a,
|
||||
0xf3, 0xfd, 0x90, 0x44, 0x61, 0x27, 0xbb, 0x5e, 0xdc, 0xca, 0xde, 0x38, 0xdf, 0x8e, 0x53, 0x6b,
|
||||
0xf6, 0xa7, 0x07, 0x0f, 0x2f, 0xaa, 0xb7, 0x7c, 0x2b, 0x3e, 0x15, 0x25, 0x8f, 0x37, 0x8a, 0x12,
|
||||
0x84, 0xc0, 0xe1, 0x9b, 0x8c, 0x9a, 0x92, 0x23, 0x6c, 0x66, 0xf4, 0x0c, 0x1c, 0x75, 0xc8, 0xab,
|
||||
0x1e, 0xd7, 0x77, 0x37, 0xff, 0x8a, 0xb7, 0x76, 0xcd, 0x62, 0xa3, 0xb9, 0xf8, 0x25, 0xfb, 0x4a,
|
||||
0xfd, 0xbe, 0xd6, 0xf6, 0xb1, 0x99, 0xd1, 0x2d, 0x78, 0x39, 0x2d, 0x32, 0x26, 0xab, 0x96, 0x8e,
|
||||
0xa6, 0xc6, 0xb8, 0x7b, 0x84, 0x1e, 0x03, 0x64, 0x82, 0xb0, 0x2d, 0xa3, 0x64, 0x2d, 0xfd, 0x2b,
|
||||
0xe3, 0x1d, 0x35, 0x27, 0x1f, 0x91, 0x0f, 0x43, 0x42, 0x53, 0xaa, 0xfb, 0xf9, 0x03, 0xcd, 0xb9,
|
||||
0xb8, 0x81, 0x17, 0x86, 0xf1, 0xfd, 0x26, 0x65, 0xc4, 0x1f, 0x56, 0x4c, 0x0d, 0xd1, 0x13, 0xb8,
|
||||
0xe6, 0x62, 0xdd, 0xcd, 0x75, 0x8d, 0x60, 0xcc, 0xc5, 0x87, 0x4e, 0x72, 0xe7, 0x5d, 0x46, 0xff,
|
||||
0xf7, 0x2e, 0x13, 0x70, 0x25, 0xfd, 0x52, 0x52, 0xae, 0x5f, 0x06, 0x4c, 0xd3, 0x16, 0xa3, 0x29,
|
||||
0x78, 0xed, 0x3d, 0x74, 0xa2, 0xa7, 0xe9, 0x2b, 0xdc, 0x5e, 0xed, 0x7d, 0xfd, 0xeb, 0x17, 0x8f,
|
||||
0x4e, 0xbf, 0x02, 0xeb, 0x74, 0x0e, 0xec, 0xef, 0xfa, 0xfb, 0x79, 0x0e, 0xac, 0xe3, 0xef, 0xc0,
|
||||
0x8e, 0x06, 0x26, 0xf8, 0xd5, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xae, 0x24, 0x77, 0xb3,
|
||||
0x02, 0x00, 0x00,
|
||||
// 425 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x4f, 0x8b, 0xd3, 0x40,
|
||||
0x1c, 0xed, 0xb4, 0xd9, 0xfe, 0xf9, 0xc5, 0xae, 0x3a, 0xc8, 0x32, 0x14, 0x4c, 0x43, 0x41, 0x08,
|
||||
0x82, 0xa9, 0x56, 0xbc, 0x78, 0xdc, 0xc3, 0x82, 0x20, 0x22, 0x51, 0xd6, 0xe3, 0xd2, 0xcc, 0xfc,
|
||||
0xda, 0x1d, 0x48, 0x67, 0x62, 0x66, 0x52, 0xa8, 0x9f, 0xc4, 0xe3, 0x7e, 0x9c, 0x1e, 0xfd, 0x04,
|
||||
0xa2, 0xf5, 0x4b, 0x78, 0x94, 0x4e, 0xd2, 0x98, 0xe3, 0xde, 0xde, 0x9b, 0xdf, 0x7b, 0xbf, 0xf7,
|
||||
0x66, 0x06, 0xc6, 0xc6, 0x16, 0x25, 0xb7, 0x26, 0xce, 0x0b, 0x6d, 0x35, 0xed, 0x8a, 0x74, 0xf2,
|
||||
0x62, 0x2d, 0xed, 0x6d, 0x99, 0xc6, 0x5c, 0x6f, 0xe6, 0x6b, 0xbd, 0xd6, 0x73, 0x37, 0x4a, 0xcb,
|
||||
0x95, 0x63, 0x8e, 0x38, 0x54, 0x59, 0x26, 0x6f, 0x5a, 0x72, 0xb3, 0x53, 0xdc, 0xde, 0x4a, 0xb5,
|
||||
0x6e, 0xa1, 0x4c, 0xa6, 0xd5, 0x06, 0xae, 0xb3, 0x79, 0x8a, 0x79, 0x65, 0x9b, 0x7d, 0x01, 0xff,
|
||||
0x4a, 0x66, 0x78, 0x8d, 0x85, 0x91, 0x5a, 0xd1, 0x97, 0x30, 0xd8, 0x56, 0x90, 0x91, 0x90, 0x44,
|
||||
0xfe, 0xe2, 0x51, 0x7c, 0x32, 0xc5, 0xd7, 0xc8, 0xad, 0x2e, 0x2e, 0xbd, 0xfd, 0xcf, 0x69, 0x27,
|
||||
0x39, 0xc9, 0xe8, 0x05, 0xf4, 0x05, 0x6e, 0x25, 0x47, 0xd6, 0x0d, 0x49, 0xf4, 0x20, 0xa9, 0xd9,
|
||||
0xec, 0x0a, 0xfc, 0x7a, 0xe9, 0x7b, 0x69, 0x2c, 0x7d, 0x05, 0xc3, 0xda, 0x61, 0x18, 0x09, 0x7b,
|
||||
0x91, 0xbf, 0x78, 0x18, 0x8b, 0x34, 0x6e, 0x65, 0xd7, 0x8b, 0x1b, 0xd9, 0x5b, 0xef, 0xfb, 0xdd,
|
||||
0xb4, 0x33, 0xfb, 0xdb, 0x85, 0xc7, 0x47, 0xd5, 0x3b, 0xb5, 0xd2, 0x9f, 0x8b, 0x52, 0xf1, 0xa5,
|
||||
0x45, 0x41, 0x29, 0x78, 0x6a, 0xb9, 0x41, 0x57, 0x72, 0x94, 0x38, 0x4c, 0x9f, 0x83, 0x67, 0x77,
|
||||
0x79, 0xd5, 0xe3, 0x7c, 0x71, 0xf1, 0xbf, 0x78, 0x63, 0xdf, 0xe5, 0x98, 0x38, 0xcd, 0xd1, 0x6f,
|
||||
0xe4, 0x37, 0x64, 0xbd, 0x90, 0x44, 0xbd, 0xc4, 0x61, 0x1a, 0x82, 0x9f, 0x63, 0xb1, 0x91, 0xa6,
|
||||
0x6a, 0xe9, 0x85, 0x24, 0x1a, 0x27, 0xed, 0x23, 0xfa, 0x14, 0x60, 0xa3, 0x85, 0x5c, 0x49, 0x14,
|
||||
0x37, 0x86, 0x9d, 0x39, 0xef, 0xe8, 0x74, 0xf2, 0x89, 0x32, 0x18, 0x08, 0xcc, 0xd0, 0xa2, 0x60,
|
||||
0xfd, 0x90, 0x44, 0xc3, 0xe4, 0x44, 0x8f, 0x13, 0xa9, 0xb6, 0xcb, 0x4c, 0x0a, 0x36, 0xa8, 0x26,
|
||||
0x35, 0xa5, 0xcf, 0xe0, 0x5c, 0xe9, 0x9b, 0x76, 0xee, 0xd0, 0x09, 0xc6, 0x4a, 0x7f, 0x6c, 0x25,
|
||||
0xb7, 0xfe, 0x65, 0x74, 0xbf, 0x7f, 0x99, 0xc0, 0xd0, 0xe0, 0xd7, 0x12, 0x15, 0x47, 0x06, 0xae,
|
||||
0x69, 0xc3, 0xe9, 0x14, 0xfc, 0xe6, 0x1e, 0xca, 0x30, 0x3f, 0x24, 0xd1, 0x59, 0xd2, 0x5c, 0xed,
|
||||
0x43, 0xfd, 0xf4, 0x97, 0x4f, 0xf6, 0xbf, 0x83, 0xce, 0xfe, 0x10, 0x90, 0x1f, 0x87, 0x80, 0xfc,
|
||||
0x3a, 0x04, 0x9d, 0xbb, 0x3f, 0x01, 0x49, 0xfb, 0x2e, 0xf8, 0xf5, 0xbf, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0x2a, 0xae, 0x24, 0x77, 0xb3, 0x02, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:generate go run ../../script/protofmt.go local.proto
|
||||
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. local.proto
|
||||
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. local.proto
|
||||
|
||||
package discover
|
||||
|
||||
|
||||
@@ -382,20 +382,21 @@ var (
|
||||
)
|
||||
|
||||
var fileDescriptorLocal = []byte{
|
||||
// 235 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0xc9, 0x4f, 0x4e,
|
||||
0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x48, 0xc9, 0x2c, 0x4e, 0xce, 0x2f, 0x4b,
|
||||
0x2d, 0x92, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf,
|
||||
0x4f, 0xcf, 0xd7, 0x07, 0x2b, 0x48, 0x2a, 0x4d, 0x03, 0xf3, 0xc0, 0x1c, 0x30, 0x0b, 0xa2, 0x51,
|
||||
0x69, 0x2d, 0x23, 0x17, 0x87, 0x63, 0x5e, 0x5e, 0x7e, 0x69, 0x5e, 0x72, 0xaa, 0x50, 0x10, 0x17,
|
||||
0x53, 0x66, 0x8a, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x8f, 0x93, 0xd3, 0x89, 0x7b, 0xf2, 0x0c, 0xb7,
|
||||
0xee, 0xc9, 0x9b, 0x20, 0x99, 0x57, 0x5c, 0x99, 0x97, 0x5c, 0x92, 0x91, 0x99, 0x97, 0x8e, 0xc4,
|
||||
0xca, 0xc9, 0x4c, 0x82, 0x58, 0x91, 0x9c, 0x9f, 0xa3, 0xe7, 0x92, 0x5a, 0x96, 0x99, 0x9c, 0xea,
|
||||
0xe9, 0xf2, 0xe8, 0x9e, 0x3c, 0x93, 0xa7, 0x4b, 0x10, 0xd0, 0x34, 0x21, 0x19, 0x2e, 0xce, 0xc4,
|
||||
0x94, 0x94, 0xa2, 0xd4, 0xe2, 0xe2, 0xd4, 0x62, 0x09, 0x26, 0x05, 0x66, 0x0d, 0xce, 0x20, 0x84,
|
||||
0x80, 0x90, 0x3e, 0x17, 0x77, 0x66, 0x5e, 0x71, 0x49, 0x22, 0xd0, 0xf6, 0x78, 0xa0, 0xd5, 0xcc,
|
||||
0x40, 0xab, 0x99, 0x9d, 0xf8, 0x80, 0xda, 0xb9, 0x3c, 0xa1, 0xc2, 0x40, 0x63, 0xb8, 0x60, 0x4a,
|
||||
0x3c, 0x53, 0x9c, 0x44, 0x4e, 0x3c, 0x94, 0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0x02, 0x10, 0x3f,
|
||||
0x78, 0x24, 0xc7, 0xb0, 0xe0, 0xb1, 0x1c, 0x63, 0x12, 0x1b, 0xd8, 0x05, 0xc6, 0x80, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00, 0x00,
|
||||
// 241 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x4f, 0x4e, 0x84, 0x30,
|
||||
0x14, 0xc6, 0x29, 0x24, 0x66, 0xa6, 0x63, 0x5c, 0x10, 0x17, 0xc4, 0x98, 0x42, 0x5c, 0xb1, 0x11,
|
||||
0x16, 0x7a, 0x01, 0x09, 0x9b, 0x6e, 0xb9, 0x80, 0x81, 0xb6, 0x32, 0x2f, 0xc1, 0x3e, 0x43, 0x61,
|
||||
0x12, 0x6f, 0xe3, 0x05, 0xbc, 0x07, 0x4b, 0xd7, 0x2e, 0x1a, 0xad, 0x17, 0x31, 0xe9, 0x68, 0x86,
|
||||
0xdd, 0xf7, 0xfd, 0xf2, 0x7b, 0x7f, 0xe8, 0x6e, 0x40, 0xd1, 0x0e, 0xc5, 0xcb, 0x88, 0x13, 0xc6,
|
||||
0x1b, 0x09, 0x46, 0xe0, 0x41, 0x8d, 0x57, 0xb7, 0x3d, 0x4c, 0xfb, 0xb9, 0x2b, 0x04, 0x3e, 0x97,
|
||||
0x3d, 0xf6, 0x58, 0x7a, 0xa1, 0x9b, 0x9f, 0x7c, 0xf3, 0xc5, 0xa7, 0xe3, 0xe0, 0xcd, 0x3b, 0xa1,
|
||||
0x9b, 0x07, 0xad, 0x71, 0xd6, 0x42, 0xc5, 0x0d, 0x0d, 0x41, 0x26, 0x24, 0x23, 0xf9, 0x79, 0x55,
|
||||
0x2d, 0x36, 0x0d, 0x3e, 0x6d, 0x7a, 0xbf, 0xda, 0x67, 0x5e, 0xb5, 0x98, 0xf6, 0xa0, 0xfb, 0x55,
|
||||
0x1a, 0xa0, 0x3b, 0x9e, 0x10, 0x38, 0x14, 0xb5, 0x3a, 0x80, 0x50, 0xbc, 0x76, 0x36, 0x0d, 0x79,
|
||||
0xdd, 0x84, 0x20, 0xe3, 0x6b, 0xba, 0x6d, 0xa5, 0x1c, 0x95, 0x31, 0xca, 0x24, 0x61, 0x16, 0xe5,
|
||||
0xdb, 0xe6, 0x04, 0xe2, 0x92, 0xee, 0x40, 0x9b, 0xa9, 0xd5, 0x42, 0x3d, 0x82, 0x4c, 0xa2, 0x8c,
|
||||
0xe4, 0x51, 0x75, 0xe1, 0x6c, 0x4a, 0xf9, 0x1f, 0xe6, 0x75, 0x43, 0xff, 0x15, 0x2e, 0xab, 0xcb,
|
||||
0xe5, 0x9b, 0x05, 0x8b, 0x63, 0xe4, 0xc3, 0x31, 0xf2, 0xe5, 0x58, 0xf0, 0xf6, 0xc3, 0x48, 0x77,
|
||||
0xe6, 0x3f, 0xb8, 0xfb, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ type Model struct {
|
||||
|
||||
folderCfgs map[string]config.FolderConfiguration // folder -> cfg
|
||||
folderFiles map[string]*db.FileSet // folder -> files
|
||||
folderDevices map[string][]protocol.DeviceID // folder -> deviceIDs
|
||||
folderDevices folderDeviceSet // folder -> deviceIDs
|
||||
deviceFolders map[protocol.DeviceID][]string // deviceID -> folders
|
||||
deviceStatRefs map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef
|
||||
folderIgnores map[string]*ignore.Matcher // folder -> matcher object
|
||||
@@ -144,7 +144,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
|
||||
clientVersion: clientVersion,
|
||||
folderCfgs: make(map[string]config.FolderConfiguration),
|
||||
folderFiles: make(map[string]*db.FileSet),
|
||||
folderDevices: make(map[string][]protocol.DeviceID),
|
||||
folderDevices: make(folderDeviceSet),
|
||||
deviceFolders: make(map[protocol.DeviceID][]string),
|
||||
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
|
||||
folderIgnores: make(map[string]*ignore.Matcher),
|
||||
@@ -303,9 +303,8 @@ func (m *Model) addFolderLocked(cfg config.FolderConfiguration) {
|
||||
m.folderCfgs[cfg.ID] = cfg
|
||||
m.folderFiles[cfg.ID] = db.NewFileSet(cfg.ID, m.db)
|
||||
|
||||
m.folderDevices[cfg.ID] = make([]protocol.DeviceID, len(cfg.Devices))
|
||||
for i, device := range cfg.Devices {
|
||||
m.folderDevices[cfg.ID][i] = device.DeviceID
|
||||
for _, device := range cfg.Devices {
|
||||
m.folderDevices.set(device.DeviceID, cfg.ID)
|
||||
m.deviceFolders[device.DeviceID] = append(m.deviceFolders[device.DeviceID], cfg.ID)
|
||||
}
|
||||
|
||||
@@ -335,7 +334,7 @@ func (m *Model) tearDownFolderLocked(folder string) {
|
||||
}
|
||||
|
||||
// Close connections to affected devices
|
||||
for _, dev := range m.folderDevices[folder] {
|
||||
for dev := range m.folderDevices[folder] {
|
||||
if conn, ok := m.conn[dev]; ok {
|
||||
closeRawConn(conn)
|
||||
}
|
||||
@@ -872,80 +871,177 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
|
||||
}
|
||||
}
|
||||
|
||||
var changed bool
|
||||
|
||||
if m.cfg.Devices()[deviceID].Introducer {
|
||||
// This device is an introducer. Go through the announced lists of folders
|
||||
// and devices and add what we are missing.
|
||||
|
||||
for _, folder := range cm.Folders {
|
||||
if _, ok := m.folderDevices[folder.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
nextDevice:
|
||||
for _, device := range folder.Devices {
|
||||
if _, ok := m.cfg.Devices()[device.ID]; !ok {
|
||||
// The device is currently unknown. Add it to the config.
|
||||
|
||||
addresses := []string{"dynamic"}
|
||||
for _, addr := range device.Addresses {
|
||||
if addr != "dynamic" {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
l.Infof("Adding device %v to config (vouched for by introducer %v)", device.ID, deviceID)
|
||||
newDeviceCfg := config.DeviceConfiguration{
|
||||
DeviceID: device.ID,
|
||||
Name: device.Name,
|
||||
Compression: m.cfg.Devices()[deviceID].Compression,
|
||||
Addresses: addresses,
|
||||
CertName: device.CertName,
|
||||
}
|
||||
|
||||
// The introducers' introducers are also our introducers.
|
||||
if device.Introducer {
|
||||
l.Infof("Device %v is now also an introducer", device.ID)
|
||||
newDeviceCfg.Introducer = true
|
||||
}
|
||||
|
||||
m.cfg.SetDevice(newDeviceCfg)
|
||||
changed = true
|
||||
}
|
||||
|
||||
for _, er := range m.deviceFolders[device.ID] {
|
||||
if er == folder.ID {
|
||||
// We already share the folder with this device, so
|
||||
// nothing to do.
|
||||
continue nextDevice
|
||||
}
|
||||
}
|
||||
|
||||
// We don't yet share this folder with this device. Add the device
|
||||
// to sharing list of the folder.
|
||||
|
||||
l.Infof("Adding device %v to share %q (vouched for by introducer %v)", device.ID, folder.ID, deviceID)
|
||||
|
||||
m.deviceFolders[device.ID] = append(m.deviceFolders[device.ID], folder.ID)
|
||||
m.folderDevices[folder.ID] = append(m.folderDevices[folder.ID], device.ID)
|
||||
|
||||
folderCfg := m.cfg.Folders()[folder.ID]
|
||||
folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{
|
||||
DeviceID: device.ID,
|
||||
})
|
||||
m.cfg.SetFolder(folderCfg)
|
||||
|
||||
changed = true
|
||||
}
|
||||
var changed = false
|
||||
if deviceCfg := m.cfg.Devices()[deviceID]; deviceCfg.Introducer {
|
||||
foldersDevices, introduced := m.handleIntroductions(deviceCfg, cm)
|
||||
if introduced {
|
||||
changed = true
|
||||
}
|
||||
// If permitted, check if the introducer has unshare devices/folders with
|
||||
// some of the devices/folders that we know were introduced to us by him.
|
||||
if !deviceCfg.SkipIntroductionRemovals && m.handleDeintroductions(deviceCfg, cm, foldersDevices) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
m.cfg.Save()
|
||||
if err := m.cfg.Save(); err != nil {
|
||||
l.Warnln("Failed to save config", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleIntroductions handles adding devices/shares that are shared by an introducer device
|
||||
func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (folderDeviceSet, bool) {
|
||||
// This device is an introducer. Go through the announced lists of folders
|
||||
// and devices and add what we are missing, remove what we have extra that
|
||||
// has been introducer by the introducer.
|
||||
changed := false
|
||||
|
||||
foldersDevices := make(folderDeviceSet)
|
||||
|
||||
for _, folder := range cm.Folders {
|
||||
// We don't have this folder, skip.
|
||||
if _, ok := m.folderDevices[folder.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Adds devices which we do not have, but the introducer has
|
||||
// for the folders that we have in common. Also, shares folders
|
||||
// with devices that we have in common, yet are currently not sharing
|
||||
// the folder.
|
||||
nextDevice:
|
||||
for _, device := range folder.Devices {
|
||||
foldersDevices.set(device.ID, folder.ID)
|
||||
|
||||
if _, ok := m.cfg.Devices()[device.ID]; !ok {
|
||||
// The device is currently unknown. Add it to the config.
|
||||
m.introduceDevice(device, introducerCfg)
|
||||
changed = true
|
||||
}
|
||||
|
||||
for _, er := range m.deviceFolders[device.ID] {
|
||||
if er == folder.ID {
|
||||
// We already share the folder with this device, so
|
||||
// nothing to do.
|
||||
continue nextDevice
|
||||
}
|
||||
}
|
||||
|
||||
// We don't yet share this folder with this device. Add the device
|
||||
// to sharing list of the folder.
|
||||
m.introduceDeviceToFolder(device, folder, introducerCfg)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
return foldersDevices, changed
|
||||
}
|
||||
|
||||
// handleIntroductions handles removals of devices/shares that are removed by an introducer device
|
||||
func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
|
||||
changed := false
|
||||
foldersIntroducedByOthers := make(folderDeviceSet)
|
||||
|
||||
// Check if we should unshare some folders, if the introducer has unshared them.
|
||||
for _, folderCfg := range m.cfg.Folders() {
|
||||
folderChanged := false
|
||||
for i := 0; i < len(folderCfg.Devices); i++ {
|
||||
if folderCfg.Devices[i].IntroducedBy == introducerCfg.DeviceID {
|
||||
if !foldersDevices.has(folderCfg.Devices[i].DeviceID, folderCfg.ID) {
|
||||
// We could not find that folder shared on the introducer with the device that was introduced to us.
|
||||
// We should follow and unshare aswell.
|
||||
l.Infof("Unsharing folder %q with %v as introducer %v no longer shares the folder with that device", folderCfg.ID, folderCfg.Devices[i].DeviceID, folderCfg.Devices[i].IntroducedBy)
|
||||
folderCfg.Devices = append(folderCfg.Devices[:i], folderCfg.Devices[i+1:]...)
|
||||
i--
|
||||
folderChanged = true
|
||||
}
|
||||
} else {
|
||||
foldersIntroducedByOthers.set(folderCfg.Devices[i].DeviceID, folderCfg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// We've modified the folder, hence update it.
|
||||
if folderChanged {
|
||||
m.cfg.SetFolder(folderCfg)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should remove some devices, if the introducer no longer shares any folder with them.
|
||||
// Yet do not remove if we share other folders that haven't been introduced by the introducer.
|
||||
raw := m.cfg.RawCopy()
|
||||
deviceChanged := false
|
||||
for i := 0; i < len(raw.Devices); i++ {
|
||||
if raw.Devices[i].IntroducedBy == introducerCfg.DeviceID {
|
||||
if !foldersDevices.hasDevice(raw.Devices[i].DeviceID) {
|
||||
if foldersIntroducedByOthers.hasDevice(raw.Devices[i].DeviceID) {
|
||||
l.Infof("Would have removed %v as %v no longer shares any folders, yet there are other folders that are shared with this device that haven't been introduced by this introducer.", raw.Devices[i].DeviceID, raw.Devices[i].IntroducedBy)
|
||||
continue
|
||||
}
|
||||
// The introducer no longer shares any folder with the device, remove the device.
|
||||
l.Infof("Removing device %v as introducer %v no longer shares any folders with that device", raw.Devices[i].DeviceID, raw.Devices[i].IntroducedBy)
|
||||
raw.Devices = append(raw.Devices[:i], raw.Devices[i+1:]...)
|
||||
i--
|
||||
deviceChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've removed a device, replace the config.
|
||||
if deviceChanged {
|
||||
if err := m.cfg.Replace(raw); err != nil {
|
||||
l.Warnln("Failed to save config", err)
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
|
||||
return changed
|
||||
|
||||
}
|
||||
|
||||
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
|
||||
addresses := []string{"dynamic"}
|
||||
for _, addr := range device.Addresses {
|
||||
if addr != "dynamic" {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
l.Infof("Adding device %v to config (vouched for by introducer %v)", device.ID, introducerCfg.DeviceID)
|
||||
newDeviceCfg := config.DeviceConfiguration{
|
||||
DeviceID: device.ID,
|
||||
Name: device.Name,
|
||||
Compression: introducerCfg.Compression,
|
||||
Addresses: addresses,
|
||||
CertName: device.CertName,
|
||||
IntroducedBy: introducerCfg.DeviceID,
|
||||
}
|
||||
|
||||
// The introducers' introducers are also our introducers.
|
||||
if device.Introducer {
|
||||
l.Infof("Device %v is now also an introducer", device.ID)
|
||||
newDeviceCfg.Introducer = true
|
||||
newDeviceCfg.SkipIntroductionRemovals = device.SkipIntroductionRemovals
|
||||
}
|
||||
|
||||
m.cfg.SetDevice(newDeviceCfg)
|
||||
}
|
||||
|
||||
func (m *Model) introduceDeviceToFolder(device protocol.Device, folder protocol.Folder, introducerCfg config.DeviceConfiguration) {
|
||||
l.Infof("Sharing folder %q with %v (vouched for by introducer %v)", folder.ID, device.ID, introducerCfg.DeviceID)
|
||||
|
||||
m.deviceFolders[device.ID] = append(m.deviceFolders[device.ID], folder.ID)
|
||||
m.folderDevices.set(device.ID, folder.ID)
|
||||
|
||||
folderCfg := m.cfg.Folders()[folder.ID]
|
||||
folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{
|
||||
DeviceID: device.ID,
|
||||
IntroducedBy: introducerCfg.DeviceID,
|
||||
})
|
||||
m.cfg.SetFolder(folderCfg)
|
||||
}
|
||||
|
||||
// Closed is called when a connection has been closed
|
||||
func (m *Model) Closed(conn protocol.Connection, err error) {
|
||||
device := conn.ID()
|
||||
@@ -1469,7 +1565,6 @@ func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo)
|
||||
m.fmut.RLock()
|
||||
folderCfg := m.folderCfgs[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
// Fire the LocalChangeDetected event to notify listeners about local updates.
|
||||
m.localChangeDetected(folderCfg, fs)
|
||||
}
|
||||
@@ -1876,7 +1971,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
|
||||
DisableTempIndexes: folderCfg.DisableTempIndexes,
|
||||
}
|
||||
|
||||
for _, device := range m.folderDevices[folder] {
|
||||
for device := range m.folderDevices[folder] {
|
||||
// DeviceID is a value type, but with an underlying array. Copy it
|
||||
// so we don't grab aliases to the same array later on in device[:]
|
||||
device := device
|
||||
@@ -1999,8 +2094,8 @@ func (m *Model) RemoteSequence(folder string) (int64, bool) {
|
||||
}
|
||||
|
||||
var ver int64
|
||||
for _, n := range m.folderDevices[folder] {
|
||||
ver += fs.Sequence(n)
|
||||
for device := range m.folderDevices[folder] {
|
||||
ver += fs.Sequence(device)
|
||||
}
|
||||
|
||||
return ver, true
|
||||
@@ -2070,15 +2165,18 @@ func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly
|
||||
}
|
||||
|
||||
func (m *Model) Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []Availability {
|
||||
// Acquire this lock first, as the value returned from foldersFiles can
|
||||
// get heavily modified on Close()
|
||||
// The slightly unusual locking sequence here is because we need to hold
|
||||
// pmut for the duration (as the value returned from foldersFiles can
|
||||
// get heavily modified on Close()), but also must acquire fmut before
|
||||
// pmut. (The locks can be *released* in any order.)
|
||||
m.fmut.RLock()
|
||||
m.pmut.RLock()
|
||||
defer m.pmut.RUnlock()
|
||||
|
||||
m.fmut.RLock()
|
||||
fs, ok := m.folderFiles[folder]
|
||||
devices := m.folderDevices[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -2091,7 +2189,7 @@ func (m *Model) Availability(folder, file string, version protocol.Vector, block
|
||||
}
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
for device := range devices {
|
||||
if m.deviceDownloads[device].Has(folder, file, version, int32(block.Offset/protocol.BlockSize)) {
|
||||
availabilities = append(availabilities, Availability{ID: device, FromTemporary: true})
|
||||
}
|
||||
@@ -2477,3 +2575,32 @@ func shouldIgnore(file db.FileIntf, matcher *ignore.Matcher, ignoreDelete bool)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// folderDeviceSet is a set of (folder, deviceID) pairs
|
||||
type folderDeviceSet map[string]map[protocol.DeviceID]struct{}
|
||||
|
||||
// set adds the (dev, folder) pair to the set
|
||||
func (s folderDeviceSet) set(dev protocol.DeviceID, folder string) {
|
||||
devs, ok := s[folder]
|
||||
if !ok {
|
||||
devs = make(map[protocol.DeviceID]struct{})
|
||||
s[folder] = devs
|
||||
}
|
||||
devs[dev] = struct{}{}
|
||||
}
|
||||
|
||||
// has returns true if the (dev, folder) pair is in the set
|
||||
func (s folderDeviceSet) has(dev protocol.DeviceID, folder string) bool {
|
||||
_, ok := s[folder][dev]
|
||||
return ok
|
||||
}
|
||||
|
||||
// hasDevice returns true if the device is set on any folder
|
||||
func (s folderDeviceSet) hasDevice(dev protocol.DeviceID) bool {
|
||||
for _, devices := range s {
|
||||
if _, ok := devices[dev]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -478,6 +478,376 @@ func TestClusterConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntroducer(t *testing.T) {
|
||||
var introducedByAnyone protocol.DeviceID
|
||||
|
||||
// LocalDeviceID is a magic value meaning don't check introducer
|
||||
contains := func(cfg config.FolderConfiguration, id, introducedBy protocol.DeviceID) bool {
|
||||
for _, dev := range cfg.Devices {
|
||||
if dev.DeviceID.Equals(id) {
|
||||
if introducedBy.Equals(introducedByAnyone) {
|
||||
return true
|
||||
}
|
||||
return introducedBy.Equals(introducedBy)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
newState := func(cfg config.Configuration) (*config.Wrapper, *Model) {
|
||||
db := db.OpenMemory()
|
||||
|
||||
wcfg := config.Wrap("/tmp/test", cfg)
|
||||
|
||||
m := NewModel(wcfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
for _, folder := range cfg.Folders {
|
||||
m.AddFolder(folder)
|
||||
}
|
||||
m.ServeBackground()
|
||||
m.AddConnection(connections.Connection{
|
||||
IntermediateConnection: connections.IntermediateConnection{
|
||||
Conn: tls.Client(&fakeConn{}, nil),
|
||||
},
|
||||
Connection: &FakeConnection{
|
||||
id: device1,
|
||||
},
|
||||
}, protocol.HelloResult{})
|
||||
return wcfg, m
|
||||
}
|
||||
|
||||
wcfg, m := newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: true,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []protocol.Device{
|
||||
{
|
||||
ID: device2,
|
||||
Introducer: true,
|
||||
SkipIntroductionRemovals: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if newDev, ok := wcfg.Device(device2); !ok || !newDev.Introducer || !newDev.SkipIntroductionRemovals {
|
||||
t.Error("devie 2 missing or wrong flags")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder1"], device2, device1) {
|
||||
t.Error("expected folder 1 to have device2 introduced by device 1")
|
||||
}
|
||||
|
||||
wcfg, m = newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: true,
|
||||
},
|
||||
{
|
||||
DeviceID: device2,
|
||||
IntroducedBy: device1,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []protocol.Device{
|
||||
{
|
||||
ID: device2,
|
||||
Introducer: true,
|
||||
SkipIntroductionRemovals: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Should not get introducer, as it's already unset, and it's an existing device.
|
||||
if newDev, ok := wcfg.Device(device2); !ok || newDev.Introducer || newDev.SkipIntroductionRemovals {
|
||||
t.Error("device 2 missing or changed flags")
|
||||
}
|
||||
|
||||
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 to be removed from folder 1")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder2"], device2, device1) {
|
||||
t.Error("expected device 2 to be added to folder 2")
|
||||
}
|
||||
|
||||
wcfg, m = newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: true,
|
||||
},
|
||||
{
|
||||
DeviceID: device2,
|
||||
IntroducedBy: device1,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{})
|
||||
|
||||
if _, ok := wcfg.Device(device2); ok {
|
||||
t.Error("device 2 should have been removed")
|
||||
}
|
||||
|
||||
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 to be removed from folder 1")
|
||||
}
|
||||
|
||||
if contains(wcfg.Folders()["folder2"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 to be removed from folder 2")
|
||||
}
|
||||
|
||||
// Two cases when removals should not happen
|
||||
// 1. Introducer flag no longer set on device
|
||||
|
||||
wcfg, m = newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: false,
|
||||
},
|
||||
{
|
||||
DeviceID: device2,
|
||||
IntroducedBy: device1,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{})
|
||||
|
||||
if _, ok := wcfg.Device(device2); !ok {
|
||||
t.Error("device 2 should not have been removed")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder1"], device2, device1) {
|
||||
t.Error("expected device 2 not to be removed from folder 1")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder2"], device2, device1) {
|
||||
t.Error("expected device 2 not to be removed from folder 2")
|
||||
}
|
||||
|
||||
// 2. SkipIntroductionRemovals is set
|
||||
|
||||
wcfg, m = newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: true,
|
||||
SkipIntroductionRemovals: true,
|
||||
},
|
||||
{
|
||||
DeviceID: device2,
|
||||
IntroducedBy: device1,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{
|
||||
Folders: []protocol.Folder{
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []protocol.Device{
|
||||
{
|
||||
ID: device2,
|
||||
Introducer: true,
|
||||
SkipIntroductionRemovals: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if _, ok := wcfg.Device(device2); !ok {
|
||||
t.Error("device 2 should not have been removed")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder1"], device2, device1) {
|
||||
t.Error("expected device 2 not to be removed from folder 1")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder2"], device2, device1) {
|
||||
t.Error("expected device 2 not to be added to folder 2")
|
||||
}
|
||||
|
||||
// Test device not being removed as it's shared without an introducer.
|
||||
|
||||
wcfg, m = newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: true,
|
||||
},
|
||||
{
|
||||
DeviceID: device2,
|
||||
IntroducedBy: device1,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{})
|
||||
|
||||
if _, ok := wcfg.Device(device2); !ok {
|
||||
t.Error("device 2 should not have been removed")
|
||||
}
|
||||
|
||||
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 to be removed from folder 1")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder2"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 not to be removed from folder 2")
|
||||
}
|
||||
|
||||
// Test device not being removed as it's shared by a different introducer.
|
||||
|
||||
wcfg, m = newState(config.Configuration{
|
||||
Devices: []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: device1,
|
||||
Introducer: true,
|
||||
},
|
||||
{
|
||||
DeviceID: device2,
|
||||
IntroducedBy: device1,
|
||||
},
|
||||
},
|
||||
Folders: []config.FolderConfiguration{
|
||||
{
|
||||
ID: "folder1",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: device1},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "folder2",
|
||||
Devices: []config.FolderDeviceConfiguration{
|
||||
{DeviceID: device1},
|
||||
{DeviceID: device2, IntroducedBy: protocol.LocalDeviceID},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
m.ClusterConfig(device1, protocol.ClusterConfig{})
|
||||
|
||||
if _, ok := wcfg.Device(device2); !ok {
|
||||
t.Error("device 2 should not have been removed")
|
||||
}
|
||||
|
||||
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 to be removed from folder 1")
|
||||
}
|
||||
|
||||
if !contains(wcfg.Folders()["folder2"], device2, introducedByAnyone) {
|
||||
t.Error("expected device 2 not to be removed from folder 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnores(t *testing.T) {
|
||||
arrEqual := func(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
@@ -1623,7 +1993,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
|
||||
t.Error("folder missing?")
|
||||
}
|
||||
|
||||
for _, id := range fdevs {
|
||||
for id := range fdevs {
|
||||
if id == device2 {
|
||||
t.Error("still there")
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter {
|
||||
mut: sync.NewMutex(),
|
||||
}
|
||||
|
||||
t.CommitConfiguration(config.Configuration{}, cfg.Raw())
|
||||
t.CommitConfiguration(config.Configuration{}, cfg.RawCopy())
|
||||
cfg.Subscribe(t)
|
||||
|
||||
return t
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Holder interface {
|
||||
Holder() (string, int)
|
||||
type Holdable interface {
|
||||
Holders() string
|
||||
}
|
||||
|
||||
func newDeadlockDetector(timeout time.Duration) *deadlockDetector {
|
||||
@@ -49,9 +49,8 @@ func (d *deadlockDetector) Watch(name string, mut sync.Locker) {
|
||||
if r := <-ok; !r {
|
||||
msg := fmt.Sprintf("deadlock detected at %s", name)
|
||||
for otherName, otherMut := range d.lockers {
|
||||
if otherHolder, ok := otherMut.(Holder); ok {
|
||||
holder, goid := otherHolder.Holder()
|
||||
msg += fmt.Sprintf("\n %s = current holder: %s at routine %d", otherName, holder, goid)
|
||||
if otherHolder, ok := otherMut.(Holdable); ok {
|
||||
msg += "\n===" + otherName + "===\n" + otherHolder.Holders()
|
||||
}
|
||||
}
|
||||
panic(msg)
|
||||
|
||||
@@ -255,14 +255,15 @@ func (*Folder) ProtoMessage() {}
|
||||
func (*Folder) Descriptor() ([]byte, []int) { return fileDescriptorBep, []int{3} }
|
||||
|
||||
type Device struct {
|
||||
ID DeviceID `protobuf:"bytes,1,opt,name=id,proto3,customtype=DeviceID" json:"id"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Addresses []string `protobuf:"bytes,3,rep,name=addresses" json:"addresses,omitempty"`
|
||||
Compression Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression,omitempty"`
|
||||
CertName string `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"cert_name,omitempty"`
|
||||
MaxSequence int64 `protobuf:"varint,6,opt,name=max_sequence,json=maxSequence,proto3" json:"max_sequence,omitempty"`
|
||||
Introducer bool `protobuf:"varint,7,opt,name=introducer,proto3" json:"introducer,omitempty"`
|
||||
IndexID IndexID `protobuf:"varint,8,opt,name=index_id,json=indexId,proto3,customtype=IndexID" json:"index_id"`
|
||||
ID DeviceID `protobuf:"bytes,1,opt,name=id,proto3,customtype=DeviceID" json:"id"`
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Addresses []string `protobuf:"bytes,3,rep,name=addresses" json:"addresses,omitempty"`
|
||||
Compression Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression,omitempty"`
|
||||
CertName string `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"cert_name,omitempty"`
|
||||
MaxSequence int64 `protobuf:"varint,6,opt,name=max_sequence,json=maxSequence,proto3" json:"max_sequence,omitempty"`
|
||||
Introducer bool `protobuf:"varint,7,opt,name=introducer,proto3" json:"introducer,omitempty"`
|
||||
IndexID IndexID `protobuf:"varint,8,opt,name=index_id,json=indexId,proto3,customtype=IndexID" json:"index_id"`
|
||||
SkipIntroductionRemovals bool `protobuf:"varint,9,opt,name=skip_introduction_removals,json=skipIntroductionRemovals,proto3" json:"skip_introduction_removals,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Device) Reset() { *m = Device{} }
|
||||
@@ -681,6 +682,16 @@ func (m *Device) MarshalTo(data []byte) (int, error) {
|
||||
i++
|
||||
i = encodeVarintBep(data, i, uint64(m.IndexID))
|
||||
}
|
||||
if m.SkipIntroductionRemovals {
|
||||
data[i] = 0x48
|
||||
i++
|
||||
if m.SkipIntroductionRemovals {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
@@ -1303,6 +1314,9 @@ func (m *Device) ProtoSize() (n int) {
|
||||
if m.IndexID != 0 {
|
||||
n += 1 + sovBep(uint64(m.IndexID))
|
||||
}
|
||||
if m.SkipIntroductionRemovals {
|
||||
n += 2
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -2282,6 +2296,26 @@ func (m *Device) Unmarshal(data []byte) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 9:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SkipIntroductionRemovals", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowBep
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.SkipIntroductionRemovals = bool(v != 0)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipBep(data[iNdEx:])
|
||||
@@ -3953,105 +3987,110 @@ var (
|
||||
)
|
||||
|
||||
var fileDescriptorBep = []byte{
|
||||
// 1598 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0xcd, 0x6e, 0xdb, 0xc6,
|
||||
0x16, 0xb6, 0x24, 0xea, 0x6f, 0x24, 0x3b, 0xf2, 0xc4, 0x71, 0x74, 0x19, 0xc7, 0xf6, 0x65, 0x92,
|
||||
0x7b, 0x7d, 0x85, 0x1b, 0xe7, 0xde, 0xa4, 0x6d, 0x80, 0x02, 0x2d, 0x20, 0x4b, 0xb4, 0x23, 0x44,
|
||||
0xa6, 0x14, 0x4a, 0x72, 0x9a, 0x2e, 0x2a, 0x48, 0xe2, 0x48, 0x26, 0x42, 0x71, 0x54, 0x52, 0x4a,
|
||||
0xe2, 0x3e, 0x42, 0xfb, 0x02, 0xdd, 0x14, 0x08, 0xba, 0xeb, 0xb6, 0xe8, 0x43, 0x64, 0x19, 0x64,
|
||||
0xd9, 0x45, 0xd0, 0xa6, 0x9b, 0xbe, 0x40, 0xf7, 0x3d, 0x3c, 0x43, 0x52, 0x94, 0x7f, 0x8a, 0x2c,
|
||||
0xba, 0x30, 0x34, 0x73, 0xce, 0x37, 0xe7, 0xcc, 0xf9, 0xce, 0xcf, 0xd0, 0x24, 0xdb, 0x67, 0x93,
|
||||
0xdd, 0x89, 0xc3, 0xa7, 0x9c, 0x66, 0xf0, 0x67, 0xc0, 0x2d, 0xf9, 0xf6, 0xc8, 0x9c, 0x1e, 0xcf,
|
||||
0xfa, 0xbb, 0x03, 0x3e, 0xbe, 0x33, 0xe2, 0x23, 0x7e, 0x07, 0x35, 0xfd, 0xd9, 0x10, 0x77, 0xb8,
|
||||
0xc1, 0x95, 0x38, 0xa8, 0x4c, 0x48, 0xf2, 0x01, 0xb3, 0x2c, 0x4e, 0xb7, 0x48, 0xce, 0x60, 0xcf,
|
||||
0xcc, 0x01, 0xeb, 0xda, 0xbd, 0x31, 0x2b, 0xc6, 0xb6, 0x63, 0x3b, 0x59, 0x9d, 0x08, 0x91, 0x06,
|
||||
0x12, 0x0f, 0x30, 0xb0, 0x4c, 0x66, 0x4f, 0x05, 0x20, 0x2e, 0x00, 0x42, 0x84, 0x80, 0x5b, 0x64,
|
||||
0xc5, 0x07, 0x3c, 0x63, 0x8e, 0x6b, 0x72, 0xbb, 0x98, 0x40, 0xcc, 0xb2, 0x90, 0x1e, 0x09, 0xa1,
|
||||
0xe2, 0x92, 0xd4, 0x03, 0xd6, 0x33, 0x98, 0x43, 0xff, 0x43, 0xa4, 0xe9, 0xc9, 0x44, 0xf8, 0x5a,
|
||||
0xb9, 0x7b, 0x65, 0x37, 0x88, 0x61, 0xf7, 0x90, 0xb9, 0x6e, 0x6f, 0xc4, 0xda, 0xa0, 0xd4, 0x11,
|
||||
0x42, 0x3f, 0x05, 0xe7, 0x7c, 0x3c, 0x71, 0x40, 0xe1, 0x19, 0x8e, 0xe3, 0x89, 0x8d, 0x33, 0x27,
|
||||
0x2a, 0x73, 0x8c, 0x1e, 0x3d, 0xa0, 0x94, 0xc9, 0x72, 0xc5, 0x9a, 0xb9, 0x53, 0xe6, 0x54, 0xb8,
|
||||
0x3d, 0x34, 0x47, 0xf4, 0x7f, 0x24, 0x3d, 0xe4, 0x16, 0xdc, 0xc2, 0x05, 0xf7, 0x89, 0x9d, 0xdc,
|
||||
0xdd, 0xc2, 0xdc, 0xd8, 0x3e, 0x2a, 0xf6, 0xa4, 0x57, 0x6f, 0xb7, 0x96, 0xf4, 0x00, 0xa6, 0x7c,
|
||||
0x13, 0x27, 0x29, 0xa1, 0xa1, 0xeb, 0x24, 0x6e, 0x1a, 0x82, 0xa2, 0xbd, 0xd4, 0xbb, 0xb7, 0x5b,
|
||||
0xf1, 0x5a, 0x55, 0x07, 0x09, 0x5d, 0x23, 0x49, 0xab, 0xd7, 0x67, 0x96, 0x4f, 0x8e, 0xd8, 0xd0,
|
||||
0x6b, 0x24, 0xeb, 0x40, 0xc0, 0x5d, 0x6e, 0x5b, 0x27, 0x48, 0x49, 0x46, 0xcf, 0x78, 0x82, 0x06,
|
||||
0xec, 0xe9, 0x6d, 0x42, 0xcd, 0x91, 0xcd, 0x1d, 0xd6, 0x9d, 0x30, 0x67, 0x6c, 0xe2, 0x6d, 0xdd,
|
||||
0xa2, 0x84, 0xa8, 0x55, 0xa1, 0x69, 0xce, 0x15, 0xf4, 0x06, 0x59, 0xf6, 0xe1, 0x06, 0xb3, 0xd8,
|
||||
0x94, 0x15, 0x93, 0x88, 0xcc, 0x0b, 0x61, 0x15, 0x65, 0x10, 0xdb, 0x9a, 0x61, 0xba, 0xbd, 0xbe,
|
||||
0xc5, 0xba, 0x53, 0x36, 0x9e, 0x74, 0x4d, 0xdb, 0x60, 0x2f, 0x98, 0x5b, 0x4c, 0x21, 0x96, 0xfa,
|
||||
0xba, 0x36, 0xa8, 0x6a, 0x42, 0xe3, 0xb1, 0x21, 0x32, 0xed, 0x16, 0x0b, 0xa7, 0xd9, 0xa8, 0xa2,
|
||||
0x22, 0x60, 0xc3, 0x87, 0x29, 0x3f, 0x02, 0x1b, 0x42, 0x43, 0xff, 0x15, 0xb2, 0x91, 0xdf, 0x5b,
|
||||
0xf7, 0x50, 0x3f, 0xbf, 0xdd, 0xca, 0x08, 0x5d, 0xad, 0x1a, 0x61, 0x87, 0x12, 0x29, 0x52, 0x39,
|
||||
0xb8, 0xa6, 0x1b, 0x24, 0xdb, 0x33, 0x0c, 0x2f, 0x4b, 0xe0, 0x3a, 0x01, 0xae, 0xb3, 0xfa, 0x5c,
|
||||
0x40, 0xef, 0x2f, 0x66, 0x5d, 0x3a, 0x5d, 0x27, 0x17, 0xa5, 0xdb, 0xa3, 0x7c, 0xc0, 0x1c, 0xbf,
|
||||
0x52, 0x93, 0xe8, 0x2f, 0xe3, 0x09, 0xb0, 0x4e, 0xff, 0x49, 0xf2, 0xe3, 0xde, 0x8b, 0xae, 0xcb,
|
||||
0xbe, 0x9c, 0x31, 0x7b, 0xc0, 0x90, 0x96, 0x84, 0x9e, 0x03, 0x59, 0xcb, 0x17, 0xd1, 0x4d, 0x42,
|
||||
0x4c, 0x7b, 0xea, 0x70, 0x63, 0x06, 0xa7, 0x8a, 0x69, 0xe4, 0x2d, 0x22, 0xa1, 0x1f, 0x92, 0x0c,
|
||||
0x92, 0xda, 0x85, 0xc0, 0x33, 0xa0, 0x95, 0xf6, 0x64, 0x3f, 0xf0, 0x34, 0x52, 0x8a, 0x71, 0x07,
|
||||
0x4b, 0x3d, 0x8d, 0xd8, 0x9a, 0xa1, 0x34, 0x48, 0x12, 0x65, 0x50, 0x40, 0x29, 0x51, 0x56, 0x7e,
|
||||
0x9f, 0xf9, 0x3b, 0xba, 0x4b, 0x92, 0x43, 0xd3, 0x02, 0x2a, 0xe2, 0x98, 0x05, 0x1a, 0xa9, 0x49,
|
||||
0x10, 0xd7, 0xec, 0x21, 0xf7, 0xf3, 0x20, 0x60, 0x4a, 0x87, 0xe4, 0xd0, 0x60, 0x67, 0x62, 0xf4,
|
||||
0x20, 0xf1, 0x7f, 0x97, 0xd9, 0xef, 0x13, 0x24, 0x13, 0x68, 0xc2, 0xb4, 0xc5, 0x22, 0x69, 0x2b,
|
||||
0xf9, 0x9d, 0x2b, 0xfa, 0x70, 0xfd, 0xac, 0xbd, 0x48, 0xeb, 0xc2, 0x79, 0xd7, 0xfc, 0x8a, 0x61,
|
||||
0xe5, 0x27, 0x74, 0x5c, 0xd3, 0x6d, 0x92, 0x3b, 0x5d, 0xee, 0xcb, 0x7a, 0x54, 0x44, 0xaf, 0x13,
|
||||
0x32, 0xe6, 0x86, 0x39, 0x34, 0x99, 0xd1, 0x75, 0x31, 0x85, 0x09, 0x3d, 0x1b, 0x48, 0x5a, 0xb4,
|
||||
0xe8, 0x15, 0xac, 0x57, 0xec, 0x86, 0x5f, 0xd5, 0xc1, 0xd6, 0xd3, 0x98, 0xf6, 0xb3, 0x9e, 0x05,
|
||||
0x99, 0x11, 0x79, 0x0b, 0xb6, 0xde, 0x7c, 0xb2, 0xf9, 0x42, 0x9b, 0x65, 0x10, 0xb0, 0x6c, 0xf3,
|
||||
0x68, 0x8b, 0x41, 0x2f, 0x04, 0xf3, 0x2b, 0x0b, 0xfa, 0x85, 0x5e, 0x38, 0x62, 0x83, 0x29, 0x0f,
|
||||
0x27, 0x83, 0x0f, 0xa3, 0x32, 0xc9, 0x84, 0xc5, 0x44, 0xf0, 0xa6, 0xe1, 0xde, 0x9b, 0x9a, 0x61,
|
||||
0x1c, 0xe0, 0x31, 0x07, 0xea, 0xa4, 0x1e, 0x86, 0xa6, 0xb9, 0xf4, 0xff, 0x24, 0xb5, 0x67, 0xf1,
|
||||
0xc1, 0xd3, 0xa0, 0xf3, 0x2e, 0xcf, 0xbd, 0xa1, 0x3c, 0x92, 0x9d, 0x54, 0x1f, 0x81, 0x1f, 0x4b,
|
||||
0xdf, 0xbe, 0xdc, 0x5a, 0x52, 0x1e, 0x91, 0x6c, 0x08, 0xf0, 0x32, 0xcf, 0x87, 0x43, 0x97, 0x4d,
|
||||
0x31, 0x4d, 0x09, 0xdd, 0xdf, 0x85, 0xe4, 0xc7, 0xd1, 0xaf, 0x20, 0x1f, 0x64, 0xc7, 0x3d, 0xf7,
|
||||
0x18, 0x13, 0x92, 0xd7, 0x71, 0xed, 0x9b, 0xfc, 0x84, 0xa4, 0x44, 0x84, 0xf4, 0x1e, 0xc9, 0x0c,
|
||||
0xf8, 0xcc, 0x9e, 0xce, 0xe7, 0xe3, 0x6a, 0xb4, 0xed, 0x50, 0xe3, 0xdf, 0x2a, 0x04, 0x2a, 0xfb,
|
||||
0x24, 0xed, 0xab, 0x80, 0xeb, 0x60, 0x26, 0x48, 0x7b, 0x57, 0x82, 0xd6, 0x68, 0x1d, 0x73, 0x67,
|
||||
0xba, 0x30, 0x12, 0x60, 0x60, 0x42, 0x6e, 0x66, 0xe2, 0x7e, 0x92, 0x2e, 0x36, 0xca, 0x4f, 0x31,
|
||||
0x92, 0xd6, 0x3d, 0x02, 0xdd, 0x69, 0x64, 0xd4, 0x26, 0x17, 0x46, 0xed, 0xbc, 0xd4, 0xe3, 0x0b,
|
||||
0xa5, 0x1e, 0x54, 0x6b, 0x22, 0x52, 0xad, 0x73, 0x72, 0xa4, 0x73, 0xc9, 0x49, 0x9e, 0x43, 0x4e,
|
||||
0x6a, 0x4e, 0x8e, 0x57, 0x38, 0x43, 0x87, 0x8f, 0x71, 0x98, 0x72, 0xa7, 0xe7, 0x9c, 0xf8, 0x95,
|
||||
0xb5, 0xec, 0x49, 0xdb, 0x81, 0x50, 0xe9, 0x92, 0x8c, 0xce, 0xdc, 0x09, 0xd4, 0x10, 0xbb, 0xf0,
|
||||
0xda, 0x60, 0x1e, 0x3a, 0xb5, 0x87, 0x97, 0x06, 0xf3, 0xde, 0x9a, 0xfe, 0x9b, 0x48, 0x03, 0x6e,
|
||||
0x88, 0x2b, 0xaf, 0x44, 0xf3, 0xaf, 0x3a, 0x0e, 0x87, 0xf7, 0xca, 0x80, 0x4e, 0xf2, 0x00, 0xf0,
|
||||
0x56, 0x17, 0xaa, 0xfc, 0xb9, 0x6d, 0xf1, 0x9e, 0xd1, 0x74, 0xf8, 0xc8, 0x1b, 0x76, 0x17, 0xb6,
|
||||
0x7c, 0x95, 0xa4, 0x67, 0x38, 0x14, 0x82, 0xa6, 0xbf, 0xb9, 0xd8, 0xa4, 0xa7, 0x0d, 0x89, 0x09,
|
||||
0x12, 0x54, 0xb6, 0x7f, 0x54, 0x79, 0x13, 0x23, 0xf2, 0xc5, 0x68, 0x5a, 0x23, 0x39, 0x81, 0xec,
|
||||
0x46, 0xde, 0xf1, 0x9d, 0xf7, 0x71, 0x84, 0xf3, 0x81, 0xcc, 0xc2, 0xf5, 0xb9, 0x8f, 0x43, 0xa4,
|
||||
0x13, 0x13, 0xef, 0xd7, 0x89, 0xf0, 0x3c, 0x62, 0x8f, 0x84, 0x4f, 0x9e, 0x04, 0xb1, 0x27, 0xf5,
|
||||
0x7c, 0x5f, 0x34, 0x0a, 0xca, 0x94, 0x14, 0x91, 0x9a, 0xa6, 0x3d, 0x52, 0xb6, 0x48, 0xb2, 0x62,
|
||||
0x71, 0x4c, 0x56, 0x0a, 0xde, 0x63, 0x17, 0xdc, 0xf8, 0x1c, 0x8a, 0x5d, 0xe9, 0x4d, 0x9c, 0xe4,
|
||||
0x22, 0x9f, 0x22, 0x70, 0x9f, 0x95, 0x4a, 0xbd, 0xd3, 0x6a, 0xab, 0x7a, 0xb7, 0xd2, 0xd0, 0xf6,
|
||||
0x6b, 0x07, 0x85, 0x25, 0x79, 0xe3, 0xeb, 0xef, 0xb6, 0x8b, 0xe3, 0x39, 0x68, 0xf1, 0x2b, 0x03,
|
||||
0x5c, 0xd4, 0xb4, 0xaa, 0xfa, 0x59, 0x21, 0x26, 0xaf, 0x01, 0xb0, 0x10, 0x01, 0x8a, 0x87, 0xe0,
|
||||
0xbf, 0x24, 0x8f, 0x80, 0x6e, 0xa7, 0x59, 0x2d, 0xb7, 0xd5, 0x42, 0x5c, 0x96, 0x01, 0xb7, 0x7e,
|
||||
0x1a, 0xe7, 0xf3, 0x7d, 0x03, 0xfa, 0x42, 0x7d, 0xd4, 0x51, 0x5b, 0xed, 0x42, 0x42, 0x5e, 0x07,
|
||||
0x20, 0x8d, 0x00, 0x83, 0x8e, 0xb9, 0x05, 0x65, 0xa8, 0xb6, 0x9a, 0x0d, 0xad, 0xa5, 0x16, 0x24,
|
||||
0xf9, 0x2a, 0xa0, 0x2e, 0x2f, 0xa0, 0xfc, 0x0a, 0xfd, 0x88, 0xac, 0x56, 0x1b, 0x8f, 0xb5, 0x7a,
|
||||
0xa3, 0x5c, 0xed, 0x36, 0xf5, 0xc6, 0x01, 0x9c, 0x69, 0x15, 0x92, 0xf2, 0x16, 0xe0, 0xaf, 0x45,
|
||||
0xf0, 0x67, 0x0a, 0xee, 0x3a, 0xb0, 0x57, 0xd3, 0x0e, 0x0a, 0x29, 0xf9, 0x32, 0x40, 0x2f, 0x45,
|
||||
0xa0, 0x1e, 0xa9, 0x5e, 0xc4, 0x95, 0x7a, 0x03, 0x5c, 0xa7, 0xcf, 0x44, 0x8c, 0x64, 0x97, 0xbe,
|
||||
0x20, 0xf4, 0xec, 0xc7, 0x1a, 0xbd, 0x49, 0x24, 0xad, 0xa1, 0xa9, 0x40, 0x28, 0xc6, 0x7f, 0x16,
|
||||
0xa1, 0x71, 0x9b, 0x51, 0x85, 0x24, 0xea, 0x9f, 0x7f, 0x00, 0x64, 0xfe, 0x03, 0x40, 0x57, 0xce,
|
||||
0x82, 0x40, 0x59, 0xe2, 0x24, 0x17, 0x35, 0xac, 0x90, 0xcc, 0xa1, 0xda, 0x2e, 0x03, 0xb9, 0x65,
|
||||
0x30, 0x8e, 0x57, 0x0a, 0xd4, 0x87, 0x6c, 0xda, 0xc3, 0x06, 0xdc, 0x20, 0x49, 0x4d, 0x3d, 0x52,
|
||||
0x75, 0x30, 0xbc, 0x0a, 0x80, 0xe5, 0x00, 0xa0, 0x31, 0xa8, 0x2b, 0xf8, 0x16, 0x48, 0x95, 0xeb,
|
||||
0x8f, 0xcb, 0x4f, 0x5a, 0x90, 0x1c, 0x0a, 0xea, 0x95, 0x40, 0x5d, 0xb6, 0x9e, 0xf7, 0x4e, 0xdc,
|
||||
0xd2, 0x1f, 0x31, 0x92, 0x8f, 0x3e, 0x7b, 0x70, 0x40, 0xda, 0xaf, 0xd5, 0xd5, 0xc0, 0x5d, 0x54,
|
||||
0xe7, 0xad, 0xe9, 0x0e, 0xc9, 0x56, 0x6b, 0xba, 0x5a, 0x69, 0x37, 0xf4, 0x27, 0x41, 0x2c, 0x51,
|
||||
0x50, 0xd5, 0x74, 0xb0, 0xb8, 0xbd, 0x8f, 0xc3, 0x7c, 0xeb, 0xc9, 0x61, 0xbd, 0xa6, 0x3d, 0xec,
|
||||
0xa2, 0xc5, 0xb8, 0x7c, 0x0d, 0xc0, 0x57, 0xa3, 0xe0, 0xd6, 0xc9, 0xd8, 0x32, 0xed, 0xa7, 0x68,
|
||||
0xf8, 0x3e, 0x59, 0x0d, 0xe0, 0x73, 0x07, 0x09, 0x79, 0x1b, 0xce, 0x6c, 0x9c, 0x73, 0x66, 0xee,
|
||||
0xe7, 0x1e, 0xb9, 0x14, 0x1c, 0xec, 0x68, 0x0f, 0x35, 0x28, 0x0b, 0xa8, 0x9c, 0x4d, 0x38, 0x26,
|
||||
0x9f, 0x73, 0xac, 0x63, 0x3f, 0xb5, 0xa1, 0x28, 0x4a, 0x3f, 0xc4, 0x48, 0x36, 0x9c, 0x50, 0x1e,
|
||||
0xcf, 0x5a, 0xa3, 0xab, 0xea, 0x7a, 0x43, 0x0f, 0x02, 0x0f, 0x95, 0x1a, 0xc7, 0x25, 0x7c, 0x78,
|
||||
0xa5, 0x0f, 0x54, 0x4d, 0xd5, 0x6b, 0x95, 0xa0, 0x1f, 0x42, 0xc8, 0x01, 0xb3, 0x99, 0x63, 0x0e,
|
||||
0xe0, 0x5f, 0x82, 0x3c, 0x98, 0x69, 0x75, 0x2a, 0x0f, 0x82, 0x88, 0xb1, 0x80, 0x23, 0xa6, 0x5a,
|
||||
0xb3, 0xc1, 0x31, 0x46, 0x5b, 0xf2, 0x5a, 0xe7, 0xa8, 0x5c, 0xaf, 0x55, 0x05, 0x34, 0x21, 0x17,
|
||||
0x01, 0xba, 0x16, 0x42, 0x6b, 0xe2, 0xd9, 0xf7, 0xb0, 0x25, 0x83, 0x6c, 0xfe, 0xf5, 0x2c, 0x82,
|
||||
0x2f, 0x92, 0x54, 0xb9, 0xd9, 0x54, 0xb5, 0x6a, 0x70, 0xfb, 0xb9, 0xae, 0x3c, 0x99, 0x30, 0xdb,
|
||||
0xf0, 0x10, 0xfb, 0x0d, 0xfd, 0x40, 0x6d, 0x07, 0x97, 0x9f, 0x23, 0xf6, 0xb9, 0x33, 0x62, 0xd3,
|
||||
0xbd, 0x8d, 0x57, 0xbf, 0x6e, 0x2e, 0xbd, 0x86, 0xbf, 0x57, 0xef, 0x36, 0x63, 0xaf, 0xe1, 0xef,
|
||||
0x97, 0x77, 0x9b, 0x4b, 0xbf, 0xc3, 0xef, 0xcb, 0xdf, 0x36, 0x63, 0xfd, 0x14, 0xce, 0xae, 0x7b,
|
||||
0x7f, 0x06, 0x00, 0x00, 0xff, 0xff, 0x96, 0x0b, 0xf7, 0x15, 0xb6, 0x0d, 0x00, 0x00,
|
||||
// 1665 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x73, 0xdb, 0xc6,
|
||||
0x15, 0x17, 0x48, 0xf0, 0xdf, 0x23, 0xa5, 0x40, 0x6b, 0x59, 0x41, 0x61, 0x85, 0x42, 0xe0, 0xb8,
|
||||
0x55, 0x34, 0x8d, 0xe2, 0xc6, 0x69, 0x33, 0xd3, 0x69, 0x3b, 0x43, 0x91, 0x90, 0xcc, 0x09, 0x0d,
|
||||
0x32, 0x4b, 0xca, 0xae, 0x73, 0x28, 0x06, 0x24, 0x96, 0x14, 0xc6, 0x20, 0x96, 0x05, 0x40, 0xd9,
|
||||
0xea, 0x47, 0x60, 0xbf, 0x40, 0x2f, 0x9c, 0xc9, 0xf4, 0xd6, 0x7b, 0x3f, 0x84, 0x8f, 0x99, 0x1c,
|
||||
0x7b, 0xf0, 0x34, 0xea, 0xa5, 0xc7, 0x5e, 0x7a, 0xef, 0x60, 0x17, 0x00, 0x41, 0xfd, 0xe9, 0xe4,
|
||||
0x90, 0x13, 0x77, 0xdf, 0xfb, 0xed, 0xdb, 0x7d, 0xbf, 0xf7, 0x7e, 0x8f, 0x80, 0xca, 0x90, 0xcc,
|
||||
0x8e, 0x66, 0x3e, 0x0d, 0x29, 0x2a, 0xb3, 0x9f, 0x11, 0x75, 0x95, 0x4f, 0x26, 0x4e, 0x78, 0x3e,
|
||||
0x1f, 0x1e, 0x8d, 0xe8, 0xf4, 0xd3, 0x09, 0x9d, 0xd0, 0x4f, 0x99, 0x67, 0x38, 0x1f, 0xb3, 0x1d,
|
||||
0xdb, 0xb0, 0x15, 0x3f, 0xa8, 0xcd, 0xa0, 0xf0, 0x94, 0xb8, 0x2e, 0x45, 0xfb, 0x50, 0xb5, 0xc9,
|
||||
0x85, 0x33, 0x22, 0xa6, 0x67, 0x4d, 0x89, 0x2c, 0xa8, 0xc2, 0x41, 0x05, 0x03, 0x37, 0x19, 0xd6,
|
||||
0x94, 0x44, 0x80, 0x91, 0xeb, 0x10, 0x2f, 0xe4, 0x80, 0x1c, 0x07, 0x70, 0x13, 0x03, 0x3c, 0x82,
|
||||
0xad, 0x18, 0x70, 0x41, 0xfc, 0xc0, 0xa1, 0x9e, 0x9c, 0x67, 0x98, 0x4d, 0x6e, 0x7d, 0xce, 0x8d,
|
||||
0x5a, 0x00, 0xc5, 0xa7, 0xc4, 0xb2, 0x89, 0x8f, 0x3e, 0x06, 0x31, 0xbc, 0x9c, 0xf1, 0xbb, 0xb6,
|
||||
0x3e, 0xbb, 0x7f, 0x94, 0xe4, 0x70, 0xf4, 0x8c, 0x04, 0x81, 0x35, 0x21, 0x83, 0xcb, 0x19, 0xc1,
|
||||
0x0c, 0x82, 0x7e, 0x07, 0xd5, 0x11, 0x9d, 0xce, 0x7c, 0x12, 0xb0, 0xc0, 0x39, 0x76, 0x62, 0xef,
|
||||
0xc6, 0x89, 0xe6, 0x0a, 0x83, 0xb3, 0x07, 0xb4, 0x06, 0x6c, 0x36, 0xdd, 0x79, 0x10, 0x12, 0xbf,
|
||||
0x49, 0xbd, 0xb1, 0x33, 0x41, 0x8f, 0xa1, 0x34, 0xa6, 0xae, 0x4d, 0xfc, 0x40, 0x16, 0xd4, 0xfc,
|
||||
0x41, 0xf5, 0x33, 0x69, 0x15, 0xec, 0x84, 0x39, 0x8e, 0xc5, 0xb7, 0xef, 0xf6, 0x37, 0x70, 0x02,
|
||||
0xd3, 0xfe, 0x9c, 0x83, 0x22, 0xf7, 0xa0, 0x5d, 0xc8, 0x39, 0x36, 0xa7, 0xe8, 0xb8, 0x78, 0xf5,
|
||||
0x6e, 0x3f, 0xd7, 0x6e, 0xe1, 0x9c, 0x63, 0xa3, 0x1d, 0x28, 0xb8, 0xd6, 0x90, 0xb8, 0x31, 0x39,
|
||||
0x7c, 0x83, 0x1e, 0x40, 0xc5, 0x27, 0x96, 0x6d, 0x52, 0xcf, 0xbd, 0x64, 0x94, 0x94, 0x71, 0x39,
|
||||
0x32, 0x74, 0x3d, 0xf7, 0x12, 0x7d, 0x02, 0xc8, 0x99, 0x78, 0xd4, 0x27, 0xe6, 0x8c, 0xf8, 0x53,
|
||||
0x87, 0xbd, 0x36, 0x90, 0x45, 0x86, 0xda, 0xe6, 0x9e, 0xde, 0xca, 0x81, 0x1e, 0xc2, 0x66, 0x0c,
|
||||
0xb7, 0x89, 0x4b, 0x42, 0x22, 0x17, 0x18, 0xb2, 0xc6, 0x8d, 0x2d, 0x66, 0x43, 0x8f, 0x61, 0xc7,
|
||||
0x76, 0x02, 0x6b, 0xe8, 0x12, 0x33, 0x24, 0xd3, 0x99, 0xe9, 0x78, 0x36, 0x79, 0x43, 0x02, 0xb9,
|
||||
0xc8, 0xb0, 0x28, 0xf6, 0x0d, 0xc8, 0x74, 0xd6, 0xe6, 0x9e, 0x88, 0x0d, 0x5e, 0xe9, 0x40, 0x96,
|
||||
0xae, 0xb3, 0xd1, 0x62, 0x8e, 0x84, 0x8d, 0x18, 0xa6, 0xfd, 0x27, 0x07, 0x45, 0xee, 0x41, 0x3f,
|
||||
0x4d, 0xd9, 0xa8, 0x1d, 0xef, 0x46, 0xa8, 0x7f, 0xbc, 0xdb, 0x2f, 0x73, 0x5f, 0xbb, 0x95, 0x61,
|
||||
0x07, 0x81, 0x98, 0xe9, 0x1c, 0xb6, 0x46, 0x7b, 0x50, 0xb1, 0x6c, 0x3b, 0xaa, 0x12, 0x09, 0xe4,
|
||||
0xbc, 0x9a, 0x3f, 0xa8, 0xe0, 0x95, 0x01, 0x7d, 0xb1, 0x5e, 0x75, 0xf1, 0x7a, 0x9f, 0xdc, 0x55,
|
||||
0xee, 0x88, 0xf2, 0x11, 0xf1, 0xe3, 0x4e, 0x2d, 0xb0, 0xfb, 0xca, 0x91, 0x81, 0xf5, 0xe9, 0x87,
|
||||
0x50, 0x9b, 0x5a, 0x6f, 0xcc, 0x80, 0xfc, 0x71, 0x4e, 0xbc, 0x11, 0x61, 0xb4, 0xe4, 0x71, 0x75,
|
||||
0x6a, 0xbd, 0xe9, 0xc7, 0x26, 0x54, 0x07, 0x70, 0xbc, 0xd0, 0xa7, 0xf6, 0x7c, 0x44, 0x7c, 0xb9,
|
||||
0xc4, 0x78, 0xcb, 0x58, 0xd0, 0x2f, 0xa1, 0xcc, 0x48, 0x35, 0x1d, 0x5b, 0x2e, 0xab, 0xc2, 0x81,
|
||||
0x78, 0xac, 0xc4, 0x89, 0x97, 0x18, 0xa5, 0x2c, 0xef, 0x64, 0x89, 0x4b, 0x0c, 0xdb, 0xb6, 0xd1,
|
||||
0x6f, 0x40, 0x09, 0x5e, 0x39, 0x51, 0x41, 0x78, 0xa4, 0xd0, 0xa1, 0x9e, 0xe9, 0x93, 0x29, 0xbd,
|
||||
0xb0, 0xdc, 0x40, 0xae, 0xb0, 0x6b, 0xe4, 0x08, 0xd1, 0xce, 0x00, 0x70, 0xec, 0xd7, 0xba, 0x50,
|
||||
0x60, 0x11, 0xd1, 0x2e, 0x14, 0x79, 0x53, 0xc6, 0x2a, 0x8d, 0x77, 0xe8, 0x08, 0x0a, 0x63, 0xc7,
|
||||
0x25, 0x81, 0x9c, 0x63, 0x35, 0x44, 0x99, 0x8e, 0x76, 0x5c, 0xd2, 0xf6, 0xc6, 0x34, 0xae, 0x22,
|
||||
0x87, 0x69, 0x67, 0x50, 0x65, 0x01, 0xcf, 0x66, 0xb6, 0x15, 0x92, 0x1f, 0x2d, 0xec, 0x5f, 0xf3,
|
||||
0x50, 0x4e, 0x3c, 0x69, 0xd1, 0x85, 0x4c, 0xd1, 0x0f, 0x63, 0xdd, 0x73, 0x15, 0xef, 0xde, 0x8c,
|
||||
0x97, 0x11, 0x3e, 0x02, 0x31, 0x70, 0xfe, 0x44, 0x98, 0x6e, 0xf2, 0x98, 0xad, 0x91, 0x0a, 0xd5,
|
||||
0xeb, 0x62, 0xd9, 0xc4, 0x59, 0x13, 0xfa, 0x00, 0x60, 0x4a, 0x6d, 0x67, 0xec, 0x10, 0xdb, 0x0c,
|
||||
0x58, 0x03, 0xe4, 0x71, 0x25, 0xb1, 0xf4, 0x91, 0x1c, 0xb5, 0x7b, 0x24, 0x15, 0x3b, 0xd6, 0x44,
|
||||
0xb2, 0x8d, 0x3c, 0x8e, 0x77, 0x61, 0xb9, 0x8e, 0x1d, 0x57, 0x3d, 0xd9, 0x46, 0xd3, 0xcd, 0xa3,
|
||||
0x6b, 0x22, 0x2d, 0x33, 0xc0, 0xa6, 0x47, 0xb3, 0x02, 0x7d, 0x0c, 0xa5, 0x64, 0xfa, 0x45, 0xf5,
|
||||
0x5c, 0x53, 0xd2, 0x73, 0x32, 0x0a, 0x69, 0x3a, 0x57, 0x62, 0x18, 0x52, 0xa0, 0x9c, 0xb6, 0x22,
|
||||
0xb0, 0x97, 0xa6, 0xfb, 0x68, 0xe6, 0xa6, 0x79, 0x78, 0x81, 0x5c, 0x55, 0x85, 0x83, 0x02, 0x4e,
|
||||
0x53, 0x33, 0x02, 0xf4, 0x0b, 0x28, 0x1e, 0xbb, 0x74, 0xf4, 0x2a, 0xd1, 0xed, 0xbd, 0xd5, 0x6d,
|
||||
0xcc, 0x9e, 0xa9, 0x4e, 0x71, 0xc8, 0x80, 0xbf, 0x16, 0xff, 0xf2, 0xcd, 0xfe, 0x86, 0xf6, 0x15,
|
||||
0x54, 0x52, 0x40, 0x54, 0x79, 0x3a, 0x1e, 0x07, 0x24, 0x64, 0x65, 0xca, 0xe3, 0x78, 0x97, 0x92,
|
||||
0x9f, 0x63, 0xf7, 0x72, 0xf2, 0x11, 0x88, 0xe7, 0x56, 0x70, 0xce, 0x0a, 0x52, 0xc3, 0x6c, 0x1d,
|
||||
0x87, 0xfc, 0x2d, 0x14, 0x79, 0x86, 0xe8, 0x09, 0x94, 0x47, 0x74, 0xee, 0x85, 0xab, 0xe9, 0xba,
|
||||
0x9d, 0x15, 0x2d, 0xf3, 0xc4, 0xaf, 0x4a, 0x81, 0xda, 0x09, 0x94, 0x62, 0x17, 0x7a, 0x94, 0x4e,
|
||||
0x14, 0xf1, 0xf8, 0x7e, 0x22, 0xac, 0xfe, 0x39, 0xf5, 0xc3, 0xb5, 0x81, 0xb2, 0x03, 0x85, 0x0b,
|
||||
0xcb, 0x9d, 0xf3, 0xf7, 0x89, 0x98, 0x6f, 0xb4, 0xbf, 0x0b, 0x50, 0xc2, 0x11, 0x81, 0x41, 0x98,
|
||||
0x19, 0xd4, 0x85, 0xb5, 0x41, 0xbd, 0x6a, 0xf5, 0xdc, 0x5a, 0xab, 0x27, 0xdd, 0x9a, 0xcf, 0x74,
|
||||
0xeb, 0x8a, 0x1c, 0xf1, 0x56, 0x72, 0x0a, 0xb7, 0x90, 0x53, 0x5c, 0x91, 0x13, 0x35, 0xce, 0xd8,
|
||||
0xa7, 0x53, 0x36, 0x8a, 0xa9, 0x6f, 0xf9, 0x97, 0x71, 0x67, 0x6d, 0x46, 0xd6, 0x41, 0x62, 0xd4,
|
||||
0x4c, 0x28, 0x63, 0x12, 0xcc, 0xa8, 0x17, 0x90, 0x3b, 0x9f, 0x8d, 0x40, 0xb4, 0xad, 0xd0, 0x62,
|
||||
0x8f, 0xae, 0x61, 0xb6, 0x46, 0x3f, 0x03, 0x71, 0x44, 0x6d, 0xfe, 0xe4, 0xad, 0x6c, 0xfd, 0x75,
|
||||
0xdf, 0xa7, 0x7e, 0x93, 0xda, 0x04, 0x33, 0x80, 0x36, 0x03, 0xa9, 0x45, 0x5f, 0x7b, 0x2e, 0xb5,
|
||||
0xec, 0x9e, 0x4f, 0x27, 0xd1, 0xa8, 0xbc, 0x53, 0xf2, 0x2d, 0x28, 0xcd, 0xd9, 0x50, 0x48, 0x44,
|
||||
0xff, 0xd1, 0xba, 0x48, 0xaf, 0x07, 0xe2, 0x13, 0x24, 0xe9, 0xec, 0xf8, 0xa8, 0xf6, 0x9d, 0x00,
|
||||
0xca, 0xdd, 0x68, 0xd4, 0x86, 0x2a, 0x47, 0x9a, 0x99, 0xaf, 0x80, 0x83, 0x1f, 0x72, 0x11, 0x9b,
|
||||
0x0f, 0x30, 0x4f, 0xd7, 0xb7, 0xfe, 0xb5, 0x64, 0x94, 0x98, 0xff, 0x61, 0x4a, 0x7c, 0x08, 0x9b,
|
||||
0x4c, 0x23, 0xe9, 0x1f, 0xa6, 0xa8, 0xe6, 0x0f, 0x0a, 0xb8, 0x36, 0xe4, 0x42, 0x61, 0x36, 0xad,
|
||||
0x08, 0x62, 0xcf, 0xf1, 0x26, 0xda, 0x3e, 0x14, 0x9a, 0x2e, 0x65, 0xc5, 0x2a, 0xfa, 0xc4, 0x0a,
|
||||
0xa8, 0x97, 0x70, 0xc8, 0x77, 0x87, 0xdf, 0xe5, 0xa0, 0x9a, 0xf9, 0x90, 0x41, 0x8f, 0x61, 0xab,
|
||||
0xd9, 0x39, 0xeb, 0x0f, 0x74, 0x6c, 0x36, 0xbb, 0xc6, 0x49, 0xfb, 0x54, 0xda, 0x50, 0xf6, 0x16,
|
||||
0x4b, 0x55, 0x9e, 0xae, 0x40, 0xeb, 0xdf, 0x28, 0xfb, 0x50, 0x68, 0x1b, 0x2d, 0xfd, 0xf7, 0x92,
|
||||
0xa0, 0xec, 0x2c, 0x96, 0xaa, 0x94, 0x01, 0xf2, 0x3f, 0x82, 0x9f, 0x43, 0x8d, 0x01, 0xcc, 0xb3,
|
||||
0x5e, 0xab, 0x31, 0xd0, 0xa5, 0x9c, 0xa2, 0x2c, 0x96, 0xea, 0xee, 0x75, 0x5c, 0xcc, 0xf7, 0x43,
|
||||
0x28, 0x61, 0xfd, 0xab, 0x33, 0xbd, 0x3f, 0x90, 0xf2, 0xca, 0xee, 0x62, 0xa9, 0xa2, 0x0c, 0x30,
|
||||
0x51, 0xcc, 0x23, 0x28, 0x63, 0xbd, 0xdf, 0xeb, 0x1a, 0x7d, 0x5d, 0x12, 0x95, 0xf7, 0x17, 0x4b,
|
||||
0xf5, 0xde, 0x1a, 0x2a, 0xee, 0xd0, 0x5f, 0xc1, 0x76, 0xab, 0xfb, 0xc2, 0xe8, 0x74, 0x1b, 0x2d,
|
||||
0xb3, 0x87, 0xbb, 0xa7, 0x58, 0xef, 0xf7, 0xa5, 0x82, 0xb2, 0xbf, 0x58, 0xaa, 0x0f, 0x32, 0xf8,
|
||||
0x1b, 0x0d, 0xf7, 0x01, 0x88, 0xbd, 0xb6, 0x71, 0x2a, 0x15, 0x95, 0x7b, 0x8b, 0xa5, 0xfa, 0x5e,
|
||||
0x06, 0x1a, 0x91, 0x1a, 0x65, 0xdc, 0xec, 0x74, 0xfb, 0xba, 0x54, 0xba, 0x91, 0x31, 0x23, 0xfb,
|
||||
0xf0, 0x0f, 0x80, 0x6e, 0x7e, 0xea, 0xa1, 0x8f, 0x40, 0x34, 0xba, 0x86, 0x2e, 0x6d, 0xf0, 0xfc,
|
||||
0x6f, 0x22, 0x0c, 0xea, 0x11, 0xa4, 0x41, 0xbe, 0xf3, 0xf5, 0xe7, 0x92, 0xa0, 0xfc, 0x64, 0xb1,
|
||||
0x54, 0xef, 0xdf, 0x04, 0x75, 0xbe, 0xfe, 0xfc, 0x90, 0x42, 0x35, 0x1b, 0x58, 0x83, 0xf2, 0x33,
|
||||
0x7d, 0xd0, 0x68, 0x35, 0x06, 0x0d, 0x69, 0x83, 0x3f, 0x29, 0x71, 0x3f, 0x23, 0xa1, 0xc5, 0x04,
|
||||
0xb8, 0x07, 0x05, 0x43, 0x7f, 0xae, 0x63, 0x49, 0x50, 0xb6, 0x17, 0x4b, 0x75, 0x33, 0x01, 0x18,
|
||||
0xe4, 0x82, 0xf8, 0xa8, 0x0e, 0xc5, 0x46, 0xe7, 0x45, 0xe3, 0x65, 0x5f, 0xca, 0x29, 0x68, 0xb1,
|
||||
0x54, 0xb7, 0x12, 0x77, 0xc3, 0x7d, 0x6d, 0x5d, 0x06, 0x87, 0xff, 0x15, 0xa0, 0x96, 0xfd, 0xdb,
|
||||
0x43, 0x75, 0x10, 0x4f, 0xda, 0x1d, 0x3d, 0xb9, 0x2e, 0xeb, 0x8b, 0xd6, 0xe8, 0x00, 0x2a, 0xad,
|
||||
0x36, 0xd6, 0x9b, 0x83, 0x2e, 0x7e, 0x99, 0xe4, 0x92, 0x05, 0xb5, 0x1c, 0x9f, 0x35, 0x77, 0xf4,
|
||||
0x69, 0x59, 0xeb, 0xbf, 0x7c, 0xd6, 0x69, 0x1b, 0x5f, 0x9a, 0x2c, 0x62, 0x4e, 0x79, 0xb0, 0x58,
|
||||
0xaa, 0xef, 0x67, 0xc1, 0xfd, 0xcb, 0xa9, 0xeb, 0x78, 0xaf, 0x58, 0xe0, 0x2f, 0x60, 0x3b, 0x81,
|
||||
0xaf, 0x2e, 0xc8, 0x2b, 0xea, 0x62, 0xa9, 0xee, 0xdd, 0x72, 0x66, 0x75, 0xcf, 0x13, 0x78, 0x2f,
|
||||
0x39, 0x78, 0x66, 0x7c, 0x69, 0x74, 0x5f, 0x18, 0x92, 0xa8, 0xd4, 0x17, 0x4b, 0x55, 0xb9, 0xe5,
|
||||
0xd8, 0x99, 0xf7, 0xca, 0xa3, 0xaf, 0xbd, 0xc3, 0xbf, 0x09, 0x50, 0x49, 0x27, 0x54, 0xc4, 0xb3,
|
||||
0xd1, 0x35, 0x75, 0x8c, 0xbb, 0x38, 0x49, 0x3c, 0x75, 0x1a, 0x94, 0x2d, 0xd1, 0x87, 0x50, 0x3a,
|
||||
0xd5, 0x0d, 0x1d, 0xb7, 0x9b, 0x89, 0x1e, 0x52, 0xc8, 0x29, 0xf1, 0x88, 0xef, 0x8c, 0xd0, 0xc7,
|
||||
0x50, 0x33, 0xba, 0x66, 0xff, 0xac, 0xf9, 0x34, 0xc9, 0x98, 0x35, 0x70, 0x26, 0x54, 0x7f, 0x3e,
|
||||
0x3a, 0x67, 0xd9, 0x1e, 0x46, 0xd2, 0x79, 0xde, 0xe8, 0xb4, 0x5b, 0x1c, 0x9a, 0x57, 0xe4, 0xc5,
|
||||
0x52, 0xdd, 0x49, 0xa1, 0x6d, 0xfe, 0xb7, 0x1f, 0x61, 0x0f, 0x6d, 0xa8, 0xff, 0xff, 0x59, 0x84,
|
||||
0x54, 0x28, 0x36, 0x7a, 0x3d, 0xdd, 0x68, 0x25, 0xaf, 0x5f, 0xf9, 0x1a, 0xb3, 0x19, 0xf1, 0xec,
|
||||
0x08, 0x71, 0xd2, 0xc5, 0xa7, 0xfa, 0x20, 0x79, 0xfc, 0x0a, 0x71, 0x42, 0xfd, 0x09, 0x09, 0x8f,
|
||||
0xf7, 0xde, 0x7e, 0x5f, 0xdf, 0xf8, 0xf6, 0xfb, 0xfa, 0xc6, 0xdb, 0xab, 0xba, 0xf0, 0xed, 0x55,
|
||||
0x5d, 0xf8, 0xe7, 0x55, 0x7d, 0xe3, 0xdf, 0x57, 0x75, 0xe1, 0x9b, 0x7f, 0xd5, 0x85, 0x61, 0x91,
|
||||
0xcd, 0xae, 0x27, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x64, 0x17, 0x1e, 0x19, 0xf4, 0x0d, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. message.proto
|
||||
// protoc protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. message.proto
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
@@ -63,14 +63,15 @@ message Folder {
|
||||
}
|
||||
|
||||
message Device {
|
||||
bytes id = 1 [(gogoproto.customname) = "ID", (gogoproto.customtype) = "DeviceID", (gogoproto.nullable) = false];
|
||||
string name = 2;
|
||||
repeated string addresses = 3;
|
||||
Compression compression = 4;
|
||||
string cert_name = 5;
|
||||
int64 max_sequence = 6;
|
||||
bool introducer = 7;
|
||||
uint64 index_id = 8 [(gogoproto.customname) = "IndexID", (gogoproto.customtype) = "IndexID", (gogoproto.nullable) = false];
|
||||
bytes id = 1 [(gogoproto.customname) = "ID", (gogoproto.customtype) = "DeviceID", (gogoproto.nullable) = false];
|
||||
string name = 2;
|
||||
repeated string addresses = 3;
|
||||
Compression compression = 4;
|
||||
string cert_name = 5;
|
||||
int64 max_sequence = 6;
|
||||
bool introducer = 7;
|
||||
uint64 index_id = 8 [(gogoproto.customname) = "IndexID", (gogoproto.customtype) = "IndexID", (gogoproto.nullable) = false];
|
||||
bool skip_introduction_removals = 9;
|
||||
}
|
||||
|
||||
enum Compression {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2014 The Protocol Authors.
|
||||
|
||||
//go:generate go run ../../script/protofmt.go bep.proto
|
||||
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. bep.proto
|
||||
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. bep.proto
|
||||
|
||||
package protocol
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ const DeviceIDLength = 32
|
||||
type DeviceID [DeviceIDLength]byte
|
||||
type ShortID uint64
|
||||
|
||||
var LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
var (
|
||||
LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
EmptyDeviceID = DeviceID{ /* all zeroes */ }
|
||||
)
|
||||
|
||||
// NewDeviceID generates a new device ID from the raw bytes of a certificate
|
||||
func NewDeviceID(rawCert []byte) DeviceID {
|
||||
@@ -49,6 +52,9 @@ func DeviceIDFromBytes(bs []byte) DeviceID {
|
||||
|
||||
// String returns the canonical string representation of the device ID
|
||||
func (n DeviceID) String() string {
|
||||
if n == EmptyDeviceID {
|
||||
return ""
|
||||
}
|
||||
id := base32.StdEncoding.EncodeToString(n[:])
|
||||
id = strings.Trim(id, "=")
|
||||
id, err := luhnify(id)
|
||||
@@ -96,6 +102,9 @@ func (n *DeviceID) UnmarshalText(bs []byte) error {
|
||||
|
||||
var err error
|
||||
switch len(id) {
|
||||
case 0:
|
||||
*n = EmptyDeviceID
|
||||
return nil
|
||||
case 56:
|
||||
// New style, with check digits
|
||||
id, err = unluhnify(id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (C) 2014 The Protocol Authors.
|
||||
|
||||
//go:generate go run ../../script/protofmt.go deviceid_test.proto
|
||||
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. deviceid_test.proto
|
||||
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. deviceid_test.proto
|
||||
|
||||
package protocol
|
||||
|
||||
@@ -37,7 +37,8 @@ var validateCases = []struct {
|
||||
s string
|
||||
ok bool
|
||||
}{
|
||||
{"", false},
|
||||
{"", true}, // Empty device ID, all zeroes
|
||||
{"a", false},
|
||||
{"P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2", true},
|
||||
{"P56IOI7-MZJNU2-IQGDREY-DM2MGT-MGL3BXN-PQ6W5B-TBBZ4TJ-XZWICQ", true},
|
||||
{"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ", true},
|
||||
|
||||
@@ -427,16 +427,16 @@ var (
|
||||
)
|
||||
|
||||
var fileDescriptorDeviceidTest = []byte{
|
||||
// 171 bytes of a gzipped FileDescriptorProto
|
||||
// 176 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x49, 0x2d, 0xcb,
|
||||
0x4c, 0x4e, 0xcd, 0x4c, 0x89, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
|
||||
0xe2, 0x00, 0x53, 0xc9, 0xf9, 0x39, 0x52, 0xba, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9,
|
||||
0xf9, 0xb9, 0xfa, 0xe9, 0xf9, 0xe9, 0xf9, 0xfa, 0x60, 0x99, 0xa4, 0xd2, 0x34, 0x30, 0x0f, 0xcc,
|
||||
0x01, 0xb3, 0x20, 0x1a, 0x95, 0x54, 0xb9, 0xf8, 0x43, 0x80, 0xc6, 0xf8, 0xe7, 0xa4, 0xb8, 0x80,
|
||||
0x8d, 0xf5, 0x74, 0x11, 0x12, 0xe2, 0x62, 0x01, 0x99, 0x2c, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x13,
|
||||
0x04, 0x66, 0x2b, 0x99, 0x43, 0x94, 0xf9, 0xa5, 0x96, 0xc3, 0x95, 0xa9, 0x20, 0x2b, 0x73, 0x12,
|
||||
0x38, 0x71, 0x4f, 0x9e, 0xe1, 0xd6, 0x3d, 0x79, 0x0e, 0x98, 0x3c, 0x44, 0xa3, 0x93, 0xcc, 0x89,
|
||||
0x87, 0x72, 0x0c, 0x17, 0x80, 0xf8, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x40, 0xfc, 0xe0, 0x91, 0x1c,
|
||||
0xc3, 0x0b, 0x20, 0x5e, 0xf0, 0x58, 0x8e, 0x31, 0x89, 0x0d, 0xec, 0x08, 0x63, 0x40, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0x35, 0x9c, 0x00, 0x78, 0xd4, 0x00, 0x00, 0x00,
|
||||
0x01, 0xb3, 0x20, 0x1a, 0x95, 0x54, 0xb9, 0xf8, 0x43, 0x52, 0x8b, 0x4b, 0xfc, 0x73, 0x52, 0x5c,
|
||||
0xc0, 0xc6, 0x7a, 0xba, 0x08, 0x09, 0x71, 0xb1, 0x80, 0x4c, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0,
|
||||
0x09, 0x02, 0xb3, 0x95, 0xcc, 0x21, 0xca, 0xfc, 0x52, 0xcb, 0xe1, 0xca, 0x54, 0x90, 0x95, 0x39,
|
||||
0x09, 0x9c, 0xb8, 0x27, 0xcf, 0x70, 0xeb, 0x9e, 0x3c, 0x07, 0x4c, 0x1e, 0xa2, 0xd1, 0x49, 0xe6,
|
||||
0xc4, 0x43, 0x39, 0x86, 0x0b, 0x0f, 0xe5, 0x18, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e,
|
||||
0xf1, 0xc1, 0x23, 0x39, 0x86, 0x17, 0x8f, 0xe4, 0x18, 0x16, 0x3c, 0x96, 0x63, 0x4c, 0x62, 0x03,
|
||||
0x3b, 0xc2, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x35, 0x9c, 0x00, 0x78, 0xd4, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ func newDynamicClient(uri *url.URL, certs []tls.Certificate, invitations chan pr
|
||||
}
|
||||
|
||||
func (c *dynamicClient) Serve() {
|
||||
defer c.cleanup()
|
||||
c.mut.Lock()
|
||||
c.stop = make(chan struct{})
|
||||
c.mut.Unlock()
|
||||
@@ -75,8 +76,6 @@ func (c *dynamicClient) Serve() {
|
||||
return
|
||||
}
|
||||
|
||||
defer c.cleanup()
|
||||
|
||||
var addrs []string
|
||||
for _, relayAnn := range ann.Relays {
|
||||
ruri, err := url.Parse(relayAnn.URL)
|
||||
|
||||
@@ -63,6 +63,7 @@ func newStaticClient(uri *url.URL, certs []tls.Certificate, invitations chan pro
|
||||
}
|
||||
|
||||
func (c *staticClient) Serve() {
|
||||
defer c.cleanup()
|
||||
c.stop = make(chan struct{})
|
||||
c.stopped = make(chan struct{})
|
||||
defer close(c.stopped)
|
||||
@@ -156,10 +157,6 @@ func (c *staticClient) Serve() {
|
||||
} else {
|
||||
c.err = nil
|
||||
}
|
||||
if c.closeInvitationsOnFinish {
|
||||
close(c.invitations)
|
||||
c.invitations = make(chan protocol.SessionInvitation)
|
||||
}
|
||||
c.mut.Unlock()
|
||||
return
|
||||
|
||||
@@ -209,6 +206,15 @@ func (c *staticClient) Invitations() chan protocol.SessionInvitation {
|
||||
return inv
|
||||
}
|
||||
|
||||
func (c *staticClient) cleanup() {
|
||||
c.mut.Lock()
|
||||
if c.closeInvitationsOnFinish {
|
||||
close(c.invitations)
|
||||
c.invitations = make(chan protocol.SessionInvitation)
|
||||
}
|
||||
c.mut.Unlock()
|
||||
}
|
||||
|
||||
func (c *staticClient) connect() error {
|
||||
if c.uri.Scheme != "relay" {
|
||||
return fmt.Errorf("Unsupported relay schema: %v", c.uri.Scheme)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/syncthing/syncthing/lib/logger"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,8 @@ var (
|
||||
// We make an exception in this package and have an actual "if debug { ...
|
||||
// }" variable, as it may be rather performance critical and does
|
||||
// nonstandard things (from a debug logging PoV).
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "sync") || os.Getenv("STTRACE") == "all"
|
||||
debug = strings.Contains(os.Getenv("STTRACE"), "sync") || os.Getenv("STTRACE") == "all"
|
||||
useDeadlock = os.Getenv("STDEADLOCK") != ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -31,5 +33,8 @@ func init() {
|
||||
if n, err := strconv.Atoi(os.Getenv("STLOCKTHRESHOLD")); err == nil {
|
||||
threshold = time.Duration(n) * time.Millisecond
|
||||
}
|
||||
if n, err := strconv.Atoi(os.Getenv("STDEADLOCK")); err == nil {
|
||||
deadlock.Opts.DeadlockTimeout = time.Duration(n) * time.Second
|
||||
}
|
||||
l.Debugf("Enabling lock logging at %v threshold", threshold)
|
||||
}
|
||||
|
||||
139
lib/sync/sync.go
139
lib/sync/sync.go
@@ -15,6 +15,8 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type Mutex interface {
|
||||
@@ -35,17 +37,28 @@ type WaitGroup interface {
|
||||
}
|
||||
|
||||
func NewMutex() Mutex {
|
||||
if useDeadlock {
|
||||
return &deadlock.Mutex{}
|
||||
}
|
||||
if debug {
|
||||
return &loggedMutex{}
|
||||
mutex := &loggedMutex{}
|
||||
mutex.holder.Store(holder{})
|
||||
return mutex
|
||||
}
|
||||
return &sync.Mutex{}
|
||||
}
|
||||
|
||||
func NewRWMutex() RWMutex {
|
||||
if useDeadlock {
|
||||
return &deadlock.RWMutex{}
|
||||
}
|
||||
if debug {
|
||||
return &loggedRWMutex{
|
||||
unlockers: make([]string, 0),
|
||||
mutex := &loggedRWMutex{
|
||||
readHolders: make(map[int][]holder),
|
||||
unlockers: make(chan holder, 1024),
|
||||
}
|
||||
mutex.holder.Store(holder{})
|
||||
return mutex
|
||||
}
|
||||
return &sync.RWMutex{}
|
||||
}
|
||||
@@ -57,81 +70,129 @@ func NewWaitGroup() WaitGroup {
|
||||
return &sync.WaitGroup{}
|
||||
}
|
||||
|
||||
type holder struct {
|
||||
at string
|
||||
time time.Time
|
||||
goid int
|
||||
}
|
||||
|
||||
func (h holder) String() string {
|
||||
if h.at == "" {
|
||||
return "not held"
|
||||
}
|
||||
return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, time.Now().Sub(h.time))
|
||||
}
|
||||
|
||||
type loggedMutex struct {
|
||||
sync.Mutex
|
||||
start time.Time
|
||||
lockedAt string
|
||||
goid int
|
||||
start time.Time
|
||||
holder atomic.Value
|
||||
}
|
||||
|
||||
func (m *loggedMutex) Lock() {
|
||||
m.Mutex.Lock()
|
||||
m.start = time.Now()
|
||||
m.lockedAt = getCaller()
|
||||
m.goid = goid()
|
||||
m.holder.Store(getHolder())
|
||||
}
|
||||
|
||||
func (m *loggedMutex) Unlock() {
|
||||
duration := time.Now().Sub(m.start)
|
||||
currentHolder := m.holder.Load().(holder)
|
||||
duration := time.Now().Sub(currentHolder.time)
|
||||
if duration >= threshold {
|
||||
l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, m.lockedAt, getCaller())
|
||||
l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
|
||||
}
|
||||
m.holder.Store(holder{})
|
||||
m.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (m *loggedMutex) Holder() (string, int) {
|
||||
return m.lockedAt, m.goid
|
||||
func (m *loggedMutex) Holders() string {
|
||||
return m.holder.Load().(holder).String()
|
||||
}
|
||||
|
||||
type loggedRWMutex struct {
|
||||
sync.RWMutex
|
||||
start time.Time
|
||||
lockedAt string
|
||||
goid int
|
||||
holder atomic.Value
|
||||
|
||||
logUnlockers uint32
|
||||
readHolders map[int][]holder
|
||||
readHoldersMut sync.Mutex
|
||||
|
||||
unlockers []string
|
||||
unlockersMut sync.Mutex
|
||||
logUnlockers int32
|
||||
unlockers chan holder
|
||||
}
|
||||
|
||||
func (m *loggedRWMutex) Lock() {
|
||||
start := time.Now()
|
||||
|
||||
atomic.StoreUint32(&m.logUnlockers, 1)
|
||||
atomic.StoreInt32(&m.logUnlockers, 1)
|
||||
m.RWMutex.Lock()
|
||||
m.logUnlockers = 0
|
||||
|
||||
m.start = time.Now()
|
||||
duration := m.start.Sub(start)
|
||||
holder := getHolder()
|
||||
m.holder.Store(holder)
|
||||
|
||||
duration := holder.time.Sub(start)
|
||||
|
||||
m.lockedAt = getCaller()
|
||||
m.goid = goid()
|
||||
if duration > threshold {
|
||||
l.Debugf("RWMutex took %v to lock. Locked at %s. RUnlockers while locking: %s", duration, m.lockedAt, strings.Join(m.unlockers, ", "))
|
||||
var unlockerStrings []string
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case holder := <-m.unlockers:
|
||||
unlockerStrings = append(unlockerStrings, holder.String())
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
l.Debugf("RWMutex took %v to lock. Locked at %s. RUnlockers while locking:\n%s", duration, holder.at, strings.Join(unlockerStrings, "\n"))
|
||||
}
|
||||
m.unlockers = m.unlockers[0:]
|
||||
}
|
||||
|
||||
func (m *loggedRWMutex) Unlock() {
|
||||
duration := time.Now().Sub(m.start)
|
||||
currentHolder := m.holder.Load().(holder)
|
||||
duration := time.Now().Sub(currentHolder.time)
|
||||
if duration >= threshold {
|
||||
l.Debugf("RWMutex held for %v. Locked at %s: unlocked at %s", duration, m.lockedAt, getCaller())
|
||||
l.Debugf("RWMutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
|
||||
}
|
||||
m.holder.Store(holder{})
|
||||
m.RWMutex.Unlock()
|
||||
}
|
||||
|
||||
func (m *loggedRWMutex) RLock() {
|
||||
m.RWMutex.RLock()
|
||||
holder := getHolder()
|
||||
m.readHoldersMut.Lock()
|
||||
m.readHolders[holder.goid] = append(m.readHolders[holder.goid], holder)
|
||||
m.readHoldersMut.Unlock()
|
||||
}
|
||||
|
||||
func (m *loggedRWMutex) RUnlock() {
|
||||
if atomic.LoadUint32(&m.logUnlockers) == 1 {
|
||||
m.unlockersMut.Lock()
|
||||
m.unlockers = append(m.unlockers, getCaller())
|
||||
m.unlockersMut.Unlock()
|
||||
id := goid()
|
||||
m.readHoldersMut.Lock()
|
||||
current := m.readHolders[id]
|
||||
if len(current) > 0 {
|
||||
m.readHolders[id] = current[:len(current)-1]
|
||||
}
|
||||
m.readHoldersMut.Unlock()
|
||||
if atomic.LoadInt32(&m.logUnlockers) == 1 {
|
||||
holder := getHolder()
|
||||
select {
|
||||
case m.unlockers <- holder:
|
||||
default:
|
||||
l.Debugf("Dropped holder %s as channel full", holder)
|
||||
}
|
||||
}
|
||||
m.RWMutex.RUnlock()
|
||||
}
|
||||
|
||||
func (m *loggedRWMutex) Holder() (string, int) {
|
||||
return m.lockedAt, m.goid
|
||||
func (m *loggedRWMutex) Holders() string {
|
||||
output := m.holder.Load().(holder).String() + " (writer)"
|
||||
m.readHoldersMut.Lock()
|
||||
for _, holders := range m.readHolders {
|
||||
for _, holder := range holders {
|
||||
output += "\n" + holder.String() + " (reader)"
|
||||
}
|
||||
}
|
||||
m.readHoldersMut.Unlock()
|
||||
return output
|
||||
}
|
||||
|
||||
type loggedWaitGroup struct {
|
||||
@@ -143,14 +204,18 @@ func (wg *loggedWaitGroup) Wait() {
|
||||
wg.WaitGroup.Wait()
|
||||
duration := time.Now().Sub(start)
|
||||
if duration >= threshold {
|
||||
l.Debugf("WaitGroup took %v at %s", duration, getCaller())
|
||||
l.Debugf("WaitGroup took %v at %s", duration, getHolder())
|
||||
}
|
||||
}
|
||||
|
||||
func getCaller() string {
|
||||
func getHolder() holder {
|
||||
_, file, line, _ := runtime.Caller(2)
|
||||
file = filepath.Join(filepath.Base(filepath.Dir(file)), filepath.Base(file))
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
return holder{
|
||||
at: fmt.Sprintf("%s:%d", file, line),
|
||||
goid: goid(),
|
||||
time: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func goid() int {
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestRWMutex(t *testing.T) {
|
||||
if len(messages) != 2 {
|
||||
t.Errorf("Unexpected message count")
|
||||
}
|
||||
if !strings.Contains(messages[1], "RUnlockers while locking: sync") || !strings.Contains(messages[1], "sync_test.go:") {
|
||||
if !strings.Contains(messages[1], "RUnlockers while locking:\nat sync") || !strings.Contains(messages[1], "sync_test.go:") {
|
||||
t.Error("Unexpected message")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "STDISCOSRV" "1" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "STDISCOSRV" "1" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
stdiscosrv \- Syncthing Discovery Server
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "STRELAYSRV" "1" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "STRELAYSRV" "1" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
strelaysrv \- Syncthing Relay Server
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-BEP" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-BEP" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-bep \- Block Exchange Protocol v1
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-CONFIG" "5" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-CONFIG" "5" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-config \- Syncthing Configuration
|
||||
.
|
||||
@@ -435,18 +435,17 @@ The following child elements may be present:
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B address
|
||||
Set the listen addresses. One or more address elements must be present. Entries must have the protocol prefix \fBtcp://\fP\&.
|
||||
Allowed address formats are:
|
||||
Set the listen address. One address element must be present. Allowed address formats are:
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B IPv4 address and port (\fBtcp://127.0.0.1:8384\fP)
|
||||
.B IPv4 address and port (\fB127.0.0.1:8384\fP)
|
||||
The address and port is used as given.
|
||||
.TP
|
||||
.B IPv6 address and port (\fBtcp://[::1]:8384\fP)
|
||||
.B IPv6 address and port (\fB[::1]:8384\fP)
|
||||
The address and port is used as given. The address must be enclosed in
|
||||
square brackets.
|
||||
.TP
|
||||
.B Wildcard and port (\fBtcp://0.0.0.0:12345\fP, \fBtcp://[::]:12345\fP, \fBtcp://:12345\fP)
|
||||
.B Wildcard and port (\fB0.0.0.0:12345\fP, \fB[::]:12345\fP, \fB:12345\fP)
|
||||
These are equivalent and will result in Syncthing listening on all
|
||||
interfaces via both IPv4 and IPv6.
|
||||
.UNINDENT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "November 12, 2016" "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" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-EVENT-API" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-event-api \- Event API
|
||||
.
|
||||
@@ -79,11 +79,14 @@ though \fBdata\fP may be \fBnull\fP\&.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B id
|
||||
A monotonically increasing integer. The first event generated has id \fB1\fP,
|
||||
the next has id \fB2\fP etc.
|
||||
A unique ID for this event on the events API. It always increases by 1: the first
|
||||
event generated has id \fB1\fP, the next has id \fB2\fP etc. If this increases by
|
||||
more than 1, then one or more events have been skipped by the events API.
|
||||
.TP
|
||||
.B globalID
|
||||
Also a monotonically increasing integer. And might not be the same as id but will always be either equal or greater in value.
|
||||
A global ID for this event, across the events API, the audit log, and any other
|
||||
sources. It may increase by more than 1, but it will always be greater
|
||||
than or equal to the id.
|
||||
.TP
|
||||
.B time
|
||||
The time the event was generated.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-FAQ" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-FAQ" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-faq \- Frequently Asked Questions
|
||||
.
|
||||
@@ -195,11 +195,15 @@ device\-ids
|
||||
.SS What if there is a conflict?
|
||||
.sp
|
||||
Syncthing does recognize conflicts. When a file has been modified on two devices
|
||||
simultaneously, one of the files will be renamed to
|
||||
\fB<filename>.sync\-conflict\-<date>\-<time>.<ext>\fP\&. The device which has the larger
|
||||
value of the first 63 bits for his device ID will have his file marked as the conflicting
|
||||
file. Note that we only create \fBsync\-conflict\fP files when the actual content
|
||||
differs.
|
||||
simultaneously and the content actually differs, one of the files will be
|
||||
renamed to \fB<filename>.sync\-conflict\-<date>\-<time>.<ext>\fP\&. The file with the
|
||||
older modification time will be marked as the conflicting file and thus be
|
||||
renamed. If the modification times are equal, the file originating from the
|
||||
device which has the larger value of the first 63 bits for his device ID will be
|
||||
marked as the conflicting file.
|
||||
If the conflict is between a modification and a deletion of the file, the
|
||||
modified file always wins and is resurrected without renaming on the
|
||||
device where it was deleted.
|
||||
.sp
|
||||
Beware that the \fB<filename>.sync\-conflict\-<date>\-<time>.<ext>\fP files are
|
||||
treated as normal files after they are created, so they are propagated between
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-globaldisco \- Global Discovery Protocol v3
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-localdisco \- Local Discovery Protocol v4
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-NETWORKING" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-NETWORKING" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-networking \- Firewall Setup
|
||||
.
|
||||
@@ -70,6 +70,33 @@ the \fISync Protocol Listen Address\fP setting.)
|
||||
.IP \(bu 2
|
||||
Port \fB21027/UDP\fP (for discovery broadcasts on IPv4 and multicasts on IPv6)
|
||||
.UNINDENT
|
||||
.SS Uncomplicated Firewall (ufw)
|
||||
.sp
|
||||
If you\(aqre using \fBufw\fP on Linux and have installed the \fI\%Syncthing package\fP <\fBhttps://apt.syncthing.net/\fP>, you can allow the necessary ports by running:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
sudo ufw allow syncthing
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
You can then verify that the ports mentioned above are allowed:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
sudo ufw status verbose
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
In case you installed Syncthing manually you can follow the \fI\%instructions to manually add the syncthing preset\fP <\fBhttps://github.com/syncthing/syncthing/tree/master/etc/firewall-ufw\fP> to ufw.
|
||||
.SH REMOTE WEB GUI
|
||||
.sp
|
||||
To be able to access the web GUI from other computers, you need to change the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-RELAY" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-RELAY" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-relay \- Relay Protocol v1
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-REST-API" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-REST-API" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-rest-api \- REST API
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-SECURITY" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-SECURITY" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-security \- Security Principles
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-STIGNORE" "5" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-STIGNORE" "5" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-VERSIONING" "7" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING-VERSIONING" "7" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING" "1" "October 28, 2016" "v0.14" "Syncthing"
|
||||
.TH "SYNCTHING" "1" "November 12, 2016" "v0.14" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing \- Syncthing
|
||||
.
|
||||
|
||||
@@ -35,6 +35,8 @@ func main() {
|
||||
if err := formatProto(in, out); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
in.Close()
|
||||
out.Close()
|
||||
if err := os.Rename(file+".tmp", file); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ description: |
|
||||
trustworthy and decentralized. Your data is your data alone and you deserve
|
||||
to choose where it is stored, if it is shared with some third party and how
|
||||
it's transmitted over the Internet.
|
||||
architectures: [{{.Architecture}}]
|
||||
|
||||
grade: devel
|
||||
grade: {{.Grade}}
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
syncthing:
|
||||
command: syncthing
|
||||
command: env HOME="$SNAP_USER_COMMON" XDG_CONFIG_HOME="$SNAP_USER_COMMON" "$SNAP/syncthing"
|
||||
plugs: [home, network, network-bind]
|
||||
|
||||
parts:
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<localAnnounceEnabled>true</localAnnounceEnabled>
|
||||
<localAnnouncePort>21027</localAnnouncePort>
|
||||
<localAnnounceMCAddr>[ff12::8384]:21027</localAnnounceMCAddr>
|
||||
<maxSendKbps>10</maxSendKbps>
|
||||
<maxSendKbps>0</maxSendKbps>
|
||||
<maxRecvKbps>0</maxRecvKbps>
|
||||
<reconnectionIntervalS>5</reconnectionIntervalS>
|
||||
<relaysEnabled>true</relaysEnabled>
|
||||
@@ -96,7 +96,7 @@
|
||||
<cacheIgnoredFiles>false</cacheIgnoredFiles>
|
||||
<progressUpdateIntervalS>5</progressUpdateIntervalS>
|
||||
<symlinksEnabled>true</symlinksEnabled>
|
||||
<limitBandwidthInLan>true</limitBandwidthInLan>
|
||||
<limitBandwidthInLan>false</limitBandwidthInLan>
|
||||
<minHomeDiskFreePct>1</minHomeDiskFreePct>
|
||||
<releasesURL>https://upgrades.syncthing.net/meta.json</releasesURL>
|
||||
<overwriteRemoteDeviceNamesOnConnect>false</overwriteRemoteDeviceNamesOnConnect>
|
||||
|
||||
201
vendor/github.com/sasha-s/go-deadlock/LICENSE
generated
vendored
Normal file
201
vendor/github.com/sasha-s/go-deadlock/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
314
vendor/github.com/sasha-s/go-deadlock/deadlock.go
generated
vendored
Normal file
314
vendor/github.com/sasha-s/go-deadlock/deadlock.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
package deadlock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Opts control how deadlock detection behaves.
|
||||
// Options are supposed to be set once at a startup (say, when parsing flags).
|
||||
var Opts = struct {
|
||||
// Mutex/RWMutex would work exactly as their sync counterparts
|
||||
// -- almost no runtime penalty, no deadlock detection if Disable == true.
|
||||
Disable bool
|
||||
// Would disable lock order based deadlock detection if DisableLockOrderDetection == true.
|
||||
DisableLockOrderDetection bool
|
||||
// Waiting for a lock for longer than DeadlockTimeout is considered a deadlock.
|
||||
// Ignored is DeadlockTimeout <= 0.
|
||||
DeadlockTimeout time.Duration
|
||||
// OnPotentialDeadlock is called each time a potential deadlock is deetcted -- either based on
|
||||
// lock order or on lock wait time.
|
||||
OnPotentialDeadlock func()
|
||||
// Will keep MaxMapSize lock pairs (happens before // happens after) in the map.
|
||||
// The map resets once the threshold is reached.
|
||||
MaxMapSize int
|
||||
// Will print to deadlock info to log buffer.
|
||||
LogBuf io.Writer
|
||||
}{
|
||||
DeadlockTimeout: time.Second * 30,
|
||||
OnPotentialDeadlock: func() {
|
||||
os.Exit(2)
|
||||
},
|
||||
MaxMapSize: 1024 * 64,
|
||||
LogBuf: os.Stderr,
|
||||
}
|
||||
|
||||
// A Mutex is a drop-in replacement for sync.Mutex.
|
||||
// Performs deadlock detection unless disabled in Opts.
|
||||
type Mutex struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Lock locks the mutex.
|
||||
// If the lock is already in use, the calling goroutine
|
||||
// blocks until the mutex is available.
|
||||
//
|
||||
// Unless deadlock detection is disabled, logs potential deadlocks to stderr,
|
||||
// calling Opts.OnPotentialDeadlock on each occasion.
|
||||
func (m *Mutex) Lock() {
|
||||
lock(m.mu.Lock, m)
|
||||
}
|
||||
|
||||
// Unlock unlocks the mutex.
|
||||
// It is a run-time error if m is not locked on entry to Unlock.
|
||||
//
|
||||
// A locked Mutex is not associated with a particular goroutine.
|
||||
// It is allowed for one goroutine to lock a Mutex and then
|
||||
// arrange for another goroutine to unlock it.
|
||||
func (m *Mutex) Unlock() {
|
||||
if Opts.Disable {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
m.mu.Unlock()
|
||||
PostUnlock(m)
|
||||
}
|
||||
|
||||
// An RWMutex is a drop-in replacement for sync.RWMutex.
|
||||
// Performs deadlock detection unless disabled in Opts.
|
||||
type RWMutex struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Lock locks rw for writing.
|
||||
// If the lock is already locked for reading or writing,
|
||||
// Lock blocks until the lock is available.
|
||||
// To ensure that the lock eventually becomes available,
|
||||
// a blocked Lock call excludes new readers from acquiring
|
||||
// the lock.
|
||||
//
|
||||
// Unless deadlock detection is disabled, logs potential deadlocks to stderr,
|
||||
// calling Opts.OnPotentialDeadlock on each occasion.
|
||||
func (m *RWMutex) Lock() {
|
||||
lock(m.mu.Lock, m)
|
||||
}
|
||||
|
||||
// Unlock unlocks the mutex for writing. It is a run-time error if rw is
|
||||
// not locked for writing on entry to Unlock.
|
||||
//
|
||||
// As with Mutexes, a locked RWMutex is not associated with a particular
|
||||
// goroutine. One goroutine may RLock (Lock) an RWMutex and then
|
||||
// arrange for another goroutine to RUnlock (Unlock) it.
|
||||
func (m *RWMutex) Unlock() {
|
||||
if Opts.Disable {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
m.mu.Unlock()
|
||||
PostUnlock(m)
|
||||
}
|
||||
|
||||
// RLock locks the mutex for reading.
|
||||
//
|
||||
// Unless deadlock detection is disabled, logs potential deadlocks to stderr,
|
||||
// calling Opts.OnPotentialDeadlock on each occasion.
|
||||
func (m *RWMutex) RLock() {
|
||||
lock(m.mu.RLock, m)
|
||||
}
|
||||
|
||||
// RUnlock undoes a single RLock call;
|
||||
// it does not affect other simultaneous readers.
|
||||
// It is a run-time error if rw is not locked for reading
|
||||
// on entry to RUnlock.
|
||||
func (m *RWMutex) RUnlock() {
|
||||
if Opts.Disable {
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
PostUnlock(m)
|
||||
}
|
||||
|
||||
// RLocker returns a Locker interface that implements
|
||||
// the Lock and Unlock methods by calling RLock and RUnlock.
|
||||
func (m *RWMutex) RLocker() sync.Locker {
|
||||
return (*rlocker)(m)
|
||||
}
|
||||
|
||||
func PreLock(skip int, p interface{}) {
|
||||
lo.PreLock(skip, p)
|
||||
}
|
||||
|
||||
func PostLock(skip int, p interface{}) {
|
||||
lo.PostLock(skip, p)
|
||||
}
|
||||
|
||||
func PostUnlock(p interface{}) {
|
||||
lo.PostUnlock(p)
|
||||
}
|
||||
|
||||
func lock(lockFn func(), ptr interface{}) {
|
||||
if Opts.Disable {
|
||||
lockFn()
|
||||
return
|
||||
}
|
||||
PreLock(4, ptr)
|
||||
if Opts.DeadlockTimeout <= 0 {
|
||||
lockFn()
|
||||
} else {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
lockFn()
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
t := time.NewTimer(Opts.DeadlockTimeout)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-t.C:
|
||||
lo.mu.Lock()
|
||||
prev, ok := lo.cur[ptr]
|
||||
if !ok {
|
||||
lo.mu.Unlock()
|
||||
break // Nobody seems to be holding a lock, try again.
|
||||
}
|
||||
fmt.Fprintln(Opts.LogBuf, header)
|
||||
fmt.Fprintln(Opts.LogBuf, "Previous place where the lock was grabbed")
|
||||
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", prev.gid, ptr)
|
||||
printStack(Opts.LogBuf, prev.stack)
|
||||
fmt.Fprintln(Opts.LogBuf, "Have been trying to lock it again for more than", Opts.DeadlockTimeout)
|
||||
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", getGID(), ptr)
|
||||
printStack(Opts.LogBuf, callers(2))
|
||||
fmt.Fprintln(Opts.LogBuf)
|
||||
stacks := stacks()
|
||||
grs := bytes.Split(stacks, []byte("\n\n"))
|
||||
for _, g := range grs {
|
||||
if extractGID(g) == prev.gid {
|
||||
fmt.Fprintln(Opts.LogBuf, "Here is what goroutine", prev.gid, "doing now")
|
||||
Opts.LogBuf.Write(g)
|
||||
fmt.Fprintln(Opts.LogBuf)
|
||||
}
|
||||
}
|
||||
lo.other(ptr)
|
||||
fmt.Fprintln(Opts.LogBuf, "All current goroutines:")
|
||||
Opts.LogBuf.Write(stacks)
|
||||
lo.mu.Unlock()
|
||||
Opts.OnPotentialDeadlock()
|
||||
<-ch
|
||||
PostLock(4, ptr)
|
||||
return
|
||||
case <-ch:
|
||||
PostLock(4, ptr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
PostLock(4, ptr)
|
||||
}
|
||||
|
||||
type lockOrder struct {
|
||||
mu sync.Mutex
|
||||
cur map[interface{}]stackGID // stacktraces + gids for the locks currently taken.
|
||||
order map[beforeAfter]ss // expected order of locks.
|
||||
}
|
||||
|
||||
type stackGID struct {
|
||||
stack []uintptr
|
||||
gid uint64
|
||||
}
|
||||
|
||||
type beforeAfter struct {
|
||||
before interface{}
|
||||
after interface{}
|
||||
}
|
||||
|
||||
type ss struct {
|
||||
before []uintptr
|
||||
after []uintptr
|
||||
}
|
||||
|
||||
var lo = newLockOrder()
|
||||
|
||||
func newLockOrder() *lockOrder {
|
||||
return &lockOrder{
|
||||
cur: map[interface{}]stackGID{},
|
||||
order: map[beforeAfter]ss{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lockOrder) PostLock(skip int, p interface{}) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.cur[p] = stackGID{callers(skip), getGID()}
|
||||
}
|
||||
|
||||
func (l *lockOrder) PreLock(skip int, p interface{}) {
|
||||
if Opts.DisableLockOrderDetection {
|
||||
return
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
stack := callers(skip)
|
||||
gid := getGID()
|
||||
for b, bs := range l.cur {
|
||||
if b == p {
|
||||
continue
|
||||
}
|
||||
if bs.gid != gid { // We want locks taken in the same goroutine only.
|
||||
continue
|
||||
}
|
||||
if s, ok := l.order[beforeAfter{p, b}]; ok {
|
||||
fmt.Fprintln(Opts.LogBuf, header, "Inconsistent locking. saw this ordering in one goroutine:")
|
||||
fmt.Fprintln(Opts.LogBuf, "happened before")
|
||||
printStack(Opts.LogBuf, s.before)
|
||||
fmt.Fprintln(Opts.LogBuf, "happened after")
|
||||
printStack(Opts.LogBuf, s.after)
|
||||
fmt.Fprintln(Opts.LogBuf, "in another goroutine: happened before")
|
||||
printStack(Opts.LogBuf, bs.stack)
|
||||
fmt.Fprintln(Opts.LogBuf, "happend after")
|
||||
printStack(Opts.LogBuf, stack)
|
||||
l.other(p)
|
||||
Opts.OnPotentialDeadlock()
|
||||
}
|
||||
l.order[beforeAfter{b, p}] = ss{bs.stack, stack}
|
||||
if len(l.order) == Opts.MaxMapSize { // Reset the map to keep memory footprint bounded.
|
||||
l.order = map[beforeAfter]ss{}
|
||||
}
|
||||
}
|
||||
l.cur[p] = stackGID{stack, gid}
|
||||
}
|
||||
|
||||
func (l *lockOrder) PostUnlock(p interface{}) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
delete(l.cur, p)
|
||||
}
|
||||
|
||||
type rlocker RWMutex
|
||||
|
||||
func (r *rlocker) Lock() { (*RWMutex)(r).RLock() }
|
||||
func (r *rlocker) Unlock() { (*RWMutex)(r).RUnlock() }
|
||||
|
||||
// Under lo.mu Locked.
|
||||
func (l *lockOrder) other(ptr interface{}) {
|
||||
fmt.Fprintln(Opts.LogBuf, "\nOther goroutines holding locks:")
|
||||
for k, pp := range l.cur {
|
||||
if k == ptr {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(Opts.LogBuf, "goroutine %v lock %p\n", pp.gid, k)
|
||||
printStack(Opts.LogBuf, pp.stack)
|
||||
}
|
||||
fmt.Fprintln(Opts.LogBuf)
|
||||
}
|
||||
|
||||
// Hacky way of getting a goroutine ID.
|
||||
func getGID() uint64 {
|
||||
b := make([]byte, 64)
|
||||
return extractGID(b[:runtime.Stack(b, false)])
|
||||
}
|
||||
|
||||
func extractGID(stack []byte) uint64 {
|
||||
b := bytes.TrimPrefix(stack, []byte("goroutine "))
|
||||
b = b[:bytes.IndexByte(b, ' ')]
|
||||
gid, _ := strconv.ParseUint(string(b), 10, 64)
|
||||
return gid
|
||||
}
|
||||
|
||||
const header = "POTENTIAL DEADLOCK:"
|
||||
127
vendor/github.com/sasha-s/go-deadlock/deadlock_test.go
generated
vendored
Normal file
127
vendor/github.com/sasha-s/go-deadlock/deadlock_test.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package deadlock
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNoDeadlocks(t *testing.T) {
|
||||
defer restore()()
|
||||
Opts.DeadlockTimeout = time.Millisecond * 5000
|
||||
var a RWMutex
|
||||
var b Mutex
|
||||
var c RWMutex
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for k := 0; k < 5; k++ {
|
||||
func() {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
func() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
func() {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
time.Sleep(time.Duration((1000 + rand.Intn(1000))) * time.Millisecond / 200)
|
||||
}()
|
||||
}()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for k := 0; k < 5; k++ {
|
||||
func() {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
func() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
func() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
time.Sleep(time.Duration((1000 + rand.Intn(1000))) * time.Millisecond / 200)
|
||||
}()
|
||||
}()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestLockOrder(t *testing.T) {
|
||||
defer restore()()
|
||||
Opts.DeadlockTimeout = 0
|
||||
var deadlocks uint32
|
||||
Opts.OnPotentialDeadlock = func() {
|
||||
atomic.AddUint32(&deadlocks, 1)
|
||||
}
|
||||
var a RWMutex
|
||||
var b Mutex
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
a.Lock()
|
||||
b.Lock()
|
||||
b.Unlock()
|
||||
a.Unlock()
|
||||
}()
|
||||
wg.Wait()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
b.Lock()
|
||||
a.RLock()
|
||||
a.RUnlock()
|
||||
b.Unlock()
|
||||
}()
|
||||
wg.Wait()
|
||||
if deadlocks != 1 {
|
||||
t.Fatalf("expected 1 deadlock, detected %d", deadlocks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHardDeadlock(t *testing.T) {
|
||||
defer restore()()
|
||||
Opts.DisableLockOrderDetection = true
|
||||
Opts.DeadlockTimeout = time.Millisecond * 20
|
||||
var deadlocks uint32
|
||||
Opts.OnPotentialDeadlock = func() {
|
||||
atomic.AddUint32(&deadlocks, 1)
|
||||
}
|
||||
var mu Mutex
|
||||
mu.Lock()
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
}()
|
||||
select {
|
||||
case <-ch:
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
}
|
||||
if deadlocks != 1 {
|
||||
t.Fatalf("expected 1 deadlock, detected %d", deadlocks)
|
||||
}
|
||||
log.Println("****************")
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func restore() func() {
|
||||
opts := Opts
|
||||
return func() {
|
||||
Opts = opts
|
||||
}
|
||||
}
|
||||
107
vendor/github.com/sasha-s/go-deadlock/stacktraces.go
generated
vendored
Normal file
107
vendor/github.com/sasha-s/go-deadlock/stacktraces.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
package deadlock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func callers(skip int) []uintptr {
|
||||
s := make([]uintptr, 50) // Most relevant context seem to appear near the top of the stack.
|
||||
return s[:runtime.Callers(2+skip, s)]
|
||||
}
|
||||
|
||||
func printStack(w io.Writer, stack []uintptr) {
|
||||
home := os.Getenv("HOME")
|
||||
usr, err := user.Current()
|
||||
if err == nil {
|
||||
home = usr.HomeDir
|
||||
}
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
for i, pc := range stack {
|
||||
f := runtime.FuncForPC(pc)
|
||||
name := f.Name()
|
||||
pkg := ""
|
||||
if pos := strings.LastIndex(name, "/"); pos >= 0 {
|
||||
name = name[pos+1:]
|
||||
}
|
||||
if pos := strings.Index(name, "."); pos >= 0 {
|
||||
pkg = name[:pos]
|
||||
name = name[pos+1:]
|
||||
}
|
||||
file, line := f.FileLine(pc - 1)
|
||||
if (pkg == "runtime" && name == "goexit") || (pkg == "testing" && name == "tRunner") {
|
||||
fmt.Fprintln(w)
|
||||
return
|
||||
}
|
||||
tail := ""
|
||||
if i == 0 {
|
||||
tail = " <<<<<" // Make the line performing a lock prominent.
|
||||
}
|
||||
// Shorten the file name.
|
||||
clean := file
|
||||
if cwd != "" {
|
||||
cl, err := filepath.Rel(cwd, file)
|
||||
if err == nil {
|
||||
clean = cl
|
||||
}
|
||||
}
|
||||
if home != "" {
|
||||
s2 := strings.Replace(file, home, "~", 1)
|
||||
if len(clean) > len(s2) {
|
||||
clean = s2
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s:%d %s.%s %s%s\n", clean, line, pkg, name, code(file, line), tail)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
var fileSources struct {
|
||||
sync.Mutex
|
||||
lines map[string][][]byte
|
||||
}
|
||||
|
||||
// Reads souce file lines from disk if not cached already.
|
||||
func getSourceLines(file string) [][]byte {
|
||||
fileSources.Lock()
|
||||
defer fileSources.Unlock()
|
||||
if fileSources.lines == nil {
|
||||
fileSources.lines = map[string][][]byte{}
|
||||
}
|
||||
if lines, ok := fileSources.lines[file]; ok {
|
||||
return lines
|
||||
}
|
||||
text, _ := ioutil.ReadFile(file)
|
||||
fileSources.lines[file] = bytes.Split(text, []byte{'\n'})
|
||||
return fileSources.lines[file]
|
||||
}
|
||||
|
||||
func code(file string, line int) string {
|
||||
lines := getSourceLines(file)
|
||||
// lines are 1 based.
|
||||
if line >= len(lines) || line <= 0 {
|
||||
return "???"
|
||||
}
|
||||
return "{ " + string(bytes.TrimSpace(lines[line-1])) + " }"
|
||||
}
|
||||
|
||||
// Stacktraces for all goroutines.
|
||||
func stacks() []byte {
|
||||
buf := make([]byte, 1024*16)
|
||||
for {
|
||||
n := runtime.Stack(buf, true)
|
||||
if n < len(buf) {
|
||||
return buf[:n]
|
||||
}
|
||||
buf = make([]byte, 2*len(buf))
|
||||
}
|
||||
}
|
||||
140
vendor/manifest
vendored
140
vendor/manifest
vendored
@@ -4,247 +4,270 @@
|
||||
{
|
||||
"importpath": "github.com/AudriusButkevicius/go-nat-pmp",
|
||||
"repository": "https://github.com/AudriusButkevicius/go-nat-pmp",
|
||||
"vcs": "",
|
||||
"revision": "452c97607362b2ab5a7839b8d1704f0396b640ca",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/bkaradzic/go-lz4",
|
||||
"repository": "https://github.com/bkaradzic/go-lz4",
|
||||
"vcs": "",
|
||||
"revision": "74ddf82598bc4745b965729e9c6a463bedd33049",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/calmh/du",
|
||||
"repository": "https://github.com/calmh/du",
|
||||
"vcs": "",
|
||||
"revision": "3c0690cca16228b97741327b1b6781397afbdb24",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/calmh/luhn",
|
||||
"repository": "https://github.com/calmh/luhn",
|
||||
"vcs": "",
|
||||
"revision": "0c8388ff95fa92d4094011e5a04fc99dea3d1632",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/calmh/xdr",
|
||||
"repository": "https://github.com/calmh/xdr",
|
||||
"vcs": "",
|
||||
"revision": "f9b9f8f7aa27725f5cabb699bd9099ca7ce09143",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/b",
|
||||
"repository": "https://github.com/cznic/b",
|
||||
"vcs": "",
|
||||
"revision": "bcff30a622dbdcb425aba904792de1df606dab7c",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/bufs",
|
||||
"repository": "https://github.com/cznic/bufs",
|
||||
"vcs": "",
|
||||
"revision": "3dcccbd7064a1689f9c093a988ea11ac00e21f51",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/fileutil",
|
||||
"repository": "https://github.com/cznic/fileutil",
|
||||
"vcs": "",
|
||||
"revision": "1c9c88fbf552b3737c7b97e1f243860359687976",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/internal/buffer",
|
||||
"repository": "https://github.com/cznic/internal",
|
||||
"vcs": "",
|
||||
"revision": "cef02a853c3a93623c42eacd574e7ea05f55531b",
|
||||
"branch": "master",
|
||||
"path": "/buffer",
|
||||
"notests": true
|
||||
"path": "/buffer"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/internal/file",
|
||||
"repository": "https://github.com/cznic/internal",
|
||||
"vcs": "",
|
||||
"revision": "cef02a853c3a93623c42eacd574e7ea05f55531b",
|
||||
"branch": "master",
|
||||
"path": "/file",
|
||||
"notests": true
|
||||
"path": "/file"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/internal/slice",
|
||||
"repository": "https://github.com/cznic/internal",
|
||||
"vcs": "",
|
||||
"revision": "cef02a853c3a93623c42eacd574e7ea05f55531b",
|
||||
"branch": "master",
|
||||
"path": "/slice",
|
||||
"notests": true
|
||||
"path": "/slice"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/lldb",
|
||||
"repository": "https://github.com/cznic/lldb",
|
||||
"vcs": "",
|
||||
"revision": "7376b3bed3d27a7b640e264bfaf278d6d5232550",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/mathutil",
|
||||
"repository": "https://github.com/cznic/mathutil",
|
||||
"vcs": "",
|
||||
"revision": "78ad7f262603437f0ecfebc835d80094f89c8f54",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/ql",
|
||||
"repository": "https://github.com/cznic/ql",
|
||||
"vcs": "",
|
||||
"revision": "c81467d34c630800dd4ba81033e234a8159ff2e3",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/sortutil",
|
||||
"repository": "https://github.com/cznic/sortutil",
|
||||
"vcs": "",
|
||||
"revision": "4c7342852e65c2088c981288f2c5610d10b9f7f4",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/strutil",
|
||||
"repository": "https://github.com/cznic/strutil",
|
||||
"vcs": "",
|
||||
"revision": "1eb03e3cc9d345307a45ec82bd3016cde4bd4464",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/cznic/zappy",
|
||||
"repository": "https://github.com/cznic/zappy",
|
||||
"vcs": "",
|
||||
"revision": "2533cb5b45cc6c07421468ce262899ddc9d53fb7",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/d4l3k/messagediff",
|
||||
"repository": "https://github.com/d4l3k/messagediff",
|
||||
"vcs": "",
|
||||
"revision": "7b706999d935b04cf2dbc71a5a5afcbd288aeb48",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/edsrzf/mmap-go",
|
||||
"repository": "https://github.com/edsrzf/mmap-go",
|
||||
"vcs": "",
|
||||
"revision": "935e0e8a636ca4ba70b713f3e38a19e1b77739e8",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/gobwas/glob",
|
||||
"repository": "https://github.com/gobwas/glob",
|
||||
"vcs": "",
|
||||
"revision": "0354991b92587e2742549d3036f3b5bae5ab03f2",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/gogo/protobuf",
|
||||
"repository": "https://github.com/gogo/protobuf",
|
||||
"vcs": "",
|
||||
"revision": "7883e1468d48d969e1c3ce4bcde89b6a7dd4adc4",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/golang/groupcache/lru",
|
||||
"repository": "https://github.com/golang/groupcache",
|
||||
"vcs": "",
|
||||
"revision": "02826c3e79038b59d737d3b1c0a1d937f71a4433",
|
||||
"branch": "master",
|
||||
"path": "/lru",
|
||||
"notests": true
|
||||
"path": "/lru"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/golang/snappy",
|
||||
"repository": "https://github.com/golang/snappy",
|
||||
"vcs": "",
|
||||
"revision": "5f1c01d9f64b941dd9582c638279d046eda6ca31",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/jackpal/gateway",
|
||||
"repository": "https://github.com/jackpal/gateway",
|
||||
"vcs": "",
|
||||
"revision": "3e333950771011fed13be63e62b9f473c5e0d9bf",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/juju/ratelimit",
|
||||
"repository": "https://github.com/juju/ratelimit",
|
||||
"vcs": "",
|
||||
"revision": "77ed1c8a01217656d2080ad51981f6e99adaa177",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/kardianos/osext",
|
||||
"repository": "https://github.com/kardianos/osext",
|
||||
"vcs": "",
|
||||
"revision": "29ae4ffbc9a6fe9fb2bc5029050ce6996ea1d3bc",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/lib/pq",
|
||||
"repository": "https://github.com/lib/pq",
|
||||
"vcs": "",
|
||||
"revision": "ee1442bda7bd1b6a84e913bdb421cb1874ec629d",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/minio/sha256-simd",
|
||||
"repository": "https://github.com/minio/sha256-simd",
|
||||
"vcs": "",
|
||||
"revision": "672e7bc9f3482375df73741cf57a157fe187ec26",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/onsi/ginkgo",
|
||||
"repository": "https://github.com/onsi/ginkgo",
|
||||
"vcs": "",
|
||||
"revision": "ac3d45ddd7ef5c4d7fc4d037b615a81f4d67981e",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/onsi/gomega",
|
||||
"repository": "https://github.com/onsi/gomega",
|
||||
"vcs": "",
|
||||
"revision": "a1094b2db2d4845621602c667bd4ccf09834544e",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/oschwald/geoip2-golang",
|
||||
"repository": "https://github.com/oschwald/geoip2-golang",
|
||||
"vcs": "",
|
||||
"revision": "51714a0e79df40e00a94ae5086ec2a5532c9ee57",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/oschwald/maxminddb-golang",
|
||||
"repository": "https://github.com/oschwald/maxminddb-golang",
|
||||
"vcs": "",
|
||||
"revision": "a26428e0305b837e06fe69221489819126a346c8",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/rcrowley/go-metrics",
|
||||
"repository": "https://github.com/rcrowley/go-metrics",
|
||||
"vcs": "",
|
||||
"revision": "eeba7bd0dd01ace6e690fa833b3f22aaec29af43",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/sasha-s/go-deadlock",
|
||||
"repository": "https://github.com/sasha-s/go-deadlock",
|
||||
"vcs": "git",
|
||||
"revision": "09aefc0ac06ad74d91ca31acdb11fc981c159149",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/stathat/go",
|
||||
"repository": "https://github.com/stathat/go",
|
||||
"vcs": "",
|
||||
"revision": "91dfa3a59c5b233fef9a346a1460f6e2bc889d93",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/syndtr/goleveldb",
|
||||
"repository": "https://github.com/syndtr/goleveldb",
|
||||
"vcs": "",
|
||||
"revision": "6ae1797c0b42b9323fc27ff7dcf568df88f2f33d",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/thejerf/suture",
|
||||
"repository": "https://github.com/thejerf/suture",
|
||||
"vcs": "",
|
||||
"revision": "fe1c0d795ff7a7e54fc54ef6afb36ee0adf0c276",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/vitrun/qart/coding",
|
||||
"repository": "https://github.com/vitrun/qart",
|
||||
"vcs": "",
|
||||
"revision": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0",
|
||||
"branch": "master",
|
||||
"path": "/coding"
|
||||
@@ -252,6 +275,7 @@
|
||||
{
|
||||
"importpath": "github.com/vitrun/qart/gf256",
|
||||
"repository": "https://github.com/vitrun/qart",
|
||||
"vcs": "",
|
||||
"revision": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0",
|
||||
"branch": "master",
|
||||
"path": "/gf256"
|
||||
@@ -259,6 +283,7 @@
|
||||
{
|
||||
"importpath": "github.com/vitrun/qart/qr",
|
||||
"repository": "https://github.com/vitrun/qart",
|
||||
"vcs": "",
|
||||
"revision": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0",
|
||||
"branch": "master",
|
||||
"path": "/qr"
|
||||
@@ -266,6 +291,7 @@
|
||||
{
|
||||
"importpath": "golang.org/x/crypto/bcrypt",
|
||||
"repository": "https://go.googlesource.com/crypto",
|
||||
"vcs": "",
|
||||
"revision": "e311231e83195f401421a286060d65643f9c9d40",
|
||||
"branch": "master",
|
||||
"path": "/bcrypt"
|
||||
@@ -273,6 +299,7 @@
|
||||
{
|
||||
"importpath": "golang.org/x/crypto/blowfish",
|
||||
"repository": "https://go.googlesource.com/crypto",
|
||||
"vcs": "",
|
||||
"revision": "e311231e83195f401421a286060d65643f9c9d40",
|
||||
"branch": "master",
|
||||
"path": "/blowfish"
|
||||
@@ -280,22 +307,23 @@
|
||||
{
|
||||
"importpath": "golang.org/x/net/bpf",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "749a502dd1eaf3e5bfd4f8956748c502357c0bbe",
|
||||
"branch": "master",
|
||||
"path": "/bpf",
|
||||
"notests": true
|
||||
"path": "/bpf"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/net/context",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "749a502dd1eaf3e5bfd4f8956748c502357c0bbe",
|
||||
"branch": "master",
|
||||
"path": "/context",
|
||||
"notests": true
|
||||
"path": "/context"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/net/internal/iana",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "08f168e593b5aab61849054b77981de812666697",
|
||||
"branch": "master",
|
||||
"path": "/internal/iana"
|
||||
@@ -303,14 +331,15 @@
|
||||
{
|
||||
"importpath": "golang.org/x/net/internal/netreflect",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "749a502dd1eaf3e5bfd4f8956748c502357c0bbe",
|
||||
"branch": "master",
|
||||
"path": "/internal/netreflect",
|
||||
"notests": true
|
||||
"path": "/internal/netreflect"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/net/ipv6",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "749a502dd1eaf3e5bfd4f8956748c502357c0bbe",
|
||||
"branch": "master",
|
||||
"path": "/ipv6"
|
||||
@@ -318,6 +347,7 @@
|
||||
{
|
||||
"importpath": "golang.org/x/net/proxy",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "749a502dd1eaf3e5bfd4f8956748c502357c0bbe",
|
||||
"branch": "master",
|
||||
"path": "/proxy"
|
||||
@@ -325,30 +355,31 @@
|
||||
{
|
||||
"importpath": "golang.org/x/net/route",
|
||||
"repository": "https://go.googlesource.com/net",
|
||||
"vcs": "",
|
||||
"revision": "749a502dd1eaf3e5bfd4f8956748c502357c0bbe",
|
||||
"branch": "master",
|
||||
"path": "/route",
|
||||
"notests": true
|
||||
"path": "/route"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/sys/unix",
|
||||
"repository": "https://go.googlesource.com/sys",
|
||||
"vcs": "",
|
||||
"revision": "a408501be4d17ee978c04a618e7a1b22af058c0e",
|
||||
"branch": "master",
|
||||
"path": "/unix",
|
||||
"notests": true
|
||||
"path": "/unix"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/sys/windows",
|
||||
"repository": "https://go.googlesource.com/sys",
|
||||
"vcs": "",
|
||||
"revision": "a408501be4d17ee978c04a618e7a1b22af058c0e",
|
||||
"branch": "master",
|
||||
"path": "/windows",
|
||||
"notests": true
|
||||
"path": "/windows"
|
||||
},
|
||||
{
|
||||
"importpath": "golang.org/x/text/transform",
|
||||
"repository": "https://go.googlesource.com/text",
|
||||
"vcs": "",
|
||||
"revision": "a71fd10341b064c10f4a81ceac72bcf70f26ea34",
|
||||
"branch": "master",
|
||||
"path": "/transform"
|
||||
@@ -356,6 +387,7 @@
|
||||
{
|
||||
"importpath": "golang.org/x/text/unicode/norm",
|
||||
"repository": "https://go.googlesource.com/text",
|
||||
"vcs": "",
|
||||
"revision": "a71fd10341b064c10f4a81ceac72bcf70f26ea34",
|
||||
"branch": "master",
|
||||
"path": "/unicode/norm"
|
||||
|
||||
Reference in New Issue
Block a user