From 23c935b05ab845da97df66a179bedbc69da71d90 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Mon, 19 Oct 2020 08:28:53 +0200 Subject: [PATCH 01/42] lib/db: Ignore not found on delete in recalcGlobal (ref #7026) (#7041) --- lib/db/lowlevel.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go index 3ed684b9d29..314bdf2a361 100644 --- a/lib/db/lowlevel.go +++ b/lib/db/lowlevel.go @@ -457,7 +457,7 @@ func (db *Lowlevel) checkGlobals(folder []byte) error { for dbi.Next() { var vl VersionList if err := vl.Unmarshal(dbi.Value()); err != nil || vl.Empty() { - if err := t.Delete(dbi.Key()); err != nil { + if err := t.Delete(dbi.Key()); err != nil && !backend.IsNotFound(err) { return err } continue @@ -486,7 +486,7 @@ func (db *Lowlevel) checkGlobals(folder []byte) error { } if newVL.Empty() { - if err := t.Delete(dbi.Key()); err != nil { + if err := t.Delete(dbi.Key()); err != nil && !backend.IsNotFound(err) { return err } } else if changed { @@ -880,7 +880,7 @@ func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) { meta := newMetadataTracker(db.keyer) if err := db.checkGlobals(folder); err != nil { - return nil, err + return nil, fmt.Errorf("checking globals: %w", err) } t, err := db.newReadWriteTransaction(meta.CommitHook(folder)) From 01a7ef3b0f840ea001fbab3faa36053af16cc3a1 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Mon, 19 Oct 2020 08:40:37 +0200 Subject: [PATCH 02/42] lib/db: Undo adding user info to panic msgs (ref #7029) (#7040) --- lib/db/lowlevel.go | 15 ++++++++------- lib/db/set.go | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/db/lowlevel.go b/lib/db/lowlevel.go index 314bdf2a361..c0c1b620c3a 100644 --- a/lib/db/lowlevel.go +++ b/lib/db/lowlevel.go @@ -817,7 +817,8 @@ func (db *Lowlevel) getMetaAndCheck(folder string) *metadataTracker { var err error defer func() { if err != nil && !backend.IsClosed(err) { - warnAndPanic(err) + l.Warnf("Fatal error: %v", err) + obfuscateAndPanic(err) } }() @@ -945,14 +946,16 @@ func (db *Lowlevel) verifyLocalSequence(curSeq int64, folder string) bool { t, err := db.newReadOnlyTransaction() if err != nil { - warnAndPanic(err) + l.Warnf("Fatal error: %v", err) + obfuscateAndPanic(err) } ok := true if err := t.withHaveSequence([]byte(folder), curSeq+1, func(fi protocol.FileIntf) bool { ok = false // we got something, which we should not have return false }); err != nil && !backend.IsClosed(err) { - warnAndPanic(err) + l.Warnf("Fatal error: %v", err) + obfuscateAndPanic(err) } t.close() @@ -1165,8 +1168,6 @@ func unchanged(nf, ef protocol.FileIntf) bool { var ldbPathRe = regexp.MustCompile(`(open|write|read) .+[\\/].+[\\/]index[^\\/]+[\\/][^\\/]+: `) -func warnAndPanic(err error) { - l.Warnf("Fatal error: %v", err) - msg := ldbPathRe.ReplaceAllString(err.Error(), "$1 x: ") - panic(msg) +func obfuscateAndPanic(err error) { + panic(ldbPathRe.ReplaceAllString(err.Error(), "$1 x: ")) } diff --git a/lib/db/set.go b/lib/db/set.go index a1c6537542a..e7759ef0199 100644 --- a/lib/db/set.go +++ b/lib/db/set.go @@ -531,5 +531,6 @@ func fatalError(err error, opStr string, db *Lowlevel) { } } } - warnAndPanic(fmt.Errorf("%v: %w:", opStr, err)) + l.Warnf("Fatal error: %v: %v", opStr, err) + obfuscateAndPanic(err) } From 1a8c10a8d0c26ddde4753241545fd29ea41fc16e Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Mon, 19 Oct 2020 08:53:19 +0200 Subject: [PATCH 03/42] lib/model: Use winning version instead of merge on conflict (#6995) --- lib/model/folder.go | 2 +- lib/model/folder_sendonly.go | 1 - lib/model/folder_sendrecv.go | 16 +++------------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/lib/model/folder.go b/lib/model/folder.go index 9d80e1c2098..cc197433c5e 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -488,7 +488,7 @@ func (f *folder) scanSubdirs(subDirs []string) error { case !ok: case gf.IsEquivalentOptional(fi, f.modTimeWindow, false, false, protocol.FlagLocalReceiveOnly): // What we have locally is equivalent to the global file. - fi.Version = fi.Version.Merge(gf.Version) + fi.Version = gf.Version fallthrough case fi.IsDeleted() && (gf.IsReceiveOnlyChanged() || gf.IsDeleted()): // Our item is deleted and the global item is our own diff --git a/lib/model/folder_sendonly.go b/lib/model/folder_sendonly.go index b9233ee4af8..501e98ad2f0 100644 --- a/lib/model/folder_sendonly.go +++ b/lib/model/folder_sendonly.go @@ -75,7 +75,6 @@ func (f *sendOnlyFolder) pull() bool { return true } - file.Version = file.Version.Merge(curFile.Version) batch = append(batch, file) batchSizeBytes += file.ProtoSize() l.Debugln(f, "Merging versions of identical file", file) diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 605ae207961..705c957f38b 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -595,11 +595,9 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot, if !curFile.IsSymlink() && f.inConflict(curFile.Version, file.Version) { // The new file has been changed in conflict with the existing one. We // should file it away as a conflict instead of just removing or - // archiving. Also merge with the version vector we had, to indicate - // we have resolved the conflict. + // archiving. // Symlinks aren't checked for conflicts. - file.Version = file.Version.Merge(curFile.Version) err = f.inWritableDir(func(name string) error { return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, curFile.Name) @@ -773,11 +771,9 @@ func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, s if !curFile.IsDirectory() && !curFile.IsSymlink() && f.inConflict(curFile.Version, file.Version) { // The new file has been changed in conflict with the existing one. We // should file it away as a conflict instead of just removing or - // archiving. Also merge with the version vector we had, to indicate - // we have resolved the conflict. + // archiving. // Directories and symlinks aren't checked for conflicts. - file.Version = file.Version.Merge(curFile.Version) return f.inWritableDir(func(name string) error { return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, curFile.Name) @@ -1213,10 +1209,6 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda f.fs.Chtimes(file.Name, file.ModTime(), file.ModTime()) // never fails - // This may have been a conflict. We should merge the version vectors so - // that our clock doesn't move backwards. - file.Version = file.Version.Merge(curFile.Version) - dbUpdateChan <- dbUpdateJob{file, dbUpdateShortcutFile} } @@ -1543,11 +1535,9 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu if !curFile.IsDirectory() && !curFile.IsSymlink() && f.inConflict(curFile.Version, file.Version) { // The new file has been changed in conflict with the existing one. We // should file it away as a conflict instead of just removing or - // archiving. Also merge with the version vector we had, to indicate - // we have resolved the conflict. + // archiving. // Directories and symlinks aren't checked for conflicts. - file.Version = file.Version.Merge(curFile.Version) err = f.inWritableDir(func(name string) error { return f.moveForConflict(name, file.ModifiedBy.String(), scanChan) }, curFile.Name) From 86b040f595c6a54f5e0c6c07d1ed1d85789550e8 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 21 Oct 2020 07:45:23 +0200 Subject: [PATCH 04/42] gui, man, authors: Update docs, translations, and contributors --- gui/default/assets/lang/lang-da.json | 48 ++++++++++++++-------------- gui/default/assets/lang/lang-de.json | 2 +- gui/default/assets/lang/lang-sv.json | 22 ++++++------- gui/default/assets/lang/lang-tr.json | 2 +- man/stdiscosrv.1 | 2 +- man/strelaysrv.1 | 2 +- man/syncthing-bep.7 | 2 +- man/syncthing-config.5 | 2 +- man/syncthing-device-ids.7 | 2 +- man/syncthing-event-api.7 | 2 +- man/syncthing-faq.7 | 2 +- man/syncthing-globaldisco.7 | 2 +- man/syncthing-localdisco.7 | 2 +- man/syncthing-networking.7 | 2 +- man/syncthing-relay.7 | 2 +- man/syncthing-rest-api.7 | 2 +- man/syncthing-security.7 | 2 +- man/syncthing-stignore.5 | 2 +- man/syncthing-versioning.7 | 2 +- man/syncthing.1 | 2 +- 20 files changed, 53 insertions(+), 53 deletions(-) diff --git a/gui/default/assets/lang/lang-da.json b/gui/default/assets/lang/lang-da.json index bc563cf6edd..0a2c39aea84 100644 --- a/gui/default/assets/lang/lang-da.json +++ b/gui/default/assets/lang/lang-da.json @@ -29,7 +29,7 @@ "Are you sure you want to restore {%count%} files?": "Er du sikker på, at du vil genskabe {{count}} filer?", "Are you sure you want to upgrade?": "Are you sure you want to upgrade?", "Auto Accept": "Autoacceptér", - "Automatic Crash Reporting": "Automatic Crash Reporting", + "Automatic Crash Reporting": "Automatisk nedbrud rapportering", "Automatic upgrade now offers the choice between stable releases and release candidates.": "Den automatiske opdatering tilbyder nu valget mellem stabile udgivelser og udgivelseskandidater.", "Automatic upgrades": "Automatisk opdatering", "Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.", @@ -39,15 +39,15 @@ "Bugs": "Fejl", "Changelog": "Udgivelsesnoter", "Clean out after": "Rens efter", - "Cleaning Versions": "Cleaning Versions", - "Cleanup Interval": "Cleanup Interval", + "Cleaning Versions": "Rydder op i versioner", + "Cleanup Interval": "Ryd op interval", "Click to see discovery failures": "Klik for at se opdagelsesfejl", "Close": "Luk", "Command": "Kommando", "Comment, when used at the start of a line": "Kommentar, når den bruges i starten af en linje", "Compression": "Anvend komprimering", "Configured": "Konfigureret", - "Connected (Unused)": "Connected (Unused)", + "Connected (Unused)": "Tilsluttet (ubrugt)", "Connection Error": "Tilslutnings fejl", "Connection Type": "Tilslutningstype", "Connections": "Forbindelser", @@ -56,7 +56,7 @@ "Copied from original": "Kopieret fra originalen", "Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 de følgende bidragsydere:", "Creating ignore patterns, overwriting an existing file at {%path%}.": "Opretter ignoreringsmønstre; overskriver en eksisterende fil på {{path}}.", - "Currently Shared With Devices": "Currently Shared With Devices", + "Currently Shared With Devices": "i øjeblikket delt med enheder", "Danger!": "Fare!", "Debugging Facilities": "Faciliteter til fejlretning", "Default Folder Path": "Standardmappesti", @@ -71,7 +71,7 @@ "Device rate limits": "Enhedens hastighedsbegrænsning", "Device that last modified the item": "Enhed, som sidst ændrede filen", "Devices": "Enheder", - "Disable Crash Reporting": "Disable Crash Reporting", + "Disable Crash Reporting": "Deaktivere nedbrud rapportering", "Disabled": "Deaktiveret", "Disabled periodic scanning and disabled watching for changes": "Deaktiverede periodisk skanning og deaktiverede overvågning af ændringer", "Disabled periodic scanning and enabled watching for changes": "Deaktiverede periodisk skanning og aktiverede overvågning af ændringer", @@ -79,7 +79,7 @@ "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).", "Discard": "Behold ikke", "Disconnected": "Ikke tilsluttet", - "Disconnected (Unused)": "Disconnected (Unused)", + "Disconnected (Unused)": "Ikke tilsluttet (ubrugt)", "Discovered": "Opdaget", "Discovery": "Opslag", "Discovery Failures": "Fejl ved opdagelse", @@ -91,10 +91,10 @@ "Downloaded": "Downloadet", "Downloading": "Downloader", "Edit": "Redigér", - "Edit Device": "Edit Device", - "Edit Folder": "Edit Folder", + "Edit Device": "Redigere enhed", + "Edit Folder": "Redigere mappe", "Editing {%path%}.": "Redigerer {{path}}.", - "Enable Crash Reporting": "Enable Crash Reporting", + "Enable Crash Reporting": "Aktivere nedbrud rapportering", "Enable NAT traversal": "Aktivér NAT-traversering", "Enable Relaying": "Aktivér videresending", "Enabled": "Aktiveret", @@ -102,7 +102,7 @@ "Enter a non-privileged port number (1024 - 65535).": "Indtast et ikke-priviligeret portnummer (1024–65535).", "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Angiv en kommaadskilt adresseliste (\"tcp://ip:port\", \"tcp://host:port\")  eller \"dynamic\" for automatisk at opdage adressen.", "Enter ignore patterns, one per line.": "Indtast ignoreringsmønstre, ét per linje.", - "Enter up to three octal digits.": "Enter up to three octal digits.", + "Enter up to three octal digits.": "Indtast op til tre oktale cifre.", "Error": "Fejl", "External File Versioning": "Ekstern filversionering", "Failed Items": "Mislykkede filer", @@ -128,10 +128,10 @@ "GUI": "GUI", "GUI Authentication Password": "GUI-adgangskode", "GUI Authentication User": "GUI-brugernavn", - "GUI Authentication: Set User and Password": "GUI Authentication: Set User and Password", + "GUI Authentication: Set User and Password": "GUI godkendelse: Angiv bruger og adgangskode", "GUI Listen Address": "GUI-lytteadresse", "GUI Theme": "GUI-tema", - "General": "Generalt", + "General": "Generelt", "Generate": "Opret", "Global Discovery": "Globalt opslag", "Global Discovery Servers": "Globale opslagsservere", @@ -162,7 +162,7 @@ "Listeners": "Lyttere", "Loading data...": "Indlæser data ...", "Loading...": "Indlæser ...", - "Local Additions": "Local Additions", + "Local Additions": "Lokale tilføjelser", "Local Discovery": "Lokal opslag", "Local State": "Lokal tilstand", "Local State (Total)": "Lokal tilstand (total)", @@ -204,7 +204,7 @@ "Pause": "Pause", "Pause All": "Sæt alt på pause", "Paused": "På pause", - "Paused (Unused)": "Paused (Unused)", + "Paused (Unused)": "Pauset (ubrugt)", "Pending changes": "Ventende ændringer", "Periodic scanning at given interval and disabled watching for changes": "Periodisk skanning med et givent interval og deaktiveret overvågning af ændringer", "Periodic scanning at given interval and enabled watching for changes": "Periodisk skanning med et givent interval og aktiveret overvågning af ændringer", @@ -215,7 +215,7 @@ "Please wait": "Vent venligst", "Prefix indicating that the file can be deleted if preventing directory removal": "Forstavelse, der indikerer, at filen kan slettes, hvis fjernelse at mappe undgåes", "Prefix indicating that the pattern should be matched without case sensitivity": "Forstavelse, der indikerer det mønster, der skal sammenlignes uden versalfølsomhed", - "Preparing to Sync": "Preparing to Sync", + "Preparing to Sync": "Forbereder synkronisering", "Preview": "Forhåndsvisning", "Preview Usage Report": "Forhåndsvisning af forbrugsrapport", "Quick guide to supported patterns": "Kvikguide til understøttede mønstre", @@ -295,7 +295,7 @@ "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Det ser ud til, at Syncthing har problemer med at udføre opgaven. Prøv at genindlæse siden eller genstarte Synching, hvis problemet vedbliver.", "Take me back": "Tag mig tilbage", "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.", - "The Syncthing Authors": "The Syncthing Authors", + "The Syncthing Authors": "Syncthing udviklere", "The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing-administationsfladen er sat op til at kunne fjernstyres uden adgangskode.", "The aggregated statistics are publicly available at the URL below.": "Den indsamlede statistik er offentligt tilgængelig på den nedenstående URL.", "The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.", @@ -331,14 +331,14 @@ "Time the item was last modified": "Tidspunkt for seneste ændring af filen", "Trash Can File Versioning": "Versionering med papirkurv", "Type": "Type", - "UNIX Permissions": "UNIX Permissions", + "UNIX Permissions": "UNIX rettigheder", "Unavailable": "Ikke tilgængelig", "Unavailable/Disabled by administrator or maintainer": "Ikke tilgængelig / deaktiveret af administrator eller vedligeholder", "Undecided (will prompt)": "Ubestemt (du bliver spurgt)", "Unignore": "Fjern ignorering", "Unknown": "Ukendt", "Unshared": "Ikke delt", - "Unshared Devices": "Unshared Devices", + "Unshared Devices": "Ikke delte enheder", "Up to Date": "Fuldt opdateret", "Updated": "Opdateret", "Upgrade": "Opgradér", @@ -353,9 +353,9 @@ "Versions": "Versioner", "Versions Path": "Versionssti", "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner slettes automatisk, hvis de er ældre end den givne maksimum alder eller overstiger det tilladte antal filer i et interval.", - "Waiting to Clean": "Waiting to Clean", - "Waiting to Scan": "Waiting to Scan", - "Waiting to Sync": "Waiting to Sync", + "Waiting to Clean": "Venter på oprydning", + "Waiting to Scan": "Venter på skanning", + "Waiting to Sync": "Venter på synkronisering", "Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Advarsel: Denne sti er en forældermappe til den eksisterende mappe “{{otherFolder}}”.", "Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel: Denne sti er en forældermappe til den eksisterende mappe “{{otherFolderLabel}}” ({{otherFolder}}).", "Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel: Denne sti er en undermappe til den eksisterende mappe “{{otherFolder}}”.", @@ -375,11 +375,11 @@ "You have unsaved changes. Do you really want to discard them?": "Du har ændringer, som ikke er gemt. Er du sikker på, at du ikke vil beholde dem?", "You must keep at least one version.": "Du skal beholde mindst én version.", "days": "dage", - "directories": "directories", + "directories": "kataloger", "files": "filer", "full documentation": "fuld dokumentation", "items": "filer", - "seconds": "seconds", + "seconds": "sekunder", "{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen “{{folder}}”.", "{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker at dele mappen “{{folderlabel}}” ({{folder}})." } \ No newline at end of file diff --git a/gui/default/assets/lang/lang-de.json b/gui/default/assets/lang/lang-de.json index 54673a8d94d..560025e9bb0 100644 --- a/gui/default/assets/lang/lang-de.json +++ b/gui/default/assets/lang/lang-de.json @@ -311,7 +311,7 @@ "The following items could not be synchronized.": "Die folgenden Elemente konnten nicht synchronisiert werden.", "The following items were changed locally.": "Die folgenden Elemente wurden lokal geändert.", "The interval must be a positive number of seconds.": "Das Intervall muss eine positive Zahl von Sekunden sein.", - "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Das Interval, in Sekunden, zwischen den Bereinigungen im Versionen Verzeichnis. 0 um das regelmäßige Bereinigen zu deaktivieren.", + "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Das Intervall, in Sekunden, zwischen den Bereinigungen im Versionsverzeichnis. 0 um das regelmäßige Bereinigen zu deaktivieren.", "The maximum age must be a number and cannot be blank.": "Das Höchstalter muss angegeben werden und eine Zahl sein.", "The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Die längste Zeit, die alte Versionen vorgehalten werden (in Tagen) (0 um alte Versionen für immer zu behalten).", "The number of days must be a number and cannot be blank.": "Die Anzahl von Versionen muss eine Ganzzahl und darf nicht leer sein.", diff --git a/gui/default/assets/lang/lang-sv.json b/gui/default/assets/lang/lang-sv.json index d72e3b1a1b7..88016ffc56e 100644 --- a/gui/default/assets/lang/lang-sv.json +++ b/gui/default/assets/lang/lang-sv.json @@ -126,11 +126,11 @@ "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "För följande mappar uppstod ett fel när du började bevaka ändringar. Det kommer att omförsökas varje minut, så felen kan försvinna snart. Om de fortsätter, försök att åtgärda det underliggande problemet och fråga om hjälp om du inte kan.", "Full Rescan Interval (s)": "Fullständig återkommande skanningsintervall (s)", "GUI": "Grafiskt gränssnitt", - "GUI Authentication Password": "Gränssnittets autentiseringslösenord", - "GUI Authentication User": "Gränssnittets autentiseringsanvändare", - "GUI Authentication: Set User and Password": "Gränssnittets autentisering: Ställ in användare och lösenord", - "GUI Listen Address": "Gränssnittets lyssnaradress", - "GUI Theme": "Gränssnittets tema", + "GUI Authentication Password": "Autentiseringslösenord för gränssnittet", + "GUI Authentication User": "Autentiseringsanvändare för gränssnittet", + "GUI Authentication: Set User and Password": "Autentisering för gränssnittets: Ställ in användare och lösenord", + "GUI Listen Address": "Lyssnaradress för gränssnittet", + "GUI Theme": "Tema för gränssnittet", "General": "Allmänt", "Generate": "Generera", "Global Discovery": "Global annonsering", @@ -211,7 +211,7 @@ "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning vid givna intervall och misslyckades med att ställa in bevakning av ändringar, försöker igen varje 1m:", "Permissions": "Behörigheter", "Please consult the release notes before performing a major upgrade.": "Vänligen läs igenom versionsnyheterna innan du utför en större uppgradering.", - "Please set a GUI Authentication User and Password in the Settings dialog.": "Vänligen ange en användare och lösenord för gränssnittets autentisering i inställningsdialogrutan.", + "Please set a GUI Authentication User and Password in the Settings dialog.": "Vänligen ange en användare och lösenord för autentisering för gränssnittet i inställningsdialogrutan.", "Please wait": "Vänligen vänta", "Prefix indicating that the file can be deleted if preventing directory removal": "Prefix som indikerar att filen kan tas bort om det förhindrar mappborttagning", "Prefix indicating that the pattern should be matched without case sensitivity": "Prefix som indikerar att mönstret ska matchas utan skiftlägeskänslighet", @@ -278,12 +278,12 @@ "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabila utgåvor är försenade med cirka två veckor. Under denna tid går de igenom tester som utgåvskandidater.", "Stable releases only": "Endast stabila utgåvor", "Staggered File Versioning": "Filversionshantering i intervall", - "Start Browser": "Starta webbläsare", + "Start Browser": "Starta webbläsaren", "Statistics": "Statistik", "Stopped": "Stoppad", "Support": "Support", "Support Bundle": "Support Bundle", - "Sync Protocol Listen Addresses": "Synkroniseringsprotokollets lyssnaradresser", + "Sync Protocol Listen Addresses": "Lyssnaradresser för synkroniseringsprotokollets", "Syncing": "Synkroniserar", "Syncthing has been shut down.": "Syncthing har stängts.", "Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:", @@ -291,10 +291,10 @@ "Syncthing is restarting.": "Syncthing startar om.", "Syncthing is upgrading.": "Syncthing uppgraderas.", "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing stöder nu automatiskt kraschrapportering till utvecklarna. Den här funktionen är aktiverad som standard.", - "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd eller så är det problem med din Internetanslutning. Försöker igen...", + "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar vara avstängd eller så är det problem med din internetanslutning. Försöker igen...", "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing verkar ha drabbats av ett problem med behandlingen av din förfrågan. Vänligen uppdatera sidan eller starta om Syncthing om problemet kvarstår.", "Take me back": "Ta mig tillbaka", - "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Det grafiska gränssnittets adressen åsidosätts av startalternativ. Ändringar här träder inte i kraft så länge åsidosättandet är på plats.", + "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Adressen för det grafiska gränssnittets åsidosätts av startalternativ. Ändringar här träder inte i kraft så länge åsidosättandet är på plats.", "The Syncthing Authors": "Syncthing-upphovsmän", "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änglig på webbadressen nedan.", @@ -348,7 +348,7 @@ "Uptime": "Drifttid", "Usage reporting is always enabled for candidate releases.": "Användningsrapportering är alltid aktiverad för kandidatutgåvor.", "Use HTTPS for GUI": "Använd HTTPS för gränssnittet", - "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Användarnamn/lösenord har inte ställts in för gränssnittets autentisering. Överväg att ställa in det.", + "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Användarnamn/lösenord har inte ställts in för autentisering för gränssnittet. Överväg att ställa in det.", "Version": "Version", "Versions": "Versioner", "Versions Path": "Sökväg för versioner", diff --git a/gui/default/assets/lang/lang-tr.json b/gui/default/assets/lang/lang-tr.json index 8a530f9692f..6fa3a7a3144 100644 --- a/gui/default/assets/lang/lang-tr.json +++ b/gui/default/assets/lang/lang-tr.json @@ -100,7 +100,7 @@ "Enabled": "Etkinleştirildi", "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Negatif olmayan bir sayı girin (örn., \"2.35\") ve bir birim seçin. Yüzdeler, toplam disk boyutunun bir parçasıdır.", "Enter a non-privileged port number (1024 - 65535).": "Yetkisiz bir bağlantı noktası numarası girin (1024 - 65535).", - "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Virgülle ayrılmış (\"tcp://ip:b.noktası\", \"tcp://host:b.noktası\") adresler veya otomatik adres keşfi yapmak için \"dynamic\" girin", + "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Virgülle ayrılmış (\"tcp://ip:b.noktası\", \"tcp://anamakine:b.noktası\") adresler veya otomatik adres keşfi yapmak için \"dynamic\" girin.", "Enter ignore patterns, one per line.": "Yoksayma şekillerini girin, her satıra bir tane.", "Enter up to three octal digits.": "En fazla üç sekizlik rakam girin.", "Error": "Hata", diff --git a/man/stdiscosrv.1 b/man/stdiscosrv.1 index 4eb3555f12b..357abfe3bed 100644 --- a/man/stdiscosrv.1 +++ b/man/stdiscosrv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "STDISCOSRV" "1" "Oct 09, 2020" "v1" "Syncthing" +.TH "STDISCOSRV" "1" "Oct 19, 2020" "v1" "Syncthing" .SH NAME stdiscosrv \- Syncthing Discovery Server . diff --git a/man/strelaysrv.1 b/man/strelaysrv.1 index c79b9f8add0..f0a76a8265e 100644 --- a/man/strelaysrv.1 +++ b/man/strelaysrv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "STRELAYSRV" "1" "Oct 09, 2020" "v1" "Syncthing" +.TH "STRELAYSRV" "1" "Oct 19, 2020" "v1" "Syncthing" .SH NAME strelaysrv \- Syncthing Relay Server . diff --git a/man/syncthing-bep.7 b/man/syncthing-bep.7 index b7ea464b199..665d1ab458e 100644 --- a/man/syncthing-bep.7 +++ b/man/syncthing-bep.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-BEP" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-BEP" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-bep \- Block Exchange Protocol v1 . diff --git a/man/syncthing-config.5 b/man/syncthing-config.5 index 20da9e91956..885d09c508e 100644 --- a/man/syncthing-config.5 +++ b/man/syncthing-config.5 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-CONFIG" "5" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-CONFIG" "5" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-config \- Syncthing Configuration . diff --git a/man/syncthing-device-ids.7 b/man/syncthing-device-ids.7 index 14f90e22abd..2c53d863e1b 100644 --- a/man/syncthing-device-ids.7 +++ b/man/syncthing-device-ids.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-DEVICE-IDS" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-DEVICE-IDS" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-device-ids \- Understanding Device IDs . diff --git a/man/syncthing-event-api.7 b/man/syncthing-event-api.7 index 9cebb609504..5bea621cec0 100644 --- a/man/syncthing-event-api.7 +++ b/man/syncthing-event-api.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-EVENT-API" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-EVENT-API" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-event-api \- Event API . diff --git a/man/syncthing-faq.7 b/man/syncthing-faq.7 index 6e7dba5a7db..1eb2f844ba5 100644 --- a/man/syncthing-faq.7 +++ b/man/syncthing-faq.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-FAQ" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-FAQ" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-faq \- Frequently Asked Questions . diff --git a/man/syncthing-globaldisco.7 b/man/syncthing-globaldisco.7 index 0ff974b61f6..7b852f3a577 100644 --- a/man/syncthing-globaldisco.7 +++ b/man/syncthing-globaldisco.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-GLOBALDISCO" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-GLOBALDISCO" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-globaldisco \- Global Discovery Protocol v3 . diff --git a/man/syncthing-localdisco.7 b/man/syncthing-localdisco.7 index 9c2831b5ff5..5117e86cc3c 100644 --- a/man/syncthing-localdisco.7 +++ b/man/syncthing-localdisco.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-LOCALDISCO" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-LOCALDISCO" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-localdisco \- Local Discovery Protocol v4 . diff --git a/man/syncthing-networking.7 b/man/syncthing-networking.7 index a1c9b2c09cf..cc869628cb6 100644 --- a/man/syncthing-networking.7 +++ b/man/syncthing-networking.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-NETWORKING" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-NETWORKING" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-networking \- Firewall Setup . diff --git a/man/syncthing-relay.7 b/man/syncthing-relay.7 index 3e814daa3ae..c6c57ae24aa 100644 --- a/man/syncthing-relay.7 +++ b/man/syncthing-relay.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-RELAY" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-RELAY" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-relay \- Relay Protocol v1 . diff --git a/man/syncthing-rest-api.7 b/man/syncthing-rest-api.7 index 06e05c2c52a..8e80974155f 100644 --- a/man/syncthing-rest-api.7 +++ b/man/syncthing-rest-api.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-REST-API" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-REST-API" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-rest-api \- REST API . diff --git a/man/syncthing-security.7 b/man/syncthing-security.7 index 1eadd4dd0d1..af047fb29d5 100644 --- a/man/syncthing-security.7 +++ b/man/syncthing-security.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-SECURITY" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-SECURITY" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-security \- Security Principles . diff --git a/man/syncthing-stignore.5 b/man/syncthing-stignore.5 index b2aa2f2ea64..fd4554b08eb 100644 --- a/man/syncthing-stignore.5 +++ b/man/syncthing-stignore.5 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-STIGNORE" "5" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-STIGNORE" "5" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-stignore \- Prevent files from being synchronized to other nodes . diff --git a/man/syncthing-versioning.7 b/man/syncthing-versioning.7 index 323ee875f62..639b3c0967e 100644 --- a/man/syncthing-versioning.7 +++ b/man/syncthing-versioning.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-VERSIONING" "7" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING-VERSIONING" "7" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing-versioning \- Keep automatic backups of deleted files by other nodes . diff --git a/man/syncthing.1 b/man/syncthing.1 index 52624899182..ac333998d50 100644 --- a/man/syncthing.1 +++ b/man/syncthing.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING" "1" "Oct 09, 2020" "v1" "Syncthing" +.TH "SYNCTHING" "1" "Oct 19, 2020" "v1" "Syncthing" .SH NAME syncthing \- Syncthing . From 27c91c57d5cab2b2c0d74a753a74304a4feb576f Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Wed, 21 Oct 2020 08:26:10 +0200 Subject: [PATCH 05/42] lib/db: Remove need for the right dev removing globals (fixes #7036) (#7044) --- lib/db/transactions.go | 2 +- lib/model/model_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/db/transactions.go b/lib/db/transactions.go index f83f3f7db92..02c9a8e0512 100644 --- a/lib/db/transactions.go +++ b/lib/db/transactions.go @@ -859,7 +859,7 @@ func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device, file continue } if fv, have := fl.Get(dev[:]); Need(removedFV, have, fv.Version) { - meta.removeNeeded(deviceID, f) + meta.removeNeeded(dev, f) } } diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 05cc05b7db8..25d33f42d0f 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -4142,6 +4142,46 @@ func TestCompletionEmptyGlobal(t *testing.T) { } } +func TestNeedMetaAfterIndexReset(t *testing.T) { + w, fcfg := tmpDefaultWrapper() + waiter, _ := w.SetDevice(config.NewDeviceConfiguration(device2, "device2")) + waiter.Wait() + fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2}) + waiter, _ = w.SetFolder(fcfg) + waiter.Wait() + m := setupModel(w) + defer cleanupModelAndRemoveDir(m, fcfg.Path) + + var seq int64 = 1 + files := []protocol.FileInfo{{Name: "foo", Size: 10, Version: protocol.Vector{}.Update(device1.Short()), Sequence: seq}} + + // Start with two remotes having one file, then both deleting it, then + // only one adding it again. + m.Index(device1, fcfg.ID, files) + m.Index(device2, fcfg.ID, files) + seq++ + files[0].SetDeleted(device2.Short()) + files[0].Sequence = seq + m.IndexUpdate(device2, fcfg.ID, files) + m.IndexUpdate(device1, fcfg.ID, files) + seq++ + files[0].Deleted = false + files[0].Size = 20 + files[0].Version = files[0].Version.Update(device1.Short()) + files[0].Sequence = seq + m.IndexUpdate(device1, fcfg.ID, files) + + if comp := m.Completion(device2, fcfg.ID); comp.NeedItems != 1 { + t.Error("Expected one needed item for device2, got", comp.NeedItems) + } + + // Pretend we had an index reset on device 1 + m.Index(device1, fcfg.ID, files) + if comp := m.Completion(device2, fcfg.ID); comp.NeedItems != 1 { + t.Error("Expected one needed item for device2, got", comp.NeedItems) + } +} + func equalStringsInAnyOrder(a, b []string) bool { if len(a) != len(b) { return false From a17a8cd48bd6e6003bf1d61ca6a1e93540b29833 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Wed, 21 Oct 2020 08:16:44 +0100 Subject: [PATCH 06/42] lib/connections: Fix LAN addresses begin advertised even when disabled (fixes #7035) (#7045) --- lib/connections/quic_listen.go | 2 +- lib/connections/tcp_listen.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/connections/quic_listen.go b/lib/connections/quic_listen.go index d709c7cca2b..a5ff9298f43 100644 --- a/lib/connections/quic_listen.go +++ b/lib/connections/quic_listen.go @@ -160,7 +160,7 @@ func (t *quicListener) URI() *url.URL { } func (t *quicListener) WANAddresses() []*url.URL { - uris := t.LANAddresses() + uris := []*url.URL{t.uri} t.mut.Lock() if t.address != nil { uris = append(uris, t.address) diff --git a/lib/connections/tcp_listen.go b/lib/connections/tcp_listen.go index 74448c95c87..fbe1e3cc8cb 100644 --- a/lib/connections/tcp_listen.go +++ b/lib/connections/tcp_listen.go @@ -146,7 +146,7 @@ func (t *tcpListener) URI() *url.URL { } func (t *tcpListener) WANAddresses() []*url.URL { - uris := t.LANAddresses() + uris := []*url.URL{t.uri} t.mut.RLock() if t.mapping != nil { addrs := t.mapping.ExternalAddresses() From 5c91723ef2878eb66ebf3695e8b2b41b52f421ad Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Wed, 21 Oct 2020 11:51:53 +0200 Subject: [PATCH 07/42] lib/model: Handle index sender lifetime (fixes #7034) (#7038) --- lib/model/indexsender.go | 403 ++++++++++++++++++++++++++++++++++++ lib/model/model.go | 356 ++++++++----------------------- lib/model/model_test.go | 59 ++---- lib/model/requests_test.go | 119 +++++++++++ lib/model/testutils_test.go | 38 ++++ 5 files changed, 668 insertions(+), 307 deletions(-) create mode 100644 lib/model/indexsender.go diff --git a/lib/model/indexsender.go b/lib/model/indexsender.go new file mode 100644 index 00000000000..7f66d7a2f0c --- /dev/null +++ b/lib/model/indexsender.go @@ -0,0 +1,403 @@ +// Copyright (C) 2020 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package model + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/thejerf/suture" + + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/lib/events" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/util" +) + +type indexSender struct { + suture.Service + conn protocol.Connection + folder string + dev string + fset *db.FileSet + prevSequence int64 + evLogger events.Logger + connClosed chan struct{} + token suture.ServiceToken + pauseChan chan struct{} + resumeChan chan *db.FileSet +} + +func (s *indexSender) serve(ctx context.Context) { + var err error + + l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.dev, s.conn, s.prevSequence) + defer l.Debugf("Exiting indexSender for %s to %s at %s: %v", s.folder, s.dev, s.conn, err) + + // We need to send one index, regardless of whether there is something to send or not + err = s.sendIndexTo(ctx) + + // Subscribe to LocalIndexUpdated (we have new information to send) and + // DeviceDisconnected (it might be us who disconnected, so we should + // exit). + sub := s.evLogger.Subscribe(events.LocalIndexUpdated | events.DeviceDisconnected) + defer sub.Unsubscribe() + + paused := false + evChan := sub.C() + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + + for err == nil { + select { + case <-ctx.Done(): + return + case <-s.connClosed: + return + default: + } + + // While we have sent a sequence at least equal to the one + // currently in the database, wait for the local index to update. The + // local index may update for other folders than the one we are + // sending for. + if s.fset.Sequence(protocol.LocalDeviceID) <= s.prevSequence { + select { + case <-ctx.Done(): + return + case <-s.connClosed: + return + case <-evChan: + case <-ticker.C: + case <-s.pauseChan: + paused = true + case s.fset = <-s.resumeChan: + paused = false + } + + continue + } + + if !paused { + err = s.sendIndexTo(ctx) + } + + // Wait a short amount of time before entering the next loop. If there + // are continuous changes happening to the local index, this gives us + // time to batch them up a little. + time.Sleep(250 * time.Millisecond) + } +} + +// Complete implements the suture.IsCompletable interface. When Serve terminates +// before Stop is called, the supervisor will check for this method and if it +// returns true removes the service instead of restarting it. Here it always +// returns true, as indexSender only terminates when a connection is +// closed/has failed, in which case retrying doesn't help. +func (s *indexSender) Complete() bool { return true } + +func (s *indexSender) resume(fset *db.FileSet) { + select { + case <-s.connClosed: + case s.resumeChan <- fset: + } +} + +func (s *indexSender) pause() { + select { + case <-s.connClosed: + case s.pauseChan <- struct{}{}: + } +} + +// sendIndexTo sends file infos with a sequence number higher than prevSequence and +// returns the highest sent sequence number. +func (s *indexSender) sendIndexTo(ctx context.Context) error { + initial := s.prevSequence == 0 + batch := newFileInfoBatch(nil) + batch.flushFn = func(fs []protocol.FileInfo) error { + l.Debugf("%v: Sending %d files (<%d bytes)", s, len(batch.infos), batch.size) + if initial { + initial = false + return s.conn.Index(ctx, s.folder, fs) + } + return s.conn.IndexUpdate(ctx, s.folder, fs) + } + + var err error + var f protocol.FileInfo + snap := s.fset.Snapshot() + defer snap.Release() + previousWasDelete := false + snap.WithHaveSequence(s.prevSequence+1, func(fi protocol.FileIntf) bool { + // This is to make sure that renames (which is an add followed by a delete) land in the same batch. + // Even if the batch is full, we allow a last delete to slip in, we do this by making sure that + // the batch ends with a non-delete, or that the last item in the batch is already a delete + if batch.full() && (!fi.IsDeleted() || previousWasDelete) { + if err = batch.flush(); err != nil { + return false + } + } + + if shouldDebug() { + if fi.SequenceNo() < s.prevSequence+1 { + panic(fmt.Sprintln("sequence lower than requested, got:", fi.SequenceNo(), ", asked to start at:", s.prevSequence+1)) + } + } + + if f.Sequence > 0 && fi.SequenceNo() <= f.Sequence { + l.Warnln("Non-increasing sequence detected: Checking and repairing the db...") + // Abort this round of index sending - the next one will pick + // up from the last successful one with the repeaired db. + defer func() { + if fixed, dbErr := s.fset.RepairSequence(); dbErr != nil { + l.Warnln("Failed repairing sequence entries:", dbErr) + panic("Failed repairing sequence entries") + } else { + s.evLogger.Log(events.Failure, "detected and repaired non-increasing sequence") + l.Infof("Repaired %v sequence entries in database", fixed) + } + }() + return false + } + + f = fi.(protocol.FileInfo) + + // Mark the file as invalid if any of the local bad stuff flags are set. + f.RawInvalid = f.IsInvalid() + // If the file is marked LocalReceive (i.e., changed locally on a + // receive only folder) we do not want it to ever become the + // globally best version, invalid or not. + if f.IsReceiveOnlyChanged() { + f.Version = protocol.Vector{} + } + + // never sent externally + f.LocalFlags = 0 + f.VersionHash = nil + + previousWasDelete = f.IsDeleted() + + batch.append(f) + return true + }) + if err != nil { + return err + } + + err = batch.flush() + + // True if there was nothing to be sent + if f.Sequence == 0 { + return err + } + + s.prevSequence = f.Sequence + return err +} + +func (s *indexSender) String() string { + return fmt.Sprintf("indexSender@%p for %s to %s at %s", s, s.folder, s.dev, s.conn) +} + +type indexSenderRegistry struct { + deviceID protocol.DeviceID + sup *suture.Supervisor + evLogger events.Logger + conn protocol.Connection + closed chan struct{} + indexSenders map[string]*indexSender + startInfos map[string]*indexSenderStartInfo + mut sync.Mutex +} + +func newIndexSenderRegistry(conn protocol.Connection, closed chan struct{}, sup *suture.Supervisor, evLogger events.Logger) *indexSenderRegistry { + return &indexSenderRegistry{ + deviceID: conn.ID(), + conn: conn, + closed: closed, + sup: sup, + evLogger: evLogger, + indexSenders: make(map[string]*indexSender), + startInfos: make(map[string]*indexSenderStartInfo), + mut: sync.Mutex{}, + } +} + +// add starts an index sender for given folder. +// If an index sender is already running, it will be stopped first. +func (r *indexSenderRegistry) add(folder config.FolderConfiguration, fset *db.FileSet, local, remote protocol.Device) { + r.mut.Lock() + r.addLocked(folder, fset, remote, local) + r.mut.Unlock() +} + +func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset *db.FileSet, local, remote protocol.Device) { + if is, ok := r.indexSenders[folder.ID]; ok { + r.sup.RemoveAndWait(is.token, 0) + delete(r.indexSenders, folder.ID) + } + if _, ok := r.startInfos[folder.ID]; ok { + delete(r.startInfos, folder.ID) + } + + myIndexID := fset.IndexID(protocol.LocalDeviceID) + mySequence := fset.Sequence(protocol.LocalDeviceID) + var startSequence int64 + + // This is the other side's description of what it knows + // about us. Lets check to see if we can start sending index + // updates directly or need to send the index from start... + + if local.IndexID == myIndexID { + // They say they've seen our index ID before, so we can + // send a delta update only. + + if local.MaxSequence > mySequence { + // Safety check. They claim to have more or newer + // index data than we have - either we have lost + // index data, or reset the index without resetting + // the IndexID, or something else weird has + // happened. We send a full index to reset the + // situation. + l.Infof("Device %v folder %s is delta index compatible, but seems out of sync with reality", r.deviceID, folder.Description()) + startSequence = 0 + } else { + l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", r.deviceID, folder.Description(), local.MaxSequence) + startSequence = local.MaxSequence + } + } else if local.IndexID != 0 { + // They say they've seen an index ID from us, but it's + // not the right one. Either they are confused or we + // must have reset our database since last talking to + // them. We'll start with a full index transfer. + l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", r.deviceID, folder.Description(), local.IndexID, myIndexID) + startSequence = 0 + } + + // This is the other side's description of themselves. We + // check to see that it matches the IndexID we have on file, + // otherwise we drop our old index data and expect to get a + // completely new set. + + theirIndexID := fset.IndexID(r.deviceID) + if remote.IndexID == 0 { + // They're not announcing an index ID. This means they + // do not support delta indexes and we should clear any + // information we have from them before accepting their + // index, which will presumably be a full index. + fset.Drop(r.deviceID) + } else if remote.IndexID != theirIndexID { + // The index ID we have on file is not what they're + // announcing. They must have reset their database and + // will probably send us a full index. We drop any + // information we have and remember this new index ID + // instead. + l.Infof("Device %v folder %s has a new index ID (%v)", r.deviceID, folder.Description(), remote.IndexID) + fset.Drop(r.deviceID) + fset.SetIndexID(r.deviceID, remote.IndexID) + } + + is := &indexSender{ + conn: r.conn, + connClosed: r.closed, + folder: folder.ID, + fset: fset, + prevSequence: startSequence, + evLogger: r.evLogger, + pauseChan: make(chan struct{}), + resumeChan: make(chan *db.FileSet), + } + is.Service = util.AsService(is.serve, is.String()) + is.token = r.sup.Add(is) + r.indexSenders[folder.ID] = is +} + +// addPaused stores the given info to start an index sender once resume is called +// for this folder. +// If an index sender is already running, it will be stopped. +func (r *indexSenderRegistry) addPaused(folder config.FolderConfiguration, local, remote protocol.Device) { + r.mut.Lock() + defer r.mut.Unlock() + + if is, ok := r.indexSenders[folder.ID]; ok { + r.sup.RemoveAndWait(is.token, 0) + delete(r.indexSenders, folder.ID) + } + r.startInfos[folder.ID] = &indexSenderStartInfo{local, remote} +} + +// remove stops a running index sender or removes one pending to be started. +// It is a noop if the folder isn't known. +func (r *indexSenderRegistry) remove(folder string) { + r.mut.Lock() + defer r.mut.Unlock() + + if is, ok := r.indexSenders[folder]; ok { + r.sup.RemoveAndWait(is.token, 0) + delete(r.indexSenders, folder) + } + delete(r.startInfos, folder) +} + +// removeAllExcept stops all running index senders and removes those pending to be started, +// except mentioned ones. +// It is a noop if the folder isn't known. +func (r *indexSenderRegistry) removeAllExcept(except map[string]struct{}) { + r.mut.Lock() + defer r.mut.Unlock() + + for folder, is := range r.indexSenders { + if _, ok := except[folder]; !ok { + r.sup.RemoveAndWait(is.token, 0) + delete(r.indexSenders, folder) + } + } + for folder := range r.indexSenders { + if _, ok := except[folder]; !ok { + delete(r.startInfos, folder) + } + } +} + +// pause stops a running index sender. +// It is a noop if the folder isn't known or has not been started yet. +func (r *indexSenderRegistry) pause(folder string) { + r.mut.Lock() + defer r.mut.Unlock() + + if is, ok := r.indexSenders[folder]; ok { + is.pause() + } +} + +// resume unpauses an already running index sender or starts it, if it was added +// while paused. +// It is a noop if the folder isn't known. +func (r *indexSenderRegistry) resume(folder config.FolderConfiguration, fset *db.FileSet) { + r.mut.Lock() + defer r.mut.Unlock() + + is, isOk := r.indexSenders[folder.ID] + if info, ok := r.startInfos[folder.ID]; ok { + if isOk { + r.sup.RemoveAndWait(is.token, 0) + delete(r.indexSenders, folder.ID) + } + r.addLocked(folder, fset, info.local, info.remote) + delete(r.startInfos, folder.ID) + } else if isOk { + is.resume(fset) + } +} + +type indexSenderStartInfo struct { + local, remote protocol.Device +} diff --git a/lib/model/model.go b/lib/model/model.go index 06c06ee89c0..6429dadb9ae 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -35,7 +35,6 @@ import ( "github.com/syncthing/syncthing/lib/stats" "github.com/syncthing/syncthing/lib/sync" "github.com/syncthing/syncthing/lib/ur/contract" - "github.com/syncthing/syncthing/lib/util" "github.com/syncthing/syncthing/lib/versioner" ) @@ -149,8 +148,8 @@ type model struct { closed map[protocol.DeviceID]chan struct{} helloMessages map[protocol.DeviceID]protocol.Hello deviceDownloads map[protocol.DeviceID]*deviceDownloadState - remotePausedFolders map[protocol.DeviceID][]string // deviceID -> folders - indexSenderTokens map[protocol.DeviceID][]suture.ServiceToken + remotePausedFolders map[protocol.DeviceID]map[string]struct{} // deviceID -> folders + indexSenders map[protocol.DeviceID]*indexSenderRegistry foldersRunning int32 // for testing only } @@ -172,9 +171,11 @@ var ( errNetworkNotAllowed = errors.New("network not allowed") errNoVersioner = errors.New("folder has no versioner") // errors about why a connection is closed - errIgnoredFolderRemoved = errors.New("folder no longer ignored") - errReplacingConnection = errors.New("replacing connection") - errStopped = errors.New("Syncthing is being stopped") + errIgnoredFolderRemoved = errors.New("folder no longer ignored") + errReplacingConnection = errors.New("replacing connection") + errStopped = errors.New("Syncthing is being stopped") + errMissingRemoteInClusterConfig = errors.New("remote device missing in cluster config") + errMissingLocalInClusterConfig = errors.New("local device missing in cluster config") ) // NewModel creates and starts a new model. The model starts in read-only mode, @@ -222,8 +223,8 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio closed: make(map[protocol.DeviceID]chan struct{}), helloMessages: make(map[protocol.DeviceID]protocol.Hello), deviceDownloads: make(map[protocol.DeviceID]*deviceDownloadState), - remotePausedFolders: make(map[protocol.DeviceID][]string), - indexSenderTokens: make(map[protocol.DeviceID][]suture.ServiceToken), + remotePausedFolders: make(map[protocol.DeviceID]map[string]struct{}), + indexSenders: make(map[protocol.DeviceID]*indexSenderRegistry), } for devID := range cfg.Devices() { m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID.String()) @@ -405,7 +406,10 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) { m.RemoveAndWait(token, 0) } + // We need to hold both fmut and pmut and must acquire locks in the same + // order always. (The locks can be *released* in any order.) m.fmut.Lock() + m.pmut.RLock() isPathUnique := true for folderID, folderCfg := range m.folderCfgs { @@ -420,8 +424,12 @@ func (m *model) removeFolder(cfg config.FolderConfiguration) { } m.cleanupFolderLocked(cfg) + for _, r := range m.indexSenders { + r.remove(cfg.ID) + } m.fmut.Unlock() + m.pmut.RUnlock() // Remove it from the database db.DropFolder(m.db, cfg.ID) @@ -472,14 +480,32 @@ func (m *model) restartFolder(from, to config.FolderConfiguration, cacheIgnoredF fset := m.folderFiles[folder] m.cleanupFolderLocked(from) - if !to.Paused { - if fset == nil { + if to.Paused { + // Care needs to be taken because we already hold fmut and the lock order + // must be the same everywhere. As fmut is acquired first, this is fine. + m.pmut.RLock() + for _, r := range m.indexSenders { + r.pause(to.ID) + } + m.pmut.RUnlock() + } else { + fsetNil := fset == nil + if fsetNil { // Create a new fset. Might take a while and we do it under // locking, but it's unsafe to create fset:s concurrently so // that's the price we pay. fset = db.NewFileSet(folder, to.Filesystem(), m.db) } m.addAndStartFolderLocked(to, fset, cacheIgnoredFiles) + if fsetNil || from.Paused { + for _, devID := range to.DeviceIDs() { + indexSenders, ok := m.indexSenders[devID] + if !ok { + continue + } + indexSenders.resume(to, fset) + } + } } var infoMsg string @@ -979,11 +1005,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon tempIndexFolders := make([]string, 0, len(cm.Folders)) m.pmut.RLock() - conn, ok := m.conn[deviceID] - closed := m.closed[deviceID] - for _, token := range m.indexSenderTokens[deviceID] { - m.RemoveAndWait(token, 0) - } + indexSenderRegistry, ok := m.indexSenders[deviceID] m.pmut.RUnlock() if !ok { panic("bug: ClusterConfig called on closed or nonexistent connection") @@ -1015,11 +1037,14 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon } } - var paused []string - indexSenderTokens := make([]suture.ServiceToken, 0, len(cm.Folders)) + paused := make(map[string]struct{}, len(cm.Folders)) + seenFolders := make(map[string]struct{}, len(cm.Folders)) for _, folder := range cm.Folders { + seenFolders[folder.ID] = struct{}{} + cfg, ok := m.cfg.Folder(folder.ID) if !ok || !cfg.SharedWith(deviceID) { + indexSenderRegistry.remove(folder.ID) if deviceCfg.IgnoredFolder(folder.ID) { l.Infof("Ignoring folder %s from device %s since we are configured to", folder.Description(), deviceID) continue @@ -1034,13 +1059,41 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID) continue } + + var foundRemote, foundLocal bool + var remoteDeviceInfo, localDeviceInfo protocol.Device + for _, dev := range folder.Devices { + if dev.ID == m.id { + localDeviceInfo = dev + foundLocal = true + } else if dev.ID == deviceID { + remoteDeviceInfo = dev + foundRemote = true + } + if foundRemote && foundLocal { + break + } + } + if !foundRemote { + l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID, folder.Description()) + return errMissingRemoteInClusterConfig + } + if !foundLocal { + l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID, folder.Description()) + return errMissingLocalInClusterConfig + } + if folder.Paused { - paused = append(paused, folder.ID) + indexSenderRegistry.remove(folder.ID) + paused[cfg.ID] = struct{}{} continue } + if cfg.Paused { + indexSenderRegistry.addPaused(cfg, localDeviceInfo, remoteDeviceInfo) continue } + m.fmut.RLock() fs, ok := m.folderFiles[folder.ID] m.fmut.RUnlock() @@ -1054,93 +1107,21 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon tempIndexFolders = append(tempIndexFolders, folder.ID) } - myIndexID := fs.IndexID(protocol.LocalDeviceID) - mySequence := fs.Sequence(protocol.LocalDeviceID) - var startSequence int64 - - for _, dev := range folder.Devices { - if dev.ID == m.id { - // This is the other side's description of what it knows - // about us. Lets check to see if we can start sending index - // updates directly or need to send the index from start... - - if dev.IndexID == myIndexID { - // They say they've seen our index ID before, so we can - // send a delta update only. - - if dev.MaxSequence > mySequence { - // Safety check. They claim to have more or newer - // index data than we have - either we have lost - // index data, or reset the index without resetting - // the IndexID, or something else weird has - // happened. We send a full index to reset the - // situation. - l.Infof("Device %v folder %s is delta index compatible, but seems out of sync with reality", deviceID, folder.Description()) - startSequence = 0 - continue - } + indexSenderRegistry.add(cfg, fs, localDeviceInfo, remoteDeviceInfo) - l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", deviceID, folder.Description(), dev.MaxSequence) - startSequence = dev.MaxSequence - } else if dev.IndexID != 0 { - // They say they've seen an index ID from us, but it's - // not the right one. Either they are confused or we - // must have reset our database since last talking to - // them. We'll start with a full index transfer. - l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", deviceID, folder.Description(), dev.IndexID, myIndexID) - startSequence = 0 - } - } else if dev.ID == deviceID { - // This is the other side's description of themselves. We - // check to see that it matches the IndexID we have on file, - // otherwise we drop our old index data and expect to get a - // completely new set. - - theirIndexID := fs.IndexID(deviceID) - if dev.IndexID == 0 { - // They're not announcing an index ID. This means they - // do not support delta indexes and we should clear any - // information we have from them before accepting their - // index, which will presumably be a full index. - fs.Drop(deviceID) - } else if dev.IndexID != theirIndexID { - // The index ID we have on file is not what they're - // announcing. They must have reset their database and - // will probably send us a full index. We drop any - // information we have and remember this new index ID - // instead. - l.Infof("Device %v folder %s has a new index ID (%v)", deviceID, folder.Description(), dev.IndexID) - fs.Drop(deviceID) - fs.SetIndexID(deviceID, dev.IndexID) - } else { - // They're sending a recognized index ID and will most - // likely use delta indexes. We might already have files - // that we need to pull so let the folder runner know - // that it should recheck the index data. - m.fmut.RLock() - if runner := m.folderRunners[folder.ID]; runner != nil { - defer runner.SchedulePull() - } - m.fmut.RUnlock() - } - } - } - - is := &indexSender{ - conn: conn, - connClosed: closed, - folder: folder.ID, - fset: fs, - prevSequence: startSequence, - evLogger: m.evLogger, + // We might already have files that we need to pull so let the + // folder runner know that it should recheck the index data. + m.fmut.RLock() + if runner := m.folderRunners[folder.ID]; runner != nil { + defer runner.SchedulePull() } - is.Service = util.AsService(is.serve, is.String()) - indexSenderTokens = append(indexSenderTokens, m.Add(is)) + m.fmut.RUnlock() } + indexSenderRegistry.removeAllExcept(seenFolders) + m.pmut.Lock() m.remotePausedFolders[deviceID] = paused - m.indexSenderTokens[deviceID] = indexSenderTokens m.pmut.Unlock() // This breaks if we send multiple CM messages during the same connection. @@ -1376,6 +1357,7 @@ func (m *model) Closed(conn protocol.Connection, err error) { delete(m.remotePausedFolders, device) closed := m.closed[device] delete(m.closed, device) + delete(m.indexSenders, device) m.pmut.Unlock() m.progressEmitter.temporaryIndexUnsubscribe(conn) @@ -1779,8 +1761,10 @@ func (m *model) AddConnection(conn connections.Connection, hello protocol.Hello) } m.conn[deviceID] = conn - m.closed[deviceID] = make(chan struct{}) + closed := make(chan struct{}) + m.closed[deviceID] = closed m.deviceDownloads[deviceID] = newDeviceDownloadState() + m.indexSenders[deviceID] = newIndexSenderRegistry(conn, closed, m.Supervisor, m.evLogger) // 0: default, <0: no limiting switch { case device.MaxRequestKiB > 0: @@ -1857,168 +1841,6 @@ func (m *model) deviceWasSeen(deviceID protocol.DeviceID) { } } -type indexSender struct { - suture.Service - conn protocol.Connection - folder string - dev string - fset *db.FileSet - prevSequence int64 - evLogger events.Logger - connClosed chan struct{} -} - -func (s *indexSender) serve(ctx context.Context) { - var err error - - l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.dev, s.conn, s.prevSequence) - defer l.Debugf("Exiting indexSender for %s to %s at %s: %v", s.folder, s.dev, s.conn, err) - - // We need to send one index, regardless of whether there is something to send or not - err = s.sendIndexTo(ctx) - - // Subscribe to LocalIndexUpdated (we have new information to send) and - // DeviceDisconnected (it might be us who disconnected, so we should - // exit). - sub := s.evLogger.Subscribe(events.LocalIndexUpdated | events.DeviceDisconnected) - defer sub.Unsubscribe() - - evChan := sub.C() - ticker := time.NewTicker(time.Minute) - defer ticker.Stop() - - for err == nil { - select { - case <-ctx.Done(): - return - case <-s.connClosed: - return - default: - } - - // While we have sent a sequence at least equal to the one - // currently in the database, wait for the local index to update. The - // local index may update for other folders than the one we are - // sending for. - if s.fset.Sequence(protocol.LocalDeviceID) <= s.prevSequence { - select { - case <-ctx.Done(): - return - case <-s.connClosed: - return - case <-evChan: - case <-ticker.C: - } - - continue - } - - err = s.sendIndexTo(ctx) - - // Wait a short amount of time before entering the next loop. If there - // are continuous changes happening to the local index, this gives us - // time to batch them up a little. - time.Sleep(250 * time.Millisecond) - } -} - -// Complete implements the suture.IsCompletable interface. When Serve terminates -// before Stop is called, the supervisor will check for this method and if it -// returns true removes the service instead of restarting it. Here it always -// returns true, as indexSender only terminates when a connection is -// closed/has failed, in which case retrying doesn't help. -func (s *indexSender) Complete() bool { return true } - -// sendIndexTo sends file infos with a sequence number higher than prevSequence and -// returns the highest sent sequence number. -func (s *indexSender) sendIndexTo(ctx context.Context) error { - initial := s.prevSequence == 0 - batch := newFileInfoBatch(nil) - batch.flushFn = func(fs []protocol.FileInfo) error { - l.Debugf("%v: Sending %d files (<%d bytes)", s, len(batch.infos), batch.size) - if initial { - initial = false - return s.conn.Index(ctx, s.folder, fs) - } - return s.conn.IndexUpdate(ctx, s.folder, fs) - } - - var err error - var f protocol.FileInfo - snap := s.fset.Snapshot() - defer snap.Release() - previousWasDelete := false - snap.WithHaveSequence(s.prevSequence+1, func(fi protocol.FileIntf) bool { - // This is to make sure that renames (which is an add followed by a delete) land in the same batch. - // Even if the batch is full, we allow a last delete to slip in, we do this by making sure that - // the batch ends with a non-delete, or that the last item in the batch is already a delete - if batch.full() && (!fi.IsDeleted() || previousWasDelete) { - if err = batch.flush(); err != nil { - return false - } - } - - if shouldDebug() { - if fi.SequenceNo() < s.prevSequence+1 { - panic(fmt.Sprintln("sequence lower than requested, got:", fi.SequenceNo(), ", asked to start at:", s.prevSequence+1)) - } - } - - if f.Sequence > 0 && fi.SequenceNo() <= f.Sequence { - l.Warnln("Non-increasing sequence detected: Checking and repairing the db...") - // Abort this round of index sending - the next one will pick - // up from the last successful one with the repeaired db. - defer func() { - if fixed, dbErr := s.fset.RepairSequence(); dbErr != nil { - l.Warnln("Failed repairing sequence entries:", dbErr) - panic("Failed repairing sequence entries") - } else { - s.evLogger.Log(events.Failure, "detected and repaired non-increasing sequence") - l.Infof("Repaired %v sequence entries in database", fixed) - } - }() - return false - } - - f = fi.(protocol.FileInfo) - - // Mark the file as invalid if any of the local bad stuff flags are set. - f.RawInvalid = f.IsInvalid() - // If the file is marked LocalReceive (i.e., changed locally on a - // receive only folder) we do not want it to ever become the - // globally best version, invalid or not. - if f.IsReceiveOnlyChanged() { - f.Version = protocol.Vector{} - } - - // never sent externally - f.LocalFlags = 0 - f.VersionHash = nil - - previousWasDelete = f.IsDeleted() - - batch.append(f) - return true - }) - if err != nil { - return err - } - - err = batch.flush() - - // True if there was nothing to be sent - if f.Sequence == 0 { - return err - } - - s.prevSequence = f.Sequence - return err -} - -func (s *indexSender) String() string { - return fmt.Sprintf("indexSender@%p for %s to %s at %s", s, s.folder, s.dev, s.conn) -} - func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) { m.pmut.RLock() nc, ok := m.conn[deviceID] @@ -2373,12 +2195,12 @@ func (m *model) Availability(folder string, file protocol.FileInfo, block protoc var availabilities []Availability snap := fs.Snapshot() defer snap.Release() -next: for _, device := range snap.Availability(file.Name) { - for _, pausedFolder := range m.remotePausedFolders[device] { - if pausedFolder == folder { - continue next - } + if _, ok := m.remotePausedFolders[device]; !ok { + continue + } + if _, ok := m.remotePausedFolders[device][folder]; ok { + continue } _, ok := m.conn[device] if ok { diff --git a/lib/model/model_test.go b/lib/model/model_test.go index 25d33f42d0f..8458d6a2062 100644 --- a/lib/model/model_test.go +++ b/lib/model/model_test.go @@ -496,23 +496,16 @@ func TestIntroducer(t *testing.T) { }, }, }) - m.ClusterConfig(device1, protocol.ClusterConfig{ - Folders: []protocol.Folder{ - { - ID: "folder1", - Devices: []protocol.Device{ - { - ID: device2, - Introducer: true, - SkipIntroductionRemovals: true, - }, - }, - }, - }, + cc := basicClusterConfig(myID, device1, "folder1") + cc.Folders[0].Devices = append(cc.Folders[0].Devices, protocol.Device{ + ID: device2, + Introducer: true, + SkipIntroductionRemovals: true, }) + m.ClusterConfig(device1, cc) if newDev, ok := m.cfg.Device(device2); !ok || !newDev.Introducer || !newDev.SkipIntroductionRemovals { - t.Error("devie 2 missing or wrong flags") + t.Error("device 2 missing or wrong flags") } if !contains(m.cfg.Folders()["folder1"], device2, device1) { @@ -549,20 +542,13 @@ func TestIntroducer(t *testing.T) { }, }, }) - m.ClusterConfig(device1, protocol.ClusterConfig{ - Folders: []protocol.Folder{ - { - ID: "folder2", - Devices: []protocol.Device{ - { - ID: device2, - Introducer: true, - SkipIntroductionRemovals: true, - }, - }, - }, - }, + cc = basicClusterConfig(myID, device1, "folder2") + cc.Folders[0].Devices = append(cc.Folders[0].Devices, protocol.Device{ + ID: device2, + Introducer: true, + SkipIntroductionRemovals: true, }) + m.ClusterConfig(device1, cc) // Should not get introducer, as it's already unset, and it's an existing device. if newDev, ok := m.cfg.Device(device2); !ok || newDev.Introducer || newDev.SkipIntroductionRemovals { @@ -703,20 +689,13 @@ func TestIntroducer(t *testing.T) { }, }, }) - m.ClusterConfig(device1, protocol.ClusterConfig{ - Folders: []protocol.Folder{ - { - ID: "folder2", - Devices: []protocol.Device{ - { - ID: device2, - Introducer: true, - SkipIntroductionRemovals: true, - }, - }, - }, - }, + cc = basicClusterConfig(myID, device1, "folder2") + cc.Folders[0].Devices = append(cc.Folders[0].Devices, protocol.Device{ + ID: device2, + Introducer: true, + SkipIntroductionRemovals: true, }) + m.ClusterConfig(device1, cc) if _, ok := m.cfg.Device(device2); !ok { t.Error("device 2 should not have been removed") diff --git a/lib/model/requests_test.go b/lib/model/requests_test.go index ee2cf289555..bdbdc0f77e9 100644 --- a/lib/model/requests_test.go +++ b/lib/model/requests_test.go @@ -1147,3 +1147,122 @@ func TestRequestLastFileProgress(t *testing.T) { t.Fatal("Timed out before file was requested") } } + +func TestRequestIndexSenderPause(t *testing.T) { + m, fc, fcfg := setupModelWithConnection() + tfs := fcfg.Filesystem() + defer cleanupModelAndRemoveDir(m, tfs.URI()) + + indexChan := make(chan []protocol.FileInfo) + fc.mut.Lock() + fc.indexFn = func(_ context.Context, folder string, fs []protocol.FileInfo) { + indexChan <- fs + } + fc.mut.Unlock() + + var seq int64 = 1 + files := []protocol.FileInfo{{Name: "foo", Size: 10, Version: protocol.Vector{}.Update(myID.Short()), Sequence: seq}} + + // Both devices connected, noone paused + localIndexUpdate(m, fcfg.ID, files) + select { + case <-time.After(5 * time.Second): + l.Infoln("timeout") + t.Fatal("timed out before receiving index") + case <-indexChan: + } + + // Remote paused + + cc := basicClusterConfig(device1, myID, fcfg.ID) + cc.Folders[0].Paused = true + m.ClusterConfig(device1, cc) + + seq++ + files[0].Sequence = seq + files[0].Version = files[0].Version.Update(myID.Short()) + localIndexUpdate(m, fcfg.ID, files) + + // I don't see what to hook into to ensure an index update is not sent. + dur := 50 * time.Millisecond + if !testing.Short() { + dur = 2 * time.Second + } + select { + case <-time.After(dur): + case <-indexChan: + t.Error("Received index despite remote being paused") + } + + // Remote unpaused + + cc.Folders[0].Paused = false + m.ClusterConfig(device1, cc) + select { + case <-time.After(5 * time.Second): + t.Fatal("timed out before receiving index") + case <-indexChan: + } + + // Local paused and resume + + fcfg.Paused = true + waiter, _ := m.cfg.SetFolder(fcfg) + waiter.Wait() + + fcfg.Paused = false + waiter, _ = m.cfg.SetFolder(fcfg) + waiter.Wait() + + seq++ + files[0].Sequence = seq + files[0].Version = files[0].Version.Update(myID.Short()) + localIndexUpdate(m, fcfg.ID, files) + select { + case <-time.After(5 * time.Second): + t.Fatal("timed out before receiving index") + case <-indexChan: + } + + // Local and remote paused, then first resume remote, then local + + cc.Folders[0].Paused = true + m.ClusterConfig(device1, cc) + + fcfg.Paused = true + waiter, _ = m.cfg.SetFolder(fcfg) + waiter.Wait() + + cc.Folders[0].Paused = false + m.ClusterConfig(device1, cc) + + fcfg.Paused = false + waiter, _ = m.cfg.SetFolder(fcfg) + waiter.Wait() + + seq++ + files[0].Sequence = seq + files[0].Version = files[0].Version.Update(myID.Short()) + localIndexUpdate(m, fcfg.ID, files) + select { + case <-time.After(5 * time.Second): + t.Fatal("timed out before receiving index") + case <-indexChan: + } + + // Folder removed on remote + + cc = protocol.ClusterConfig{} + m.ClusterConfig(device1, cc) + + seq++ + files[0].Sequence = seq + files[0].Version = files[0].Version.Update(myID.Short()) + localIndexUpdate(m, fcfg.ID, files) + + select { + case <-time.After(dur): + case <-indexChan: + t.Error("Received index despite remote not having the folder") + } +} diff --git a/lib/model/testutils_test.go b/lib/model/testutils_test.go index e4187c2819b..7980866f565 100644 --- a/lib/model/testutils_test.go +++ b/lib/model/testutils_test.go @@ -230,3 +230,41 @@ func folderIgnoresAlwaysReload(m *model, fcfg config.FolderConfiguration) { m.addAndStartFolderLockedWithIgnores(fcfg, fset, ignores) m.fmut.Unlock() } + +func basicClusterConfig(local, remote protocol.DeviceID, folders ...string) protocol.ClusterConfig { + var cc protocol.ClusterConfig + for _, folder := range folders { + cc.Folders = append(cc.Folders, protocol.Folder{ + ID: folder, + Devices: []protocol.Device{ + { + ID: local, + }, + { + ID: remote, + }, + }, + }) + } + return cc +} + +func localIndexUpdate(m *model, folder string, fs []protocol.FileInfo) { + m.fmut.RLock() + fset := m.folderFiles[folder] + m.fmut.RUnlock() + + fset.Update(protocol.LocalDeviceID, fs) + seq := fset.Sequence(protocol.LocalDeviceID) + filenames := make([]string, len(fs)) + for i, file := range fs { + filenames[i] = file.Name + } + m.evLogger.Log(events.LocalIndexUpdated, map[string]interface{}{ + "folder": folder, + "items": len(fs), + "filenames": filenames, + "sequence": seq, + "version": seq, // legacy for sequence + }) +} From 2ba3be5e4dc538e3883e32be1d0ad12b45a02746 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Wed, 21 Oct 2020 14:21:09 +0200 Subject: [PATCH 08/42] lib/db: Add mechanism to repair db without schema update (ref #7044) (#7047) --- lib/db/schemaupdater.go | 63 ++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/lib/db/schemaupdater.go b/lib/db/schemaupdater.go index 6a998d2a488..c33c3a25208 100644 --- a/lib/db/schemaupdater.go +++ b/lib/db/schemaupdater.go @@ -28,8 +28,12 @@ import ( // 10-11: v1.6.0 // 12-13: v1.7.0 // 14: v1.9.0 +// +// dbMigrationVersion is for migrations that do not change the schema and thus +// do not put restrictions on downgrades (e.g. for repairs after a bugfix). const ( dbVersion = 14 + dbMigrationVersion = 15 dbMinSyncthingVersion = "v1.9.0" ) @@ -46,6 +50,8 @@ func (e *databaseDowngradeError) Error() string { return fmt.Sprintf("Syncthing %s required", e.minSyncthingVersion) } +// UpdateSchema updates a possibly outdated database to the current schema and +// also does repairs where necessary. func UpdateSchema(db *Lowlevel) error { updater := &schemaUpdater{db} return updater.updateSchema() @@ -77,33 +83,44 @@ func (db *schemaUpdater) updateSchema() error { return err } - if prevVersion == dbVersion { + prevMigration, _, err := miscDB.Int64("dbMigrationVersion") + if err != nil { + return err + } + // Cover versions before adding `dbMigrationVersion` (== 0) and possible future weirdness. + if prevMigration < prevVersion { + prevMigration = prevVersion + } + + if prevVersion == dbVersion && prevMigration >= dbMigrationVersion { return nil } type migration struct { - schemaVersion int64 - migration func(prevVersion int) error + schemaVersion int64 + migrationVersion int64 + migration func(prevSchema int) error } var migrations = []migration{ - {1, db.updateSchema0to1}, - {2, db.updateSchema1to2}, - {3, db.updateSchema2to3}, - {5, db.updateSchemaTo5}, - {6, db.updateSchema5to6}, - {7, db.updateSchema6to7}, - {9, db.updateSchemaTo9}, - {10, db.updateSchemaTo10}, - {11, db.updateSchemaTo11}, - {13, db.updateSchemaTo13}, - {14, db.updateSchemaTo14}, + {1, 1, db.updateSchema0to1}, + {2, 2, db.updateSchema1to2}, + {3, 3, db.updateSchema2to3}, + {5, 5, db.updateSchemaTo5}, + {6, 6, db.updateSchema5to6}, + {7, 7, db.updateSchema6to7}, + {9, 9, db.updateSchemaTo9}, + {10, 10, db.updateSchemaTo10}, + {11, 11, db.updateSchemaTo11}, + {13, 13, db.updateSchemaTo13}, + {14, 14, db.updateSchemaTo14}, + {14, 15, db.migration15}, } for _, m := range migrations { - if prevVersion < m.schemaVersion { - l.Infof("Migrating database to schema version %d...", m.schemaVersion) + if prevMigration < m.migrationVersion { + l.Infof("Running database migration %d...", m.migrationVersion) if err := m.migration(int(prevVersion)); err != nil { - return fmt.Errorf("failed migrating to version %v: %w", m.schemaVersion, err) + return fmt.Errorf("failed to do migration %v: %w", m.migrationVersion, err) } } } @@ -114,6 +131,9 @@ func (db *schemaUpdater) updateSchema() error { if err := miscDB.PutString("dbMinSyncthingVersion", dbMinSyncthingVersion); err != nil { return err } + if err := miscDB.PutInt64("dbMigrationVersion", dbMigrationVersion); err != nil { + return err + } l.Infoln("Compacting database after migration...") return db.Compact() @@ -749,6 +769,15 @@ func (db *schemaUpdater) updateSchemaTo14(_ int) error { return nil } +func (db *schemaUpdater) migration15(_ int) error { + for _, folder := range db.ListFolders() { + if _, err := db.recalcMeta(folder); err != nil { + return err + } + } + return nil +} + func (db *schemaUpdater) rewriteGlobals(t readWriteTransaction) error { it, err := t.NewPrefixIterator([]byte{KeyTypeGlobal}) if err != nil { From 1c2be84e4e0140ad4e894822b5188fc30a73442f Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 22 Oct 2020 13:05:31 +0200 Subject: [PATCH 09/42] lib/model: Pass device infos as struct (fixes #7051) (#7052) --- lib/model/indexsender.go | 32 ++++++++++++++++---------------- lib/model/model.go | 10 ++++++++-- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/model/indexsender.go b/lib/model/indexsender.go index 7f66d7a2f0c..288db08c4c5 100644 --- a/lib/model/indexsender.go +++ b/lib/model/indexsender.go @@ -233,13 +233,13 @@ func newIndexSenderRegistry(conn protocol.Connection, closed chan struct{}, sup // add starts an index sender for given folder. // If an index sender is already running, it will be stopped first. -func (r *indexSenderRegistry) add(folder config.FolderConfiguration, fset *db.FileSet, local, remote protocol.Device) { +func (r *indexSenderRegistry) add(folder config.FolderConfiguration, fset *db.FileSet, startInfo *indexSenderStartInfo) { r.mut.Lock() - r.addLocked(folder, fset, remote, local) + r.addLocked(folder, fset, startInfo) r.mut.Unlock() } -func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset *db.FileSet, local, remote protocol.Device) { +func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset *db.FileSet, startInfo *indexSenderStartInfo) { if is, ok := r.indexSenders[folder.ID]; ok { r.sup.RemoveAndWait(is.token, 0) delete(r.indexSenders, folder.ID) @@ -256,11 +256,11 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset // about us. Lets check to see if we can start sending index // updates directly or need to send the index from start... - if local.IndexID == myIndexID { + if startInfo.local.IndexID == myIndexID { // They say they've seen our index ID before, so we can // send a delta update only. - if local.MaxSequence > mySequence { + if startInfo.local.MaxSequence > mySequence { // Safety check. They claim to have more or newer // index data than we have - either we have lost // index data, or reset the index without resetting @@ -270,15 +270,15 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset l.Infof("Device %v folder %s is delta index compatible, but seems out of sync with reality", r.deviceID, folder.Description()) startSequence = 0 } else { - l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", r.deviceID, folder.Description(), local.MaxSequence) - startSequence = local.MaxSequence + l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", r.deviceID, folder.Description(), startInfo.local.MaxSequence) + startSequence = startInfo.local.MaxSequence } - } else if local.IndexID != 0 { + } else if startInfo.local.IndexID != 0 { // They say they've seen an index ID from us, but it's // not the right one. Either they are confused or we // must have reset our database since last talking to // them. We'll start with a full index transfer. - l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", r.deviceID, folder.Description(), local.IndexID, myIndexID) + l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", r.deviceID, folder.Description(), startInfo.local.IndexID, myIndexID) startSequence = 0 } @@ -288,21 +288,21 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset // completely new set. theirIndexID := fset.IndexID(r.deviceID) - if remote.IndexID == 0 { + if startInfo.remote.IndexID == 0 { // They're not announcing an index ID. This means they // do not support delta indexes and we should clear any // information we have from them before accepting their // index, which will presumably be a full index. fset.Drop(r.deviceID) - } else if remote.IndexID != theirIndexID { + } else if startInfo.remote.IndexID != theirIndexID { // The index ID we have on file is not what they're // announcing. They must have reset their database and // will probably send us a full index. We drop any // information we have and remember this new index ID // instead. - l.Infof("Device %v folder %s has a new index ID (%v)", r.deviceID, folder.Description(), remote.IndexID) + l.Infof("Device %v folder %s has a new index ID (%v)", r.deviceID, folder.Description(), startInfo.remote.IndexID) fset.Drop(r.deviceID) - fset.SetIndexID(r.deviceID, remote.IndexID) + fset.SetIndexID(r.deviceID, startInfo.remote.IndexID) } is := &indexSender{ @@ -323,7 +323,7 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset // addPaused stores the given info to start an index sender once resume is called // for this folder. // If an index sender is already running, it will be stopped. -func (r *indexSenderRegistry) addPaused(folder config.FolderConfiguration, local, remote protocol.Device) { +func (r *indexSenderRegistry) addPaused(folder config.FolderConfiguration, startInfo *indexSenderStartInfo) { r.mut.Lock() defer r.mut.Unlock() @@ -331,7 +331,7 @@ func (r *indexSenderRegistry) addPaused(folder config.FolderConfiguration, local r.sup.RemoveAndWait(is.token, 0) delete(r.indexSenders, folder.ID) } - r.startInfos[folder.ID] = &indexSenderStartInfo{local, remote} + r.startInfos[folder.ID] = startInfo } // remove stops a running index sender or removes one pending to be started. @@ -391,7 +391,7 @@ func (r *indexSenderRegistry) resume(folder config.FolderConfiguration, fset *db r.sup.RemoveAndWait(is.token, 0) delete(r.indexSenders, folder.ID) } - r.addLocked(folder, fset, info.local, info.remote) + r.addLocked(folder, fset, info) delete(r.startInfos, folder.ID) } else if isOk { is.resume(fset) diff --git a/lib/model/model.go b/lib/model/model.go index 6429dadb9ae..5464e1117c1 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1090,7 +1090,10 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon } if cfg.Paused { - indexSenderRegistry.addPaused(cfg, localDeviceInfo, remoteDeviceInfo) + indexSenderRegistry.addPaused(cfg, &indexSenderStartInfo{ + local: localDeviceInfo, + remote: remoteDeviceInfo, + }) continue } @@ -1107,7 +1110,10 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon tempIndexFolders = append(tempIndexFolders, folder.ID) } - indexSenderRegistry.add(cfg, fs, localDeviceInfo, remoteDeviceInfo) + indexSenderRegistry.add(cfg, fs, &indexSenderStartInfo{ + local: localDeviceInfo, + remote: remoteDeviceInfo, + }) // We might already have files that we need to pull so let the // folder runner know that it should recheck the index data. From f0f60ba2e74737f16c9fac5e9447d1835fa3deb5 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Thu, 22 Oct 2020 19:54:35 +0200 Subject: [PATCH 10/42] lib/api: Add /rest/config endpoint (fixes #6540) (#7001) --- go.mod | 1 + go.sum | 1 + .../syncthing/core/syncthingController.js | 8 +- lib/api/api.go | 199 ++++----- lib/api/api_test.go | 180 ++++++++- lib/api/confighandler.go | 378 ++++++++++++++++++ lib/api/mocked_config_test.go | 12 + lib/config/config.go | 2 + lib/config/migrations.go | 52 +-- lib/config/migrations_test.go | 2 + lib/config/wrapper.go | 34 ++ lib/protocol/deviceid.go | 2 +- 12 files changed, 717 insertions(+), 154 deletions(-) create mode 100644 lib/api/confighandler.go diff --git a/go.mod b/go.mod index 41e03eb8adb..74ca0a18cf7 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/greatroar/blobloom v0.3.0 github.com/jackpal/gateway v1.0.6 github.com/jackpal/go-nat-pmp v1.0.2 + github.com/julienschmidt/httprouter v1.2.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kr/pretty v0.2.0 // indirect github.com/lib/pq v1.2.0 diff --git a/go.sum b/go.sum index 19cf5cce9e5..50d3d0275b9 100644 --- a/go.sum +++ b/go.sum @@ -173,6 +173,7 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 5af03062211..b4643099383 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -268,7 +268,7 @@ angular.module('syncthing.core') $scope.$on(Events.CONFIG_SAVED, function (event, arg) { updateLocalConfig(arg.data); - $http.get(urlbase + '/system/config/insync').success(function (data) { + $http.get(urlbase + '/config/insync').success(function (data) { $scope.configInSync = data.configInSync; }).error($scope.emitHTTPError); }); @@ -578,12 +578,12 @@ angular.module('syncthing.core') } function refreshConfig() { - $http.get(urlbase + '/system/config').success(function (data) { + $http.get(urlbase + '/config').success(function (data) { updateLocalConfig(data); console.log("refreshConfig", data); }).error($scope.emitHTTPError); - $http.get(urlbase + '/system/config/insync').success(function (data) { + $http.get(urlbase + '/config/insync').success(function (data) { $scope.configInSync = data.configInSync; }).error($scope.emitHTTPError); } @@ -1257,7 +1257,7 @@ angular.module('syncthing.core') 'Content-Type': 'application/json' } }; - $http.post(urlbase + '/system/config', cfg, opts).success(function () { + $http.put(urlbase + '/config', cfg, opts).success(function () { refreshConfig(); if (callback) { diff --git a/lib/api/api.go b/lib/api/api.go index 15a76bf8069..9d8791bf518 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -31,10 +31,10 @@ import ( "strings" "time" + "github.com/julienschmidt/httprouter" metrics "github.com/rcrowley/go-metrics" "github.com/thejerf/suture" "github.com/vitrun/qart/qr" - "golang.org/x/crypto/bcrypt" "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/config" @@ -81,7 +81,6 @@ type service struct { connectionsService connections.Service fss model.FolderSummaryService urService *ur.Service - systemConfigMut sync.Mutex // serializes posts to /rest/system/config contr Controller noUpgrade bool tlsDefaultCommonName string @@ -123,7 +122,6 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam connectionsService: connectionsService, fss: fss, urService: urService, - systemConfigMut: sync.NewMutex(), guiErrors: errors, systemLog: systemLog, contr: contr, @@ -243,60 +241,80 @@ func (s *service) serve(ctx context.Context) { s.cfg.Subscribe(s) defer s.cfg.Unsubscribe(s) + restMux := httprouter.New() + // The GET handlers - getRestMux := http.NewServeMux() - getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // [device] [folder] - getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file - getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder - getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page] - getRestMux.HandleFunc("/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page] - getRestMux.HandleFunc("/rest/db/localchanged", s.getDBLocalChanged) // folder - getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder - getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels] - getRestMux.HandleFunc("/rest/folder/versions", s.getFolderVersions) // folder - getRestMux.HandleFunc("/rest/folder/errors", s.getFolderErrors) // folder - getRestMux.HandleFunc("/rest/folder/pullerrors", s.getFolderErrors) // folder (deprecated) - getRestMux.HandleFunc("/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events] - getRestMux.HandleFunc("/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout] - getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // - - getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // - - getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id - getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // - - getRestMux.HandleFunc("/rest/svc/report", s.getReport) // - - getRestMux.HandleFunc("/rest/svc/random/string", s.getRandomString) // [length] - getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current - getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // - - getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // - - getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // - - getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // - - getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // - - getRestMux.HandleFunc("/rest/system/ping", s.restPing) // - - getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // - - getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // - - getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // - - getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // - - getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since] - getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since] + restMux.HandlerFunc(http.MethodGet, "/rest/db/completion", s.getDBCompletion) // [device] [folder] + restMux.HandlerFunc(http.MethodGet, "/rest/db/file", s.getDBFile) // folder file + restMux.HandlerFunc(http.MethodGet, "/rest/db/ignores", s.getDBIgnores) // folder + restMux.HandlerFunc(http.MethodGet, "/rest/db/need", s.getDBNeed) // folder [perpage] [page] + restMux.HandlerFunc(http.MethodGet, "/rest/db/remoteneed", s.getDBRemoteNeed) // device folder [perpage] [page] + restMux.HandlerFunc(http.MethodGet, "/rest/db/localchanged", s.getDBLocalChanged) // folder + restMux.HandlerFunc(http.MethodGet, "/rest/db/status", s.getDBStatus) // folder + restMux.HandlerFunc(http.MethodGet, "/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels] + restMux.HandlerFunc(http.MethodGet, "/rest/folder/versions", s.getFolderVersions) // folder + restMux.HandlerFunc(http.MethodGet, "/rest/folder/errors", s.getFolderErrors) // folder + restMux.HandlerFunc(http.MethodGet, "/rest/folder/pullerrors", s.getFolderErrors) // folder (deprecated) + restMux.HandlerFunc(http.MethodGet, "/rest/events", s.getIndexEvents) // [since] [limit] [timeout] [events] + restMux.HandlerFunc(http.MethodGet, "/rest/events/disk", s.getDiskEvents) // [since] [limit] [timeout] + restMux.HandlerFunc(http.MethodGet, "/rest/stats/device", s.getDeviceStats) // - + restMux.HandlerFunc(http.MethodGet, "/rest/stats/folder", s.getFolderStats) // - + restMux.HandlerFunc(http.MethodGet, "/rest/svc/deviceid", s.getDeviceID) // id + restMux.HandlerFunc(http.MethodGet, "/rest/svc/lang", s.getLang) // - + restMux.HandlerFunc(http.MethodGet, "/rest/svc/report", s.getReport) // - + restMux.HandlerFunc(http.MethodGet, "/rest/svc/random/string", s.getRandomString) // [length] + restMux.HandlerFunc(http.MethodGet, "/rest/system/browse", s.getSystemBrowse) // current + restMux.HandlerFunc(http.MethodGet, "/rest/system/connections", s.getSystemConnections) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/discovery", s.getSystemDiscovery) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/error", s.getSystemError) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/ping", s.restPing) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/status", s.getSystemStatus) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/upgrade", s.getSystemUpgrade) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/version", s.getSystemVersion) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/debug", s.getSystemDebug) // - + restMux.HandlerFunc(http.MethodGet, "/rest/system/log", s.getSystemLog) // [since] + restMux.HandlerFunc(http.MethodGet, "/rest/system/log.txt", s.getSystemLogTxt) // [since] // The POST handlers - postRestMux := http.NewServeMux() - postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page] - postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder - postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder - postRestMux.HandleFunc("/rest/db/revert", s.postDBRevert) // folder - postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay] - postRestMux.HandleFunc("/rest/folder/versions", s.postFolderVersionsRestore) // folder - postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // - postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // - postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // - - postRestMux.HandleFunc("/rest/system/ping", s.restPing) // - - postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder] - postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // - - postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // - - postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // - - postRestMux.HandleFunc("/rest/system/pause", s.makeDevicePauseHandler(true)) // [device] - postRestMux.HandleFunc("/rest/system/resume", s.makeDevicePauseHandler(false)) // [device] - postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable] + restMux.HandlerFunc(http.MethodPost, "/rest/db/prio", s.postDBPrio) // folder file [perpage] [page] + restMux.HandlerFunc(http.MethodPost, "/rest/db/ignores", s.postDBIgnores) // folder + restMux.HandlerFunc(http.MethodPost, "/rest/db/override", s.postDBOverride) // folder + restMux.HandlerFunc(http.MethodPost, "/rest/db/revert", s.postDBRevert) // folder + restMux.HandlerFunc(http.MethodPost, "/rest/db/scan", s.postDBScan) // folder [sub...] [delay] + restMux.HandlerFunc(http.MethodPost, "/rest/folder/versions", s.postFolderVersionsRestore) // folder + restMux.HandlerFunc(http.MethodPost, "/rest/system/error", s.postSystemError) // + restMux.HandlerFunc(http.MethodPost, "/rest/system/error/clear", s.postSystemErrorClear) // - + restMux.HandlerFunc(http.MethodPost, "/rest/system/ping", s.restPing) // - + restMux.HandlerFunc(http.MethodPost, "/rest/system/reset", s.postSystemReset) // [folder] + restMux.HandlerFunc(http.MethodPost, "/rest/system/restart", s.postSystemRestart) // - + restMux.HandlerFunc(http.MethodPost, "/rest/system/shutdown", s.postSystemShutdown) // - + restMux.HandlerFunc(http.MethodPost, "/rest/system/upgrade", s.postSystemUpgrade) // - + restMux.HandlerFunc(http.MethodPost, "/rest/system/pause", s.makeDevicePauseHandler(true)) // [device] + restMux.HandlerFunc(http.MethodPost, "/rest/system/resume", s.makeDevicePauseHandler(false)) // [device] + restMux.HandlerFunc(http.MethodPost, "/rest/system/debug", s.postSystemDebug) // [enable] [disable] + + // Config endpoints + + configBuilder := &configMuxBuilder{ + Router: restMux, + id: s.id, + cfg: s.cfg, + mut: sync.NewMutex(), + } + + configBuilder.registerConfig("/rest/config/") + configBuilder.registerConfigInsync("/rest/config/insync") + configBuilder.registerFolders("/rest/config/folders") + configBuilder.registerDevices("/rest/config/devices") + configBuilder.registerFolder("/rest/config/folders/:id") + configBuilder.registerDevice("/rest/config/devices/:id") + configBuilder.registerOptions("/rest/config/options") + configBuilder.registerLDAP("/rest/config/ldap") + configBuilder.registerGUI("/rest/config/gui") + + // Deprecated config endpoints + configBuilder.registerConfigDeprecated("/rest/system/config") // POST instead of PUT + configBuilder.registerConfigInsync("/rest/system/config/insync") // Debug endpoints, not for general use debugMux := http.NewServeMux() @@ -305,15 +323,14 @@ func (s *service) serve(ctx context.Context) { debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf) debugMux.HandleFunc("/rest/debug/support", s.getSupportBundle) - getRestMux.Handle("/rest/debug/", s.whenDebugging(debugMux)) + restMux.Handler(http.MethodGet, "/rest/debug/", s.whenDebugging(debugMux)) - // A handler that splits requests between the two above and disables - // caching - restMux := noCacheMiddleware(metricsMiddleware(getPostHandler(getRestMux, postRestMux))) + // A handler that disables caching + noCacheRestMux := noCacheMiddleware(metricsMiddleware(restMux)) // The main routing handler mux := http.NewServeMux() - mux.Handle("/rest/", restMux) + mux.Handle("/rest/", noCacheRestMux) mux.HandleFunc("/qr/", s.getQR) // Serve compiled in assets unless an asset directory was set (for development) @@ -446,19 +463,6 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool { return true } -func getPostHandler(get, post http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case "GET": - get.ServeHTTP(w, r) - case "POST": - post.ServeHTTP(w, r) - default: - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - } - }) -} - func debugMiddleware(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t0 := time.Now() @@ -837,57 +841,6 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) { }) } -func (s *service) getSystemConfig(w http.ResponseWriter, r *http.Request) { - sendJSON(w, s.cfg.RawCopy()) -} - -func (s *service) postSystemConfig(w http.ResponseWriter, r *http.Request) { - s.systemConfigMut.Lock() - defer s.systemConfigMut.Unlock() - - to, err := config.ReadJSON(r.Body, s.id) - r.Body.Close() - if err != nil { - l.Warnln("Decoding posted config:", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if to.GUI.Password != s.cfg.GUI().Password { - if to.GUI.Password != "" && !bcryptExpr.MatchString(to.GUI.Password) { - hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0) - if err != nil { - l.Warnln("bcrypting password:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - to.GUI.Password = string(hash) - } - } - - // Activate and save. Wait for the configuration to become active before - // completing the request. - - if wg, err := s.cfg.Replace(to); err != nil { - l.Warnln("Replacing config:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } else { - wg.Wait() - } - - if err := s.cfg.Save(); err != nil { - l.Warnln("Saving config:", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} - -func (s *service) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) { - sendJSON(w, map[string]bool{"configInSync": !s.cfg.RequiresRestart()}) -} - func (s *service) postSystemRestart(w http.ResponseWriter, r *http.Request) { s.flushResponse(`{"ok": "restarting"}`, w) go s.contr.Restart() diff --git a/lib/api/api_test.go b/lib/api/api_test.go index 5444327118b..4ef45c95696 100644 --- a/lib/api/api_test.go +++ b/lib/api/api_test.go @@ -40,8 +40,13 @@ import ( var ( confDir = filepath.Join("testdata", "config") token = filepath.Join(confDir, "csrftokens.txt") + dev1 protocol.DeviceID ) +func init() { + dev1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR") +} + func TestMain(m *testing.M) { orig := locations.GetBaseDir(locations.ConfigBaseDir) locations.SetBaseDir(locations.ConfigBaseDir, confDir) @@ -396,6 +401,56 @@ func TestAPIServiceRequests(t *testing.T) { Type: "text/plain", Prefix: "", }, + + // /rest/config + { + URL: "/rest/config/folders", + Code: 200, + Type: "application/json", + Prefix: "", + }, + { + URL: "/rest/config/folders/missing", + Code: 404, + Type: "text/plain", + Prefix: "", + }, + { + URL: "/rest/config/devices", + Code: 200, + Type: "application/json", + Prefix: "", + }, + { + URL: "/rest/config/devices/illegalid", + Code: 400, + Type: "text/plain", + Prefix: "", + }, + { + URL: "/rest/config/devices/" + protocol.GlobalDeviceID.String(), + Code: 404, + Type: "text/plain", + Prefix: "", + }, + { + URL: "/rest/config/options", + Code: 200, + Type: "application/json", + Prefix: "{", + }, + { + URL: "/rest/config/gui", + Code: 200, + Type: "application/json", + Prefix: "{", + }, + { + URL: "/rest/config/ldap", + Code: 200, + Type: "application/json", + Prefix: "{", + }, } for _, tc := range cases { @@ -520,7 +575,7 @@ func TestHTTPLogin(t *testing.T) { } } -func startHTTP(cfg *mockedConfig) (string, *suture.Supervisor, error) { +func startHTTP(cfg config.Wrapper) (string, *suture.Supervisor, error) { m := new(mockedModel) assetDir := "../../gui" eventSub := new(mockedEventSub) @@ -552,7 +607,7 @@ func startHTTP(cfg *mockedConfig) (string, *suture.Supervisor, error) { return "", nil, fmt.Errorf("weird address from API service: %w", err) } - host, _, _ := net.SplitHostPort(cfg.gui.RawAddress) + host, _, _ := net.SplitHostPort(cfg.GUI().RawAddress) if host == "" || host == "0.0.0.0" { host = "127.0.0.1" } @@ -1174,6 +1229,127 @@ func TestShouldRegenerateCertificate(t *testing.T) { } } +func TestConfigChanges(t *testing.T) { + t.Parallel() + + const testAPIKey = "foobarbaz" + cfg := config.Configuration{ + GUI: config.GUIConfiguration{ + RawAddress: "127.0.0.1:0", + RawUseTLS: false, + APIKey: testAPIKey, + }, + } + tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-") + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name()) + w := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger) + tmpFile.Close() + baseURL, sup, err := startHTTP(w) + if err != nil { + t.Fatal("Unexpected error from getting base URL:", err) + } + defer sup.Stop() + + cli := &http.Client{ + Timeout: time.Second, + } + + do := func(req *http.Request, status int) *http.Response { + t.Helper() + req.Header.Set("X-API-Key", testAPIKey) + resp, err := cli.Do(req) + if err != nil { + t.Fatal(err) + } + if resp.StatusCode != status { + t.Errorf("Expected status %v, got %v", status, resp.StatusCode) + } + return resp + } + + mod := func(method, path string, data interface{}) { + t.Helper() + bs, err := json.Marshal(data) + if err != nil { + t.Fatal(err) + } + req, _ := http.NewRequest(method, baseURL+path, bytes.NewReader(bs)) + do(req, http.StatusOK).Body.Close() + } + + get := func(path string) *http.Response { + t.Helper() + req, _ := http.NewRequest(http.MethodGet, baseURL+path, nil) + return do(req, http.StatusOK) + } + + dev1Path := "/rest/config/devices/" + dev1.String() + + // Create device + mod(http.MethodPut, "/rest/config/devices", []config.DeviceConfiguration{{DeviceID: dev1}}) + + // Check its there + get(dev1Path).Body.Close() + + // Modify just a single attribute + mod(http.MethodPatch, dev1Path, map[string]bool{"Paused": true}) + + // Check that attribute + resp := get(dev1Path) + var dev config.DeviceConfiguration + if err := unmarshalTo(resp.Body, &dev); err != nil { + t.Fatal(err) + } + if !dev.Paused { + t.Error("Expected device to be paused") + } + + folder2Path := "/rest/config/folders/folder2" + + // Create a folder and add another + mod(http.MethodPut, "/rest/config/folders", []config.FolderConfiguration{{ID: "folder1", Path: "folder1"}}) + mod(http.MethodPut, folder2Path, config.FolderConfiguration{ID: "folder2", Path: "folder2"}) + + // Check they are there + get("/rest/config/folders/folder1").Body.Close() + get(folder2Path).Body.Close() + + // Modify just a single attribute + mod(http.MethodPatch, folder2Path, map[string]bool{"Paused": true}) + + // Check that attribute + resp = get(folder2Path) + var folder config.FolderConfiguration + if err := unmarshalTo(resp.Body, &folder); err != nil { + t.Fatal(err) + } + if !dev.Paused { + t.Error("Expected folder to be paused") + } + + // Delete folder2 + req, _ := http.NewRequest(http.MethodDelete, baseURL+folder2Path, nil) + do(req, http.StatusOK) + + // Check folder1 is still there and folder2 gone + get("/rest/config/folders/folder1").Body.Close() + req, _ = http.NewRequest(http.MethodGet, baseURL+folder2Path, nil) + do(req, http.StatusNotFound) + + mod(http.MethodPatch, "/rest/config/options", map[string]int{"maxSendKbps": 50}) + resp = get("/rest/config/options") + var opts config.OptionsConfiguration + if err := unmarshalTo(resp.Body, &opts); err != nil { + t.Fatal(err) + } + if opts.MaxSendKbps != 50 { + t.Error("Exepcted 50 for MaxSendKbps, got", opts.MaxSendKbps) + } +} + func equalStrings(a, b []string) bool { if len(a) != len(b) { return false diff --git a/lib/api/confighandler.go b/lib/api/confighandler.go new file mode 100644 index 00000000000..0bf3b120564 --- /dev/null +++ b/lib/api/confighandler.go @@ -0,0 +1,378 @@ +// Copyright (C) 2020 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package api + +import ( + "encoding/json" + "io" + "io/ioutil" + "net/http" + + "github.com/julienschmidt/httprouter" + "golang.org/x/crypto/bcrypt" + + "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/sync" +) + +type configMuxBuilder struct { + *httprouter.Router + id protocol.DeviceID + cfg config.Wrapper + mut sync.Mutex +} + +func (c *configMuxBuilder) registerConfig(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.RawCopy()) + }) + + c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustConfig(w, r) + }) +} + +func (c *configMuxBuilder) registerConfigDeprecated(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.RawCopy()) + }) + + c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustConfig(w, r) + }) +} + +func (c *configMuxBuilder) registerConfigInsync(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, map[string]bool{"configInSync": !c.cfg.RequiresRestart()}) + }) +} + +func (c *configMuxBuilder) registerFolders(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.FolderList()) + }) + + c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) { + c.mut.Lock() + defer c.mut.Unlock() + var folders []config.FolderConfiguration + if err := unmarshalTo(r.Body, &folders); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetFolders(folders) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) + }) + + c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) { + c.mut.Lock() + defer c.mut.Unlock() + var folder config.FolderConfiguration + if err := unmarshalTo(r.Body, &folder); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetFolder(folder) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) + }) +} + +func (c *configMuxBuilder) registerDevices(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.DeviceList()) + }) + + c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) { + c.mut.Lock() + defer c.mut.Unlock() + var devices []config.DeviceConfiguration + if err := unmarshalTo(r.Body, &devices); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetDevices(devices) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) + }) + + c.HandlerFunc(http.MethodPost, path, func(w http.ResponseWriter, r *http.Request) { + c.mut.Lock() + defer c.mut.Unlock() + var device config.DeviceConfiguration + if err := unmarshalTo(r.Body, &device); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetDevice(device) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) + }) +} + +func (c *configMuxBuilder) registerFolder(path string) { + c.Handle(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) { + folder, ok := c.cfg.Folder(p.ByName("id")) + if !ok { + http.Error(w, "No folder with given ID", http.StatusNotFound) + return + } + sendJSON(w, folder) + }) + + c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + c.adjustFolder(w, r, config.FolderConfiguration{}) + }) + + c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + folder, ok := c.cfg.Folder(p.ByName("id")) + if !ok { + http.Error(w, "No folder with given ID", http.StatusNotFound) + return + } + c.adjustFolder(w, r, folder) + }) + + c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) { + waiter, err := c.cfg.RemoveFolder(p.ByName("id")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + c.finish(w, waiter) + }) +} + +func (c *configMuxBuilder) registerDevice(path string) { + deviceFromParams := func(w http.ResponseWriter, p httprouter.Params) (config.DeviceConfiguration, bool) { + id, err := protocol.DeviceIDFromString(p.ByName("id")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return config.DeviceConfiguration{}, false + } + device, ok := c.cfg.Device(id) + if !ok { + http.Error(w, "No device with given ID", http.StatusNotFound) + return config.DeviceConfiguration{}, false + } + return device, true + } + + c.Handle(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) { + if device, ok := deviceFromParams(w, p); ok { + sendJSON(w, device) + } + }) + + c.Handle(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + c.adjustDevice(w, r, config.DeviceConfiguration{}) + }) + + c.Handle(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + if device, ok := deviceFromParams(w, p); ok { + c.adjustDevice(w, r, device) + } + }) + + c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) { + id, err := protocol.DeviceIDFromString(p.ByName("id")) + waiter, err := c.cfg.RemoveDevice(id) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + c.finish(w, waiter) + }) +} + +func (c *configMuxBuilder) registerOptions(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.Options()) + }) + + c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustOptions(w, r, config.OptionsConfiguration{}) + }) + + c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustOptions(w, r, c.cfg.Options()) + }) +} + +func (c *configMuxBuilder) registerLDAP(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.LDAP()) + }) + + c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustLDAP(w, r, config.LDAPConfiguration{}) + }) + + c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustLDAP(w, r, c.cfg.LDAP()) + }) +} + +func (c *configMuxBuilder) registerGUI(path string) { + c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) { + sendJSON(w, c.cfg.GUI()) + }) + + c.HandlerFunc(http.MethodPut, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustGUI(w, r, config.GUIConfiguration{}) + }) + + c.HandlerFunc(http.MethodPatch, path, func(w http.ResponseWriter, r *http.Request) { + c.adjustGUI(w, r, c.cfg.GUI()) + }) +} + +func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request) { + c.mut.Lock() + defer c.mut.Unlock() + cfg, err := config.ReadJSON(r.Body, c.id) + r.Body.Close() + if err != nil { + l.Warnln("Decoding posted config:", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if cfg.GUI.Password, err = checkGUIPassword(c.cfg.GUI().Password, cfg.GUI.Password); err != nil { + l.Warnln("bcrypting password:", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + waiter, err := c.cfg.Replace(cfg) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) +} + +func (c *configMuxBuilder) adjustFolder(w http.ResponseWriter, r *http.Request, folder config.FolderConfiguration) { + c.mut.Lock() + defer c.mut.Unlock() + if err := unmarshalTo(r.Body, &folder); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetFolder(folder) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) +} + +func (c *configMuxBuilder) adjustDevice(w http.ResponseWriter, r *http.Request, device config.DeviceConfiguration) { + c.mut.Lock() + defer c.mut.Unlock() + if err := unmarshalTo(r.Body, &device); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetDevice(device) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) +} + +func (c *configMuxBuilder) adjustOptions(w http.ResponseWriter, r *http.Request, opts config.OptionsConfiguration) { + c.mut.Lock() + defer c.mut.Unlock() + if err := unmarshalTo(r.Body, &opts); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetOptions(opts) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) +} + +func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui config.GUIConfiguration) { + c.mut.Lock() + defer c.mut.Unlock() + oldPassword := gui.Password + err := unmarshalTo(r.Body, &gui) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if gui.Password, err = checkGUIPassword(oldPassword, gui.Password); err != nil { + l.Warnln("bcrypting password:", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + waiter, err := c.cfg.SetGUI(gui) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) +} + +func (c *configMuxBuilder) adjustLDAP(w http.ResponseWriter, r *http.Request, ldap config.LDAPConfiguration) { + c.mut.Lock() + defer c.mut.Unlock() + if err := unmarshalTo(r.Body, &ldap); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + waiter, err := c.cfg.SetLDAP(ldap) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + c.finish(w, waiter) +} + +// Unmarshals the content of the given body and stores it in to (i.e. to must be a pointer). +func unmarshalTo(body io.ReadCloser, to interface{}) error { + bs, err := ioutil.ReadAll(body) + body.Close() + if err != nil { + return err + } + return json.Unmarshal(bs, to) +} + +func checkGUIPassword(oldPassword, newPassword string) (string, error) { + if newPassword == oldPassword { + return newPassword, nil + } + hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), 0) + return string(hash), err +} + +func (c *configMuxBuilder) finish(w http.ResponseWriter, waiter config.Waiter) { + waiter.Wait() + if err := c.cfg.Save(); err != nil { + l.Warnln("Saving config:", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} diff --git a/lib/api/mocked_config_test.go b/lib/api/mocked_config_test.go index 1ac538d4b0b..9ff426ae8d2 100644 --- a/lib/api/mocked_config_test.go +++ b/lib/api/mocked_config_test.go @@ -28,6 +28,10 @@ func (c *mockedConfig) LDAP() config.LDAPConfiguration { return config.LDAPConfiguration{} } +func (c *mockedConfig) SetLDAP(config.LDAPConfiguration) (config.Waiter, error) { + return noopWaiter{}, nil +} + func (c *mockedConfig) RawCopy() config.Configuration { cfg := config.Configuration{} util.SetDefaults(&cfg.Options) @@ -54,6 +58,10 @@ func (c *mockedConfig) Devices() map[protocol.DeviceID]config.DeviceConfiguratio return nil } +func (c *mockedConfig) DeviceList() []config.DeviceConfiguration { + return nil +} + func (c *mockedConfig) SetDevice(config.DeviceConfiguration) (config.Waiter, error) { return noopWaiter{}, nil } @@ -102,6 +110,10 @@ func (c *mockedConfig) SetFolders(folders []config.FolderConfiguration) (config. return noopWaiter{}, nil } +func (c *mockedConfig) RemoveFolder(id string) (config.Waiter, error) { + return noopWaiter{}, nil +} + func (c *mockedConfig) Device(id protocol.DeviceID) (config.DeviceConfiguration, bool) { return config.DeviceConfiguration{}, false } diff --git a/lib/config/config.go b/lib/config/config.go index c7a760daf39..dd7a26175aa 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -304,7 +304,9 @@ func (cfg *Configuration) clean() error { } // Upgrade configuration versions as appropriate + migrationsMut.Lock() migrations.apply(cfg) + migrationsMut.Unlock() // Build a list of available devices existingDevices := make(map[protocol.DeviceID]bool) diff --git a/lib/config/migrations.go b/lib/config/migrations.go index 5f47b0d373f..09a23658d1f 100644 --- a/lib/config/migrations.go +++ b/lib/config/migrations.go @@ -14,6 +14,7 @@ import ( "runtime" "sort" "strings" + "sync" "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/upgrade" @@ -24,30 +25,33 @@ import ( // config version. The conversion function can be nil in which case we just // update the config version. The order of migrations doesn't matter here, // put the newest on top for readability. -var migrations = migrationSet{ - {32, migrateToConfigV32}, - {31, migrateToConfigV31}, - {30, migrateToConfigV30}, - {29, migrateToConfigV29}, - {28, migrateToConfigV28}, - {27, migrateToConfigV27}, - {26, nil}, // triggers database update - {25, migrateToConfigV25}, - {24, migrateToConfigV24}, - {23, migrateToConfigV23}, - {22, migrateToConfigV22}, - {21, migrateToConfigV21}, - {20, migrateToConfigV20}, - {19, nil}, // Triggers a database tweak - {18, migrateToConfigV18}, - {17, nil}, // Fsync = true removed - {16, nil}, // Triggers a database tweak - {15, migrateToConfigV15}, - {14, migrateToConfigV14}, - {13, migrateToConfigV13}, - {12, migrateToConfigV12}, - {11, migrateToConfigV11}, -} +var ( + migrations = migrationSet{ + {32, migrateToConfigV32}, + {31, migrateToConfigV31}, + {30, migrateToConfigV30}, + {29, migrateToConfigV29}, + {28, migrateToConfigV28}, + {27, migrateToConfigV27}, + {26, nil}, // triggers database update + {25, migrateToConfigV25}, + {24, migrateToConfigV24}, + {23, migrateToConfigV23}, + {22, migrateToConfigV22}, + {21, migrateToConfigV21}, + {20, migrateToConfigV20}, + {19, nil}, // Triggers a database tweak + {18, migrateToConfigV18}, + {17, nil}, // Fsync = true removed + {16, nil}, // Triggers a database tweak + {15, migrateToConfigV15}, + {14, migrateToConfigV14}, + {13, migrateToConfigV13}, + {12, migrateToConfigV12}, + {11, migrateToConfigV11}, + } + migrationsMut = sync.Mutex{} +) type migrationSet []migration diff --git a/lib/config/migrations_test.go b/lib/config/migrations_test.go index e8e43faedf8..9e4bf11141f 100644 --- a/lib/config/migrations_test.go +++ b/lib/config/migrations_test.go @@ -26,7 +26,9 @@ func TestMigrateCrashReporting(t *testing.T) { for i, tc := range cases { cfg := Configuration{Version: 28, Options: tc.opts} + migrationsMut.Lock() migrations.apply(&cfg) + migrationsMut.Unlock() if cfg.Options.CREnabled != tc.enabled { t.Errorf("%d: unexpected result, CREnabled: %v != %v", i, cfg.Options.CREnabled, tc.enabled) } diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go index 375d774b943..8d0620f256d 100644 --- a/lib/config/wrapper.go +++ b/lib/config/wrapper.go @@ -64,6 +64,7 @@ type Wrapper interface { GUI() GUIConfiguration SetGUI(gui GUIConfiguration) (Waiter, error) LDAP() LDAPConfiguration + SetLDAP(ldap LDAPConfiguration) (Waiter, error) Options() OptionsConfiguration SetOptions(opts OptionsConfiguration) (Waiter, error) @@ -71,11 +72,13 @@ type Wrapper interface { Folder(id string) (FolderConfiguration, bool) Folders() map[string]FolderConfiguration FolderList() []FolderConfiguration + RemoveFolder(id string) (Waiter, error) SetFolder(fld FolderConfiguration) (Waiter, error) SetFolders(folders []FolderConfiguration) (Waiter, error) Device(id protocol.DeviceID) (DeviceConfiguration, bool) Devices() map[protocol.DeviceID]DeviceConfiguration + DeviceList() []DeviceConfiguration RemoveDevice(id protocol.DeviceID) (Waiter, error) SetDevice(DeviceConfiguration) (Waiter, error) SetDevices([]DeviceConfiguration) (Waiter, error) @@ -230,6 +233,13 @@ func (w *wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration { return deviceMap } +// DeviceList returns a slice of devices. +func (w *wrapper) DeviceList() []DeviceConfiguration { + w.mut.Lock() + defer w.mut.Unlock() + return w.cfg.Copy().Devices +} + // SetDevices adds new devices to the configuration, or overwrites existing // devices with the same ID. func (w *wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) { @@ -327,6 +337,22 @@ func (w *wrapper) SetFolders(folders []FolderConfiguration) (Waiter, error) { return w.replaceLocked(newCfg) } +// RemoveFolder removes the folder from the configuration +func (w *wrapper) RemoveFolder(id string) (Waiter, error) { + w.mut.Lock() + defer w.mut.Unlock() + + newCfg := w.cfg.Copy() + for i := range newCfg.Folders { + if newCfg.Folders[i].ID == id { + newCfg.Folders = append(newCfg.Folders[:i], newCfg.Folders[i+1:]...) + return w.replaceLocked(newCfg) + } + } + + return noopWaiter{}, nil +} + // Options returns the current options configuration object. func (w *wrapper) Options() OptionsConfiguration { w.mut.Lock() @@ -349,6 +375,14 @@ func (w *wrapper) LDAP() LDAPConfiguration { return w.cfg.LDAP.Copy() } +func (w *wrapper) SetLDAP(ldap LDAPConfiguration) (Waiter, error) { + w.mut.Lock() + defer w.mut.Unlock() + newCfg := w.cfg.Copy() + newCfg.LDAP = ldap.Copy() + return w.replaceLocked(newCfg) +} + // GUI returns the current GUI configuration object. func (w *wrapper) GUI() GUIConfiguration { w.mut.Lock() diff --git a/lib/protocol/deviceid.go b/lib/protocol/deviceid.go index c76b393fb47..00f805967c0 100644 --- a/lib/protocol/deviceid.go +++ b/lib/protocol/deviceid.go @@ -84,7 +84,7 @@ func (n DeviceID) Short() ShortID { return ShortID(binary.BigEndian.Uint64(n[:])) } -func (n *DeviceID) MarshalText() ([]byte, error) { +func (n DeviceID) MarshalText() ([]byte, error) { return []byte(n.String()), nil } From a20d85d451fcce63d94b6d8c40de65d4db3c7bd9 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Fri, 23 Oct 2020 08:27:02 +0200 Subject: [PATCH 11/42] gui: Refactor to make encryption diff smaller (#7049) --- gui/default/index.html | 6 +- gui/default/syncthing/app.js | 9 + .../syncthing/core/syncthingController.js | 196 ++++++++---------- .../syncthing/core/validDeviceidDirective.js | 5 +- .../syncthing/device/editDeviceModalView.html | 4 +- .../syncthing/folder/editFolderModalView.html | 12 +- 6 files changed, 106 insertions(+), 126 deletions(-) diff --git a/gui/default/index.html b/gui/default/index.html index c7290b6aa18..b7cf19e9eac 100644 --- a/gui/default/index.html +++ b/gui/default/index.html @@ -204,10 +204,10 @@

- + {%device%} wants to share folder "{%folder%}". - + {%device%} wants to share folder "{%folderlabel%}" ({%folder%}). Share this folder? @@ -753,7 +753,7 @@

 Introduced By - {{ deviceName(findDevice(deviceCfg.introducedBy)) || deviceCfg.introducedBy.substring(0, 5) }} + {{ deviceName(devices[deviceCfg.introducedBy]) || deviceCfg.introducedBy.substring(0, 5) }}  Version diff --git a/gui/default/syncthing/app.js b/gui/default/syncthing/app.js index 21aa692566c..b0d607bca0d 100644 --- a/gui/default/syncthing/app.js +++ b/gui/default/syncthing/app.js @@ -74,6 +74,15 @@ function deviceMap(l) { return m; } +function deviceList(m) { + var l = []; + for (var id in m) { + l.push(m[id]); + } + l.sort(deviceCompare); + return l; +} + function folderMap(l) { var m = {}; l.forEach(function (r) { diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index b4643099383..efdfcc0be04 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -27,7 +27,7 @@ angular.module('syncthing.core') $scope.errors = []; $scope.model = {}; $scope.myID = ''; - $scope.devices = []; + $scope.devices = {}; $scope.discoveryCache = {}; $scope.protocolChanged = false; $scope.reportData = {}; @@ -63,9 +63,6 @@ angular.module('syncthing.core') $scope.folderDefaults = { devices: [], - sharedDevices: {}, - selectedDevices: {}, - unrelatedDevices: {}, type: "sendreceive", rescanIntervalS: 3600, fsWatcherDelayS: 10, @@ -378,15 +375,14 @@ angular.module('syncthing.core') $scope.config.options._globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', '); $scope.config.options._urAcceptedStr = "" + $scope.config.options.urAccepted; - $scope.devices = $scope.config.devices; - $scope.devices.forEach(function (deviceCfg) { - $scope.completion[deviceCfg.deviceID] = { + $scope.devices = deviceMap($scope.config.devices); + for (var id in $scope.devices) { + $scope.completion[id] = { _total: 100, _needBytes: 0, _needItems: 0 }; - }); - $scope.devices.sort(deviceCompare); + }; $scope.folders = folderMap($scope.config.folders); Object.keys($scope.folders).forEach(function (folder) { refreshFolder(folder); @@ -689,6 +685,14 @@ angular.module('syncthing.core') }); }; + function initShareEditing(editing) { + $scope.currentSharing = {}; + $scope.currentSharing.editing = editing; + $scope.currentSharing.shared = []; + $scope.currentSharing.unrelated = []; + $scope.currentSharing.selected = {}; + }; + $scope.refreshFailed = function (page, perpage) { if (!$scope.failed || !$scope.failed.folder) { return; @@ -1007,11 +1011,11 @@ angular.module('syncthing.core') } // loop through all devices - var deviceCount = $scope.devices.length; + var deviceCount = 0; var pendingFolders = 0; - for (var i = 0; i < $scope.devices.length; i++) { + for (var id in $scope.devices) { var status = $scope.deviceStatus({ - deviceID: $scope.devices[i].deviceID + deviceID: id }); switch (status) { case 'unknown': @@ -1024,7 +1028,8 @@ angular.module('syncthing.core') deviceCount--; break; } - pendingFolders += $scope.devices[i].pendingFolders.length; + pendingFolders += $scope.devices[id].pendingFolders.length; + deviceCount++; } // enumerate notifications @@ -1061,8 +1066,8 @@ angular.module('syncthing.core') }; $scope.friendlyNameFromShort = function (shortID) { - var matches = $scope.devices.filter(function (n) { - return n.deviceID.substr(0, 7) === shortID; + var matches = Object.keys($scope.devices).filter(function (id) { + return id.substr(0, 7) === shortID; }); if (matches.length !== 1) { return shortID; @@ -1071,23 +1076,13 @@ angular.module('syncthing.core') }; $scope.friendlyNameFromID = function (deviceID) { - var match = $scope.findDevice(deviceID); + var match = $scope.devices[deviceID]; if (match) { return $scope.deviceName(match); } return deviceID.substr(0, 6); }; - $scope.findDevice = function (deviceID) { - var matches = $scope.devices.filter(function (n) { - return n.deviceID === deviceID; - }); - if (matches.length !== 1) { - return undefined; - } - return matches[0]; - }; - $scope.deviceName = function (deviceCfg) { if (typeof deviceCfg === 'undefined' || typeof deviceCfg.deviceID === 'undefined') { return ""; @@ -1110,12 +1105,8 @@ angular.module('syncthing.core') }; $scope.setDevicePause = function (device, pause) { - $scope.devices.forEach(function (cfg) { - if (cfg.deviceID == device) { - cfg.paused = pause; - } - }); - $scope.config.devices = $scope.devices; + $scope.devices[id].paused = pause; + $scope.config.devices = $scope.deviceList(); $scope.saveConfig(); }; @@ -1344,7 +1335,7 @@ angular.module('syncthing.core') // at it before that and conclude that the settings are // modified (even though we just saved) unless we update // here as well... - $scope.devices = $scope.config.devices; + $scope.devices = deviceMap($scope.config.devices); $scope.saveConfig(function () { if (themeChanged) { @@ -1410,45 +1401,45 @@ angular.module('syncthing.core') $scope.editingExisting = true; $scope.willBeReintroducedBy = undefined; if (deviceCfg.introducedBy) { - var introducerDevice = $scope.findDevice(deviceCfg.introducedBy); + var introducerDevice = $scope.devices[deviceCfg.introducedBy]; if (introducerDevice && introducerDevice.introducer) { $scope.willBeReintroducedBy = $scope.deviceName(introducerDevice); } } $scope.currentDevice._addressesStr = deviceCfg.addresses.join(', '); - $scope.currentDevice.selectedFolders = {}; + initShareEditing('device'); + $scope.currentSharing.selected = {}; $scope.deviceFolders($scope.currentDevice).forEach(function (folder) { - $scope.currentDevice.selectedFolders[folder] = true; + $scope.currentSharing.selected[folder] = true; }); $scope.deviceEditor.$setPristine(); $('#editDevice').modal(); }; - $scope.selectAllFolders = function () { - angular.forEach($scope.folders, function (_, id) { - $scope.currentDevice.selectedFolders[id] = true; - }); + $scope.selectAllSharedFolders = function (state) { + var devices = $scope.currentSharing.shared; + for (var i = 0; i < devices.length; i++) { + $scope.currentSharing.selected[devices[i].deviceID] = !!state; + } }; - $scope.deSelectAllFolders = function () { - angular.forEach($scope.folders, function (_, id) { - $scope.currentDevice.selectedFolders[id] = false; - }); + $scope.selectAllUnrelatedFolders = function (state) { + var devices = $scope.currentSharing.unrelated; + for (var i = 0; i < devices.length; i++) { + $scope.currentSharing.selected[devices[i].deviceID] = !!state; + } }; $scope.addDevice = function (deviceID, name) { return $http.get(urlbase + '/system/discovery') .success(function (registry) { $scope.discovery = []; - outer: for (var id in registry) { if ($scope.discovery.length === 5) { break; } - for (var i = 0; i < $scope.devices.length; i++) { - if ($scope.devices[i].deviceID === id) { - continue outer; - } + if (id in $scope.devices) { + continue } $scope.discovery.push(id); } @@ -1460,7 +1451,6 @@ angular.module('syncthing.core') _addressesStr: 'dynamic', compression: 'metadata', introducer: false, - selectedFolders: {}, pendingFolders: [], ignoredFolders: [] }; @@ -1476,10 +1466,8 @@ angular.module('syncthing.core') return; } - $scope.devices = $scope.devices.filter(function (n) { - return n.deviceID !== $scope.currentDevice.deviceID; - }); - $scope.config.devices = $scope.devices; + delete $scope.devices[id]; + $scope.config.devices = $scope.deviceList(); for (var id in $scope.folders) { $scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) { @@ -1500,23 +1488,11 @@ angular.module('syncthing.core') return x.trim(); }); - var done = false; - for (var i = 0; i < $scope.devices.length && !done; i++) { - if ($scope.devices[i].deviceID === deviceCfg.deviceID) { - $scope.devices[i] = deviceCfg; - done = true; - } - } + $scope.devices[deviceCfg.deviceID] = deviceCfg; + $scope.config.devices = deviceList($scope.devices); - if (!done) { - $scope.devices.push(deviceCfg); - } - - $scope.devices.sort(deviceCompare); - $scope.config.devices = $scope.devices; - - for (var id in deviceCfg.selectedFolders) { - if (deviceCfg.selectedFolders[id]) { + for (var id in $scope.currentSharing.selected) { + if ($scope.currentSharing.selected[id]) { var found = false; for (i = 0; i < $scope.folders[id].devices.length; i++) { if ($scope.folders[id].devices[i].deviceID === deviceCfg.deviceID) { @@ -1574,13 +1550,13 @@ angular.module('syncthing.core') }; $scope.otherDevices = function () { - return $scope.devices.filter(function (n) { + return $scope.deviceList().filter(function (n) { return n.deviceID !== $scope.myID; }); }; $scope.thisDevice = function () { - return $scope.thisDeviceIn($scope.devices); + return $scope.devices[$scope.myID]; }; $scope.thisDeviceIn = function (l) { @@ -1599,16 +1575,16 @@ angular.module('syncthing.core') }; $scope.setAllDevicesPause = function (pause) { - $scope.devices.forEach(function (cfg) { - cfg.paused = pause; - }); - $scope.config.devices = $scope.devices; + for (var id in $scope.devices) { + $scope.devices[id].paused = pause; + }; + $scope.config.devices = deviceList($scope.devices); $scope.saveConfig(); } $scope.isAtleastOneDevicePausedStateSetTo = function (pause) { - for (var i = 0; i < $scope.devices.length; i++) { - if ($scope.devices[i].paused == pause) { + for (var id in $scope.devices) { + if ($scope.devices[id].paused == pause) { return true; } } @@ -1641,9 +1617,8 @@ angular.module('syncthing.core') }; $scope.friendlyDevices = function (str) { - for (var i = 0; i < $scope.devices.length; i++) { - var cfg = $scope.devices[i]; - str = str.replace(cfg.deviceID, $scope.deviceName(cfg)); + for (var id in $scope.devices) { + str = str.replace(id, $scope.deviceName($scope.devices[id])); } return str; }; @@ -1652,6 +1627,10 @@ angular.module('syncthing.core') return folderList($scope.folders); }; + $scope.deviceList = function () { + return deviceList($scope.devices); + }; + $scope.directoryList = []; $scope.$watch('currentFolder.path', function (newvalue) { @@ -1724,18 +1703,15 @@ angular.module('syncthing.core') $scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1); } // Cache complete device objects indexed by ID for lookups - var devMap = deviceMap($scope.devices) - $scope.currentFolder.sharedDevices = []; - $scope.currentFolder.selectedDevices = {}; + initShareEditing('folder'); $scope.currentFolder.devices.forEach(function (n) { if (n.deviceID !== $scope.myID) { - $scope.currentFolder.sharedDevices.push(devMap[n.deviceID]); + $scope.currentSharing.shared.push($scope.devices[n.deviceID]); } - $scope.currentFolder.selectedDevices[n.deviceID] = true; + $scope.currentSharing.selected[n.deviceID] = true; }); - $scope.currentFolder.unrelatedDevices = $scope.devices.filter(function (n) { - return n.deviceID !== $scope.myID - && !$scope.currentFolder.selectedDevices[n.deviceID] + $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) { + return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID] }); if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") { $scope.currentFolder.trashcanFileVersioning = true; @@ -1795,16 +1771,16 @@ angular.module('syncthing.core') }; $scope.selectAllSharedDevices = function (state) { - var devices = $scope.currentFolder.sharedDevices; + var devices = $scope.currentSharing.shared; for (var i = 0; i < devices.length; i++) { - $scope.currentFolder.selectedDevices[devices[i].deviceID] = !!state; + $scope.currentSharing.selected[devices[i].deviceID] = !!state; } }; $scope.selectAllUnrelatedDevices = function (state) { - var devices = $scope.currentFolder.unrelatedDevices; + var devices = $scope.currentSharing.unrelated; for (var i = 0; i < devices.length; i++) { - $scope.currentFolder.selectedDevices[devices[i].deviceID] = !!state; + $scope.currentSharing.selected[devices[i].deviceID] = !!state; } }; @@ -1812,8 +1788,9 @@ angular.module('syncthing.core') $http.get(urlbase + '/svc/random/string?length=10').success(function (data) { $scope.editingExisting = false; $scope.currentFolder = angular.copy($scope.folderDefaults); + initShareEditing('folder'); $scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase(); - $scope.currentFolder.unrelatedDevices = $scope.otherDevices(); + $scope.currentSharing.unrelated = $scope.otherDevices(); $scope.ignores.text = ''; $scope.ignores.error = null; $scope.ignores.disabled = false; @@ -1829,8 +1806,11 @@ angular.module('syncthing.core') $scope.currentFolder.viewFlags = { importFromOtherDevice: true }; - $scope.currentFolder.selectedDevices[device] = true; - $scope.currentFolder.unrelatedDevices = $scope.otherDevices(); + initShareEditing('folder'); + $scope.currentSharing.selected[device] = true; + $scope.currentSharing.unrelated = $scope.deviceList().filter(function (n) { + return n.deviceID !== $scope.myID && !$scope.currentSharing.selected[n.deviceID] + }); $scope.ignores.text = ''; $scope.ignores.error = null; $scope.ignores.disabled = false; @@ -1848,25 +1828,23 @@ angular.module('syncthing.core') $scope.saveFolder = function () { $('#editFolder').modal('hide'); var folderCfg = angular.copy($scope.currentFolder); - folderCfg.selectedDevices[$scope.myID] = true; + $scope.currentSharing.selected[$scope.myID] = true; var newDevices = []; folderCfg.devices.forEach(function (dev) { - if (folderCfg.selectedDevices[dev.deviceID] === true) { + if ($scope.currentSharing.selected[dev.deviceID] === true) { newDevices.push(dev); - delete folderCfg.selectedDevices[dev.deviceID]; + delete $scope.currentSharing.selected[dev.deviceID]; }; }); - for (var deviceID in folderCfg.selectedDevices) { - if (folderCfg.selectedDevices[deviceID] === true) { + for (var deviceID in $scope.currentSharing.selected) { + if ($scope.currentSharing.selected[deviceID] === true) { newDevices.push({ deviceID: deviceID }); } } folderCfg.devices = newDevices; - delete folderCfg.sharedDevices; - delete folderCfg.selectedDevices; - delete folderCfg.unrelatedDevices; + delete $scope.currentSharing; if (folderCfg.fileVersioningSelector === "trashcan") { folderCfg.versioning = { @@ -1948,12 +1926,9 @@ angular.module('syncthing.core') // Bump time pendingFolder.time = (new Date()).toISOString(); - for (var i = 0; i < $scope.devices.length; i++) { - if ($scope.devices[i].deviceID == device) { - $scope.devices[i].ignoredFolders.push(pendingFolder); + if (id in $scope.devices) { + $scope.devices[id].ignoredFolders.push(pendingFolder); $scope.saveConfig(); - return; - } } }; @@ -1961,7 +1936,7 @@ angular.module('syncthing.core') var names = []; folderCfg.devices.forEach(function (device) { if (device.deviceID !== $scope.myID) { - names.push($scope.deviceName($scope.findDevice(device.deviceID))); + names.push($scope.deviceName($scope.devices[device.deviceID])); } }); names.sort(); @@ -2481,7 +2456,6 @@ angular.module('syncthing.core') $scope.modalLoaded = function () { // once all modal elements have been processed if ($('modal').length === 0) { - // pseudo main. called on all definitions assigned initController(); } diff --git a/gui/default/syncthing/core/validDeviceidDirective.js b/gui/default/syncthing/core/validDeviceidDirective.js index 643d7ade8f9..6002846f054 100644 --- a/gui/default/syncthing/core/validDeviceidDirective.js +++ b/gui/default/syncthing/core/validDeviceidDirective.js @@ -16,10 +16,7 @@ angular.module('syncthing.core') } }); //Prevents user from adding a duplicate ID - var matches = scope.devices.filter(function (n) { - return n.deviceID == viewValue; - }).length; - if (matches > 0) { + if ($scope.devices.hasOwnProperty(viewValue)) { ctrl.$setValidity('unique', false); } else { ctrl.$setValidity('unique', true); diff --git a/gui/default/syncthing/device/editDeviceModalView.html b/gui/default/syncthing/device/editDeviceModalView.html index 9d856b2b6e3..49fd7d613d7 100644 --- a/gui/default/syncthing/device/editDeviceModalView.html +++ b/gui/default/syncthing/device/editDeviceModalView.html @@ -76,10 +76,10 @@
diff --git a/gui/default/syncthing/folder/editFolderModalView.html b/gui/default/syncthing/folder/editFolderModalView.html index 593937abd83..35af1cb44bb 100644 --- a/gui/default/syncthing/folder/editFolderModalView.html +++ b/gui/default/syncthing/folder/editFolderModalView.html @@ -46,7 +46,7 @@

-
+

Deselect devices to stop sharing this folder with.  @@ -54,16 +54,16 @@ Deselect All

-
+
-
+

Select additional devices to share this folder with.  @@ -74,10 +74,10 @@ There are no devices to share this folder with.

-
+
From 9189c79d744e0b9fa8d399cace5948ab9cbe55af Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Fri, 23 Oct 2020 10:34:20 +0200 Subject: [PATCH 12/42] lib/api: Add missing config mod. locks (ref #7001) (#7053) --- lib/api/confighandler.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/api/confighandler.go b/lib/api/confighandler.go index 0bf3b120564..dc580adbe9a 100644 --- a/lib/api/confighandler.go +++ b/lib/api/confighandler.go @@ -153,6 +153,8 @@ func (c *configMuxBuilder) registerFolder(path string) { }) c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) { + c.mut.Lock() + defer c.mut.Unlock() waiter, err := c.cfg.RemoveFolder(p.ByName("id")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -194,6 +196,8 @@ func (c *configMuxBuilder) registerDevice(path string) { }) c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) { + c.mut.Lock() + defer c.mut.Unlock() id, err := protocol.DeviceIDFromString(p.ByName("id")) waiter, err := c.cfg.RemoveDevice(id) if err != nil { From 0d90ae26ac72fed2862bfaa59163b2cdfa5b20ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Mon, 26 Oct 2020 09:09:32 +0100 Subject: [PATCH 13/42] gui: Fix undefined variables fallout from #7049 (#7056) --- gui/default/syncthing/core/syncthingController.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index efdfcc0be04..fd5aa8b5c68 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -1105,7 +1105,7 @@ angular.module('syncthing.core') }; $scope.setDevicePause = function (device, pause) { - $scope.devices[id].paused = pause; + $scope.devices[device].paused = pause; $scope.config.devices = $scope.deviceList(); $scope.saveConfig(); }; @@ -1466,6 +1466,7 @@ angular.module('syncthing.core') return; } + var id = $scope.currentDevice.deviceID delete $scope.devices[id]; $scope.config.devices = $scope.deviceList(); From c7d40ccbaee867b579ccb291b258fd28299b3464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Mon, 26 Oct 2020 11:27:03 +0100 Subject: [PATCH 14/42] gui: Remove needless looping in ignoreFolder() (#7059) --- gui/default/syncthing/core/syncthingController.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index fd5aa8b5c68..762cef7dcdc 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -1927,10 +1927,8 @@ angular.module('syncthing.core') // Bump time pendingFolder.time = (new Date()).toISOString(); - if (id in $scope.devices) { - $scope.devices[id].ignoredFolders.push(pendingFolder); - $scope.saveConfig(); - } + $scope.devices[id].ignoredFolders.push(pendingFolder); + $scope.saveConfig(); }; $scope.sharesFolder = function (folderCfg) { From bc012d750dce9137b00334115c5494a5afa37bf4 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Tue, 27 Oct 2020 16:40:16 +0100 Subject: [PATCH 15/42] gui: Readd check if device exists (ref #7059) (#7061) This reverts commit c7d40ccbaee867b579ccb291b258fd28299b3464. --- gui/default/syncthing/core/syncthingController.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 762cef7dcdc..1947de17539 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -1927,8 +1927,10 @@ angular.module('syncthing.core') // Bump time pendingFolder.time = (new Date()).toISOString(); - $scope.devices[id].ignoredFolders.push(pendingFolder); - $scope.saveConfig(); + if (id in $scope.devices) { + $scope.devices[id].ignoredFolders.push(pendingFolder); + $scope.saveConfig(); + } }; $scope.sharesFolder = function (folderCfg) { From deafe4ca53f4962acb98f2ea4369a371a6d32dd9 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 28 Oct 2020 07:45:27 +0100 Subject: [PATCH 16/42] gui, man, authors: Update docs, translations, and contributors --- gui/default/assets/lang/lang-da.json | 16 +- gui/default/assets/lang/lang-en-AU.json | 385 ++++++++++++++++++++++++ gui/default/assets/lang/lang-pl.json | 2 +- gui/default/assets/lang/lang-zh-CN.json | 6 +- gui/default/assets/lang/prettyprint.js | 2 +- gui/default/assets/lang/valid-langs.js | 2 +- man/stdiscosrv.1 | 2 +- man/strelaysrv.1 | 2 +- man/syncthing-bep.7 | 2 +- man/syncthing-config.5 | 7 +- man/syncthing-device-ids.7 | 2 +- man/syncthing-event-api.7 | 2 +- man/syncthing-faq.7 | 2 +- man/syncthing-globaldisco.7 | 2 +- man/syncthing-localdisco.7 | 2 +- man/syncthing-networking.7 | 2 +- man/syncthing-relay.7 | 2 +- man/syncthing-rest-api.7 | 53 +++- man/syncthing-security.7 | 2 +- man/syncthing-stignore.5 | 2 +- man/syncthing-versioning.7 | 2 +- man/syncthing.1 | 26 +- 22 files changed, 480 insertions(+), 45 deletions(-) create mode 100644 gui/default/assets/lang/lang-en-AU.json diff --git a/gui/default/assets/lang/lang-da.json b/gui/default/assets/lang/lang-da.json index 0a2c39aea84..332c3711a4a 100644 --- a/gui/default/assets/lang/lang-da.json +++ b/gui/default/assets/lang/lang-da.json @@ -27,12 +27,12 @@ "Are you sure you want to remove device {%name%}?": "Er du sikker på, at du vil fjerne enheden {{name}}?", "Are you sure you want to remove folder {%label%}?": "Er du sikker på, at du vil fjerne mappen {{label}}?", "Are you sure you want to restore {%count%} files?": "Er du sikker på, at du vil genskabe {{count}} filer?", - "Are you sure you want to upgrade?": "Are you sure you want to upgrade?", + "Are you sure you want to upgrade?": "Opgradere?", "Auto Accept": "Autoacceptér", "Automatic Crash Reporting": "Automatisk nedbrud rapportering", "Automatic upgrade now offers the choice between stable releases and release candidates.": "Den automatiske opdatering tilbyder nu valget mellem stabile udgivelser og udgivelseskandidater.", "Automatic upgrades": "Automatisk opdatering", - "Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.", + "Automatic upgrades are always enabled for candidate releases.": "Automatisk opgradering er altid aktiveret for kandidat udgivelser", "Automatically create or share folders that this device advertises at the default path.": "Opret eller del automatisk mapper på standardstien, som denne enhed tilbyder.", "Available debug logging facilities:": "Tilgængelige faciliteter for fejlretningslogning:", "Be careful!": "Vær forsigtig!", @@ -62,7 +62,7 @@ "Default Folder Path": "Standardmappesti", "Deleted": "Slettet", "Deselect All": "Fravælg alle", - "Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.", + "Deselect devices to stop sharing this folder with.": "Fravælg enheder for at stoppe mappe deling.", "Device": "Enhed", "Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enheden “{{name}}” ({{device}} på {{address}}) vil gerne forbinde. Tilføj denne enhed?", "Device ID": "Enheds-ID", @@ -168,7 +168,7 @@ "Local State (Total)": "Lokal tilstand (total)", "Locally Changed Items": "Lokalt ændrede filer", "Log": "Logbog", - "Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.", + "Log tailing paused. Scroll to the bottom to continue.": "Log sammenkædning er i pause. Rul til bunden for at fortsætte.", "Logs": "Logbog", "Major Upgrade": "Opgradering til ny hovedversion", "Mass actions": "Massehandlinger", @@ -287,7 +287,7 @@ "Syncing": "Synkroniserer", "Syncthing has been shut down.": "Syncthing er lukket ned.", "Syncthing includes the following software or portions thereof:": "Syncthing indeholder følgende software eller dele heraf:", - "Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing is Free and Open Source Software licensed as MPL v2.0.", + "Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing er fri og åben kildekode software licenseret som MPL v2.0.", "Syncthing is restarting.": "Syncthing genstarter.", "Syncthing is upgrading.": "Syncthing opgraderer.", "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.", @@ -298,7 +298,7 @@ "The Syncthing Authors": "Syncthing udviklere", "The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing-administationsfladen er sat op til at kunne fjernstyres uden adgangskode.", "The aggregated statistics are publicly available at the URL below.": "Den indsamlede statistik er offentligt tilgængelig på den nedenstående URL.", - "The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.", + "The cleanup interval cannot be blank.": "Ryd op interval kan ikke være tom.", "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen er gemt, men ikke aktiveret. Syncthing skal genstarte for at aktivere den nye konfiguration.", "The device ID cannot be blank.": "Enhedens ID må ikke være tom.", "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).": "Det enheds-ID, som skal indtastes her, kan findes under menuen “Handlinger > Vis ID” på den anden enhed. Mellemrum og bindestreger er valgfri (ignoreres).", @@ -310,7 +310,7 @@ "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ølgende intervaller er brugt: Inden for den første time bliver en version gemt hvert 30. sekund, inden for den første dag bliver en version gemt hver time, inden for de første 30 dage bliver en version gemt hver dag, og indtil den maksimale alder bliver en version gemt hver uge.", "The following items could not be synchronized.": "Følgende filer kunne ikke synkroniseres.", "The following items were changed locally.": "De følgende filer er ændret lokalt.", - "The interval must be a positive number of seconds.": "The interval must be a positive number of seconds.", + "The interval must be a positive number of seconds.": "Intervallet skal være et positivt antal sekunder.", "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.", "The maximum age must be a number and cannot be blank.": "Maksimal alder skal være et tal og feltet må ikke være tomt.", "The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den maksimale tid, en version skal gemmes (i dage; sæt lig med 0 for at beholde gamle versioner for altid).", @@ -321,7 +321,7 @@ "The path cannot be blank.": "Stien må ikke være tom.", "The rate limit must be a non-negative number (0: no limit)": "Hastighedsbegrænsningen skal være et ikke-negativt tal (0: ingen begrænsning)", "The rescan interval must be a non-negative number of seconds.": "Genskanningsintervallet skal være et ikke-negativt antal sekunder.", - "There are no devices to share this folder with.": "There are no devices to share this folder with.", + "There are no devices to share this folder with.": "Der er ingen enheder at dele denne mappe med.", "They are retried automatically and will be synced when the error is resolved.": "De prøves igen automatisk og vil blive synkroniseret, når fejlen er løst.", "This Device": "Denne enhed", "This can easily give hackers access to read and change any files on your computer.": "Dette gør det nemt for hackere at få adgang til at læse og ændre filer på din computer.", diff --git a/gui/default/assets/lang/lang-en-AU.json b/gui/default/assets/lang/lang-en-AU.json new file mode 100644 index 00000000000..f5b5db034ef --- /dev/null +++ b/gui/default/assets/lang/lang-en-AU.json @@ -0,0 +1,385 @@ +{ + "A device with that ID is already added.": "A device with that ID is already added.", + "A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.", + "A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.", + "API Key": "API Key", + "About": "About", + "Action": "Action", + "Actions": "Actions", + "Add": "Add", + "Add Device": "Add Device", + "Add Folder": "Add Folder", + "Add Remote Device": "Add Remote Device", + "Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.", + "Add new folder?": "Add new folder?", + "Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.", + "Address": "Address", + "Addresses": "Addresses", + "Advanced": "Advanced", + "Advanced Configuration": "Advanced Configuration", + "All Data": "All Data", + "Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?", + "Allowed Networks": "Allowed Networks", + "Alphabetic": "Alphabetic", + "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.", + "Anonymous Usage Reporting": "Anonymous Usage Reporting", + "Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?", + "Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?", + "Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?", + "Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?", + "Are you sure you want to upgrade?": "Are you sure you want to upgrade?", + "Auto Accept": "Auto Accept", + "Automatic Crash Reporting": "Automatic Crash Reporting", + "Automatic upgrade now offers the choice between stable releases and release candidates.": "Automatic upgrade now offers the choice between stable releases and release candidates.", + "Automatic upgrades": "Automatic upgrades", + "Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.", + "Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.", + "Available debug logging facilities:": "Available debug logging facilities:", + "Be careful!": "Be careful!", + "Bugs": "Bugs", + "Changelog": "Changelog", + "Clean out after": "Clean out after", + "Cleaning Versions": "Cleaning Versions", + "Cleanup Interval": "Cleanup Interval", + "Click to see discovery failures": "Click to see discovery failures", + "Close": "Close", + "Command": "Command", + "Comment, when used at the start of a line": "Comment, when used at the start of a line", + "Compression": "Compression", + "Configured": "Configured", + "Connected (Unused)": "Connected (Unused)", + "Connection Error": "Connection Error", + "Connection Type": "Connection Type", + "Connections": "Connections", + "Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.", + "Copied from elsewhere": "Copied from elsewhere", + "Copied from original": "Copied from original", + "Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:", + "Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.", + "Currently Shared With Devices": "Currently Shared With Devices", + "Danger!": "Danger!", + "Debugging Facilities": "Debugging Facilities", + "Default Folder Path": "Default Folder Path", + "Deleted": "Deleted", + "Deselect All": "Deselect All", + "Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.", + "Device": "Device", + "Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?", + "Device ID": "Device ID", + "Device Identification": "Device Identification", + "Device Name": "Device Name", + "Device rate limits": "Device rate limits", + "Device that last modified the item": "Device that last modified the item", + "Devices": "Devices", + "Disable Crash Reporting": "Disable Crash Reporting", + "Disabled": "Disabled", + "Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes", + "Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes", + "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:", + "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).", + "Discard": "Discard", + "Disconnected": "Disconnected", + "Disconnected (Unused)": "Disconnected (Unused)", + "Discovered": "Discovered", + "Discovery": "Discovery", + "Discovery Failures": "Discovery Failures", + "Do not restore": "Do not restore", + "Do not restore all": "Do not restore all", + "Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?", + "Documentation": "Documentation", + "Download Rate": "Download Rate", + "Downloaded": "Downloaded", + "Downloading": "Downloading", + "Edit": "Edit", + "Edit Device": "Edit Device", + "Edit Folder": "Edit Folder", + "Editing {%path%}.": "Editing {{path}}.", + "Enable Crash Reporting": "Enable Crash Reporting", + "Enable NAT traversal": "Enable NAT traversal", + "Enable Relaying": "Enable Relaying", + "Enabled": "Enabled", + "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.", + "Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).", + "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.", + "Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.", + "Enter up to three octal digits.": "Enter up to three octal digits.", + "Error": "Error", + "External File Versioning": "External File Versioning", + "Failed Items": "Failed Items", + "Failed to setup, retrying": "Failed to setup, retrying", + "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.", + "File Pull Order": "File Pull Order", + "File Versioning": "File Versioning", + "Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Files are moved to .stversions directory when replaced or deleted by Syncthing.", + "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.", + "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.", + "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronised from the cluster, but any changes made locally will not be sent to other devices.", + "Filesystem Watcher Errors": "Filesystem Watcher Errors", + "Filter by date": "Filter by date", + "Filter by name": "Filter by name", + "Folder": "Folder", + "Folder ID": "Folder ID", + "Folder Label": "Folder Label", + "Folder Path": "Folder Path", + "Folder Type": "Folder Type", + "Folders": "Folders", + "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.", + "Full Rescan Interval (s)": "Full Rescan Interval (s)", + "GUI": "GUI", + "GUI Authentication Password": "GUI Authentication Password", + "GUI Authentication User": "GUI Authentication User", + "GUI Authentication: Set User and Password": "GUI Authentication: Set User and Password", + "GUI Listen Address": "GUI Listen Address", + "GUI Theme": "GUI Theme", + "General": "General", + "Generate": "Generate", + "Global Discovery": "Global Discovery", + "Global Discovery Servers": "Global Discovery Servers", + "Global State": "Global State", + "Help": "Help", + "Home page": "Home page", + "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.", + "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.", + "Ignore": "Ignore", + "Ignore Patterns": "Ignore Patterns", + "Ignore Permissions": "Ignore Permissions", + "Ignored Devices": "Ignored Devices", + "Ignored Folders": "Ignored Folders", + "Ignored at": "Ignored at", + "Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)", + "Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.", + "Introduced By": "Introduced By", + "Introducer": "Introducer", + "Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)", + "Keep Versions": "Keep Versions", + "LDAP": "LDAP", + "Largest First": "Largest First", + "Last Scan": "Last Scan", + "Last seen": "Last seen", + "Latest Change": "Latest Change", + "Learn more": "Learn more", + "Limit": "Limit", + "Listeners": "Listeners", + "Loading data...": "Loading data...", + "Loading...": "Loading...", + "Local Additions": "Local Additions", + "Local Discovery": "Local Discovery", + "Local State": "Local State", + "Local State (Total)": "Local State (Total)", + "Locally Changed Items": "Locally Changed Items", + "Log": "Log", + "Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.", + "Logs": "Logs", + "Major Upgrade": "Major Upgrade", + "Mass actions": "Mass actions", + "Maximum Age": "Maximum Age", + "Metadata Only": "Metadata Only", + "Minimum Free Disk Space": "Minimum Free Disk Space", + "Mod. Device": "Mod. Device", + "Mod. Time": "Mod. Time", + "Move to top of queue": "Move to top of queue", + "Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)", + "Never": "Never", + "New Device": "New Device", + "New Folder": "New Folder", + "Newest First": "Newest First", + "No": "No", + "No File Versioning": "No File Versioning", + "No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.", + "No upgrades": "No upgrades", + "Notice": "Notice", + "OK": "OK", + "Off": "Off", + "Oldest First": "Oldest First", + "Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.", + "Options": "Options", + "Out of Sync": "Out of Sync", + "Out of Sync Items": "Out of Sync Items", + "Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)", + "Override Changes": "Override Changes", + "Path": "Path", + "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for", + "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.", + "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).", + "Pause": "Pause", + "Pause All": "Pause All", + "Paused": "Paused", + "Paused (Unused)": "Paused (Unused)", + "Pending changes": "Pending changes", + "Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes", + "Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes", + "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:", + "Permissions": "Permissions", + "Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.", + "Please set a GUI Authentication User and Password in the Settings dialog.": "Please set a GUI Authentication User and Password in the Settings dialog.", + "Please wait": "Please wait", + "Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal", + "Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity", + "Preparing to Sync": "Preparing to Sync", + "Preview": "Preview", + "Preview Usage Report": "Preview Usage Report", + "Quick guide to supported patterns": "Quick guide to supported patterns", + "Random": "Random", + "Receive Only": "Receive Only", + "Recent Changes": "Recent Changes", + "Reduced by ignore patterns": "Reduced by ignore patterns", + "Release Notes": "Release Notes", + "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.", + "Remote Devices": "Remote Devices", + "Remove": "Remove", + "Remove Device": "Remove Device", + "Remove Folder": "Remove Folder", + "Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.", + "Rescan": "Rescan", + "Rescan All": "Rescan All", + "Rescans": "Rescans", + "Restart": "Restart", + "Restart Needed": "Restart Needed", + "Restarting": "Restarting", + "Restore": "Restore", + "Restore Versions": "Restore Versions", + "Resume": "Resume", + "Resume All": "Resume All", + "Reused": "Reused", + "Revert Local Changes": "Revert Local Changes", + "Save": "Save", + "Scan Time Remaining": "Scan Time Remaining", + "Scanning": "Scanning", + "See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.", + "Select All": "Select All", + "Select a version": "Select a version", + "Select additional devices to share this folder with.": "Select additional devices to share this folder with.", + "Select latest version": "Select latest version", + "Select oldest version": "Select oldest version", + "Select the folders to share with this device.": "Select the folders to share with this device.", + "Send & Receive": "Send & Receive", + "Send Only": "Send Only", + "Settings": "Settings", + "Share": "Share", + "Share Folder": "Share Folder", + "Share Folders With Device": "Share Folders With Device", + "Share this folder?": "Share this folder?", + "Shared With": "Shared With", + "Sharing": "Sharing", + "Show ID": "Show ID", + "Show QR": "Show QR", + "Show diff with previous version": "Show diff with previous version", + "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.", + "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.", + "Shutdown": "Shutdown", + "Shutdown Complete": "Shutdown Complete", + "Simple File Versioning": "Simple File Versioning", + "Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)", + "Size": "Size", + "Smallest First": "Smallest First", + "Some items could not be restored:": "Some items could not be restored:", + "Source Code": "Source Code", + "Stable releases and release candidates": "Stable releases and release candidates", + "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.", + "Stable releases only": "Stable releases only", + "Staggered File Versioning": "Staggered File Versioning", + "Start Browser": "Start Browser", + "Statistics": "Statistics", + "Stopped": "Stopped", + "Support": "Support", + "Support Bundle": "Support Bundle", + "Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses", + "Syncing": "Syncing", + "Syncthing has been shut down.": "Syncthing has been shut down.", + "Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:", + "Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing is Free and Open Source Software licensed as MPL v2.0.", + "Syncthing is restarting.": "Syncthing is restarting.", + "Syncthing is upgrading.": "Syncthing is upgrading.", + "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.", + "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…", + "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.", + "Take me back": "Take me back", + "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.", + "The Syncthing Authors": "The Syncthing Authors", + "The Syncthing admin interface is configured to allow remote access without a password.": "The Syncthing admin interface is configured to allow remote access without a password.", + "The aggregated statistics are publicly available at the URL below.": "The aggregated statistics are publicly available at the URL below.", + "The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.", + "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.", + "The device ID cannot be blank.": "The device ID cannot be blank.", + "The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).", + "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.", + "The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.", + "The folder ID cannot be blank.": "The folder ID cannot be blank.", + "The folder ID must be unique.": "The folder ID must be unique.", + "The folder path cannot be blank.": "The folder path cannot be blank.", + "The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.", + "The following items could not be synchronized.": "The following items could not be synchronised.", + "The following items were changed locally.": "The following items were changed locally.", + "The interval must be a positive number of seconds.": "The interval must be a positive number of seconds.", + "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.", + "The maximum age must be a number and cannot be blank.": "The maximum age must be a number and cannot be blank.", + "The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).", + "The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.", + "The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the bin. Zero means forever.", + "The number of old versions to keep, per file.": "The number of old versions to keep, per file.", + "The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.", + "The path cannot be blank.": "The path cannot be blank.", + "The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)", + "The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.", + "There are no devices to share this folder with.": "There are no devices to share this folder with.", + "They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.", + "This Device": "This Device", + "This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.", + "This is a major version upgrade.": "This is a major version upgrade.", + "This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.", + "Time": "Time", + "Time the item was last modified": "Time the item was last modified", + "Trash Can File Versioning": "Bin File Versioning", + "Type": "Type", + "UNIX Permissions": "UNIX Permissions", + "Unavailable": "Unavailable", + "Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer", + "Undecided (will prompt)": "Undecided (will prompt)", + "Unignore": "Unignore", + "Unknown": "Unknown", + "Unshared": "Unshared", + "Unshared Devices": "Unshared Devices", + "Up to Date": "Up to Date", + "Updated": "Updated", + "Upgrade": "Upgrade", + "Upgrade To {%version%}": "Upgrade To {{version}}", + "Upgrading": "Upgrading", + "Upload Rate": "Upload Rate", + "Uptime": "Uptime", + "Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.", + "Use HTTPS for GUI": "Use HTTPS for GUI", + "Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.", + "Version": "Version", + "Versions": "Versions", + "Versions Path": "Versions Path", + "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.", + "Waiting to Clean": "Waiting to Clean", + "Waiting to Scan": "Waiting to Scan", + "Waiting to Sync": "Waiting to Sync", + "Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a parent directory of an existing folder \"{{otherFolder}}\".", + "Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Warning, this path is a parent directory of an existing folder \"{{otherFolderLabel}}\" ({{otherFolder}}).", + "Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a subdirectory of an existing folder \"{{otherFolder}}\".", + "Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Warning, this path is a subdirectory of an existing folder \"{{otherFolderLabel}}\" ({{otherFolder}}).", + "Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.", + "Watch for Changes": "Watch for Changes", + "Watching for Changes": "Watching for Changes", + "Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.", + "When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.", + "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.", + "Yes": "Yes", + "You can also select one of these nearby devices:": "You can also select one of these nearby devices:", + "You can change your choice at any time in the Settings dialog.": "You can change your choice at any time in the Settings dialog.", + "You can read more about the two release channels at the link below.": "You can read more about the two release channels at the link below.", + "You have no ignored devices.": "You have no ignored devices.", + "You have no ignored folders.": "You have no ignored folders.", + "You have unsaved changes. Do you really want to discard them?": "You have unsaved changes. Do you really want to discard them?", + "You must keep at least one version.": "You must keep at least one version.", + "days": "days", + "directories": "directories", + "files": "files", + "full documentation": "full documentation", + "items": "items", + "seconds": "seconds", + "{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".", + "{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})." +} \ No newline at end of file diff --git a/gui/default/assets/lang/lang-pl.json b/gui/default/assets/lang/lang-pl.json index 375b95a0a85..8f3af9c2ac4 100644 --- a/gui/default/assets/lang/lang-pl.json +++ b/gui/default/assets/lang/lang-pl.json @@ -241,7 +241,7 @@ "Resume": "Wznów", "Resume All": "Wznów wszystkie", "Reused": "Ponownie użyte", - "Revert Local Changes": "Przywróć zmiany lokalne", + "Revert Local Changes": "Odrzuć zmiany lokalne", "Save": "Zapisz", "Scan Time Remaining": "Pozostały czas skanowania", "Scanning": "Skanowanie", diff --git a/gui/default/assets/lang/lang-zh-CN.json b/gui/default/assets/lang/lang-zh-CN.json index 9fb90fa99d6..26994937063 100644 --- a/gui/default/assets/lang/lang-zh-CN.json +++ b/gui/default/assets/lang/lang-zh-CN.json @@ -91,8 +91,8 @@ "Downloaded": "已下载", "Downloading": "下载中", "Edit": "选项", - "Edit Device": "Edit Device", - "Edit Folder": "Edit Folder", + "Edit Device": "编辑设备", + "Edit Folder": "编辑文件夹", "Editing {%path%}.": "正在编辑 {{path}}。", "Enable Crash Reporting": "启用自动发送崩溃报告", "Enable NAT traversal": "启用 NAT 遍历", @@ -375,7 +375,7 @@ "You have unsaved changes. Do you really want to discard them?": "你有未保存的更改。你真的要丢弃它们吗?", "You must keep at least one version.": "您必须保留至少一个版本。", "days": "天", - "directories": "directories", + "directories": "目录", "files": "文件", "full documentation": "完整文档", "items": "条目", diff --git a/gui/default/assets/lang/prettyprint.js b/gui/default/assets/lang/prettyprint.js index cd5652b7a60..b488c000ff2 100644 --- a/gui/default/assets/lang/prettyprint.js +++ b/gui/default/assets/lang/prettyprint.js @@ -1 +1 @@ -var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"} +var langPrettyprint = {"bg":"Bulgarian","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-AU":"English (Australia)","en-GB":"English (United Kingdom)","eo":"Esperanto","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fy":"Western Frisian","hu":"Hungarian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sk":"Slovak","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"} diff --git a/gui/default/assets/lang/valid-langs.js b/gui/default/assets/lang/valid-langs.js index a420a88385e..44a9ad209fb 100644 --- a/gui/default/assets/lang/valid-langs.js +++ b/gui/default/assets/lang/valid-langs.js @@ -1 +1 @@ -var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-GB","eo","es","es-ES","eu","fi","fr","fy","hu","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","zh-CN","zh-TW"] +var validLangs = ["bg","ca@valencia","cs","da","de","el","en","en-AU","en-GB","eo","es","es-ES","eu","fi","fr","fy","hu","it","ja","ko-KR","lt","nb","nl","pl","pt-BR","pt-PT","ru","sk","sv","tr","uk","zh-CN","zh-TW"] diff --git a/man/stdiscosrv.1 b/man/stdiscosrv.1 index 357abfe3bed..ec50197d6b4 100644 --- a/man/stdiscosrv.1 +++ b/man/stdiscosrv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "STDISCOSRV" "1" "Oct 19, 2020" "v1" "Syncthing" +.TH "STDISCOSRV" "1" "Oct 27, 2020" "v1" "Syncthing" .SH NAME stdiscosrv \- Syncthing Discovery Server . diff --git a/man/strelaysrv.1 b/man/strelaysrv.1 index f0a76a8265e..5b46fd38de9 100644 --- a/man/strelaysrv.1 +++ b/man/strelaysrv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "STRELAYSRV" "1" "Oct 19, 2020" "v1" "Syncthing" +.TH "STRELAYSRV" "1" "Oct 27, 2020" "v1" "Syncthing" .SH NAME strelaysrv \- Syncthing Relay Server . diff --git a/man/syncthing-bep.7 b/man/syncthing-bep.7 index 665d1ab458e..a0b897fbb08 100644 --- a/man/syncthing-bep.7 +++ b/man/syncthing-bep.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-BEP" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-BEP" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-bep \- Block Exchange Protocol v1 . diff --git a/man/syncthing-config.5 b/man/syncthing-config.5 index 885d09c508e..8d5f45153e7 100644 --- a/man/syncthing-config.5 +++ b/man/syncthing-config.5 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-CONFIG" "5" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-CONFIG" "5" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-config \- Syncthing Configuration . @@ -504,8 +504,7 @@ element: .INDENT 0.0 .TP .B id -The device ID. This must be written in canonical form, that is without any -spaces or dashes. (mandatory) +The device ID\&. (mandatory) .TP .B name A friendly name for the device. (optional) @@ -995,7 +994,7 @@ as part of launching Syncthing, set this option to \fBfalse\fP\&. .UNINDENT .SS Listen Addresses .sp -The following address types are accepted in sync protocol listen addresses. If you want Syncthing to listen on multiple addresses, you can have multiple \fB\fP tags. The same is achieved in the GUI by entering several addresses separated by comma. +The following address types are accepted in sync protocol listen addresses. If you want Syncthing to listen on multiple addresses, you can either: add multiple \fB\fP tags in the configuration file or enter several addresses separated by commas in the GUI. .INDENT 0.0 .TP .B Default listen addresses (\fBdefault\fP) diff --git a/man/syncthing-device-ids.7 b/man/syncthing-device-ids.7 index 2c53d863e1b..5ae61dc9ec1 100644 --- a/man/syncthing-device-ids.7 +++ b/man/syncthing-device-ids.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-DEVICE-IDS" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-DEVICE-IDS" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-device-ids \- Understanding Device IDs . diff --git a/man/syncthing-event-api.7 b/man/syncthing-event-api.7 index 5bea621cec0..120a643c4b1 100644 --- a/man/syncthing-event-api.7 +++ b/man/syncthing-event-api.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-EVENT-API" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-EVENT-API" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-event-api \- Event API . diff --git a/man/syncthing-faq.7 b/man/syncthing-faq.7 index 1eb2f844ba5..365badcb819 100644 --- a/man/syncthing-faq.7 +++ b/man/syncthing-faq.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-FAQ" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-FAQ" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-faq \- Frequently Asked Questions . diff --git a/man/syncthing-globaldisco.7 b/man/syncthing-globaldisco.7 index 7b852f3a577..5f0f791fd36 100644 --- a/man/syncthing-globaldisco.7 +++ b/man/syncthing-globaldisco.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-GLOBALDISCO" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-GLOBALDISCO" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-globaldisco \- Global Discovery Protocol v3 . diff --git a/man/syncthing-localdisco.7 b/man/syncthing-localdisco.7 index 5117e86cc3c..599dabdf08b 100644 --- a/man/syncthing-localdisco.7 +++ b/man/syncthing-localdisco.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-LOCALDISCO" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-LOCALDISCO" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-localdisco \- Local Discovery Protocol v4 . diff --git a/man/syncthing-networking.7 b/man/syncthing-networking.7 index cc869628cb6..80b21d2d1cd 100644 --- a/man/syncthing-networking.7 +++ b/man/syncthing-networking.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-NETWORKING" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-NETWORKING" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-networking \- Firewall Setup . diff --git a/man/syncthing-relay.7 b/man/syncthing-relay.7 index c6c57ae24aa..399767a7b61 100644 --- a/man/syncthing-relay.7 +++ b/man/syncthing-relay.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-RELAY" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-RELAY" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-relay \- Relay Protocol v1 . diff --git a/man/syncthing-rest-api.7 b/man/syncthing-rest-api.7 index 8e80974155f..b5f9cd5bbef 100644 --- a/man/syncthing-rest-api.7 +++ b/man/syncthing-rest-api.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-REST-API" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-REST-API" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-rest-api \- REST API . @@ -88,6 +88,10 @@ $ curl \-H "X\-API\-Key: yourkey" localhost:8384/rest/system/browse?current=/var .UNINDENT .UNINDENT .SS GET /rest/system/config +.sp +Deprecated since version v1.12.0: This endpoint still works as before but is deprecated. Use rest\-config +instead. + .sp Returns the current configuration. .INDENT 0.0 @@ -256,6 +260,10 @@ Returns the current configuration. .UNINDENT .UNINDENT .SS GET /rest/system/config/insync +.sp +Deprecated since version v1.12.0: This endpoint still works as before but is deprecated. Use +rest\-config\-insync instead. + .sp Returns whether the config is in sync, i.e. whether the running configuration is the same as that on disk. @@ -272,6 +280,10 @@ configuration is the same as that on disk. .UNINDENT .UNINDENT .SS POST /rest/system/config +.sp +Deprecated since version v1.12.0: This endpoint still works as before but is deprecated. Use rest\-config +instead. + .sp Post the full contents of the configuration, in the same format as returned by the corresponding GET request. When posting the configuration succeeds, @@ -678,6 +690,45 @@ Returns the current Syncthing version information. .fi .UNINDENT .UNINDENT +.SH CONFIG ENDPOINTS +.SS Config Endpoints +.sp +New in version 1.12.0. + +.sp +These endpoints facilitate access and modification of the configuration in a granular way. Config sent to the endpoints must be in the same +format as returned by the corresponding GET request. When posting the +configuration succeeds, the posted configuration is immediately applied, except +for changes that require a restart. Query \fI\%/rest/system/config/insync\fP to check if +a restart is required. +.sp +For all endpoints supporting \fBPATCH\fP, it takes the existing config and +unmarshals the given JSON object on top of it. This means all child objects will +replace the existing objects, not extend them. For example for +\fBRawListenAddresses\(ga in options, which is an array of strings, sending +\(ga\(ga{RawListenAddresses: ["tcp://10.0.0.2"]\fP will replace all existing listen +addresses. +.SS /rest/config +.sp +\fBGET\fP returns the entire config and \fBPUT\fP replaces it. +.SS /rest/system/config/insync +.sp +\fBGET\fP returns whether the config is in sync, i.e. whether the running configuration is +the same as that on disk or if a restart is required. +.SS /rest/config/folders, /rest/config/devices +.sp +\fBGET\fP returns all folders respectively devices as an array. \fBPUT\fP takes an array and +\fBPOST\fP a single object. In both cases if a given folder/device already exists, +it’s replaced, otherwise a new one is added. +.SS /rest/config/folders/*id*, /rest/config/devices/*id* +.sp +Put the desired folder\- respectively device\-ID in place of *id*. \fBGET\fP +returns the folder/device for the given ID, \fBPUT\fP replaces the entire config +and \fBPATCH\fP replaces only the given child objects. +.SS /rest/config/options, /rest/config/ldap, /rest/config/gui +.sp +\fBGET\fP returns the respective object, \fBPUT\fP replaces the entire object and +\fBPATCH\fP replaces only the given child objects. .SH DATABASE ENDPOINTS .SS GET /rest/db/browse .sp diff --git a/man/syncthing-security.7 b/man/syncthing-security.7 index af047fb29d5..259e9a2550f 100644 --- a/man/syncthing-security.7 +++ b/man/syncthing-security.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-SECURITY" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-SECURITY" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-security \- Security Principles . diff --git a/man/syncthing-stignore.5 b/man/syncthing-stignore.5 index fd4554b08eb..6381aa4d90d 100644 --- a/man/syncthing-stignore.5 +++ b/man/syncthing-stignore.5 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-STIGNORE" "5" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-STIGNORE" "5" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-stignore \- Prevent files from being synchronized to other nodes . diff --git a/man/syncthing-versioning.7 b/man/syncthing-versioning.7 index 639b3c0967e..9b744f31dc0 100644 --- a/man/syncthing-versioning.7 +++ b/man/syncthing-versioning.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-VERSIONING" "7" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING-VERSIONING" "7" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing-versioning \- Keep automatic backups of deleted files by other nodes . diff --git a/man/syncthing.1 b/man/syncthing.1 index ac333998d50..026d2aec1ba 100644 --- a/man/syncthing.1 +++ b/man/syncthing.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING" "1" "Oct 19, 2020" "v1" "Syncthing" +.TH "SYNCTHING" "1" "Oct 27, 2020" "v1" "Syncthing" .SH NAME syncthing \- Syncthing . @@ -64,7 +64,8 @@ Write events to timestamped file \fBaudit\-YYYYMMDD\-HHMMSS.log\fP\&. .INDENT 0.0 .TP .B \-auditfile= -Use specified file or stream (\fB"\-"\fP for stdout, \fB"\-\-"\fP for stderr) for audit events, rather than the timestamped default file name. +Use specified file or stream (\fB"\-"\fP for stdout, \fB"\-\-"\fP for stderr) for +audit events, rather than the timestamped default file name. .UNINDENT .INDENT 0.0 .TP @@ -110,7 +111,8 @@ together with \fB\-config\fP\&. .INDENT 0.0 .TP .B \-logfile= -Set destination filename for logging (use \fB"\-"\fP for stdout, which is the default option). +Set destination filename for logging (use \fB"\-"\fP for stdout, which is the +default option). .UNINDENT .INDENT 0.0 .TP @@ -147,12 +149,14 @@ Hide the console window. (On Windows only) .INDENT 0.0 .TP .B \-no\-restart -Disable the Syncthing monitor process which handles restarts for some configuration changes, upgrades, crashes and also log file writing (stdout is still written). +Do not restart Syncthing when it exits. The monitor process will still run +to handle crashes and writing to logfiles (if configured to). .UNINDENT .INDENT 0.0 .TP .B \-paths -Print the paths used for configuration, keys, database, GUI overrides, default sync folder and the log file. +Print the paths used for configuration, keys, database, GUI overrides, +default sync folder and the log file. .UNINDENT .INDENT 0.0 .TP @@ -222,11 +226,9 @@ Restarting Upgrading .UNINDENT .sp -Some of these exit codes are only returned when running without a monitor -process (with environment variable \fBSTNORESTART\fP set). Exit codes over 125 are -usually returned by the shell/binary loader/default signal handler. Exit codes -over 128+N on Unix usually represent the signal which caused the process to -exit. For example, \fB128 + 9 (SIGKILL) = 137\fP\&. +Exit codes over 125 are usually returned by the shell/binary loader/default +signal handler. Exit codes over 128+N on Unix usually represent the signal which +caused the process to exit. For example, \fB128 + 9 (SIGKILL) = 137\fP\&. .SH PROXIES .sp Syncthing can use a SOCKS, HTTP, or HTTPS proxy to talk to the outside @@ -381,9 +383,7 @@ Don’t create a default folder when starting for the first time. This variable will be ignored anytime after the first run. .TP .B STNORESTART -Equivalent to the \fB\-no\-restart\fP flag. Disable the Syncthing monitor -process which handles restarts for some configuration changes, upgrades, -crashes and also log file writing (stdout is still written). +Equivalent to the \fB\-no\-restart\fP flag .TP .B STNOUPGRADE Disable automatic upgrades. From 4a616f3cb27f59156179b24c91d05c6ba1d5fd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Wilczy=C5=84ski?= Date: Fri, 30 Oct 2020 23:13:56 +0900 Subject: [PATCH 17/42] lib/config: Check for "msdos" when detecting FAT FS in Android (#7072) --- lib/config/folderconfiguration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index bfa9bc75507..4c08b9d128a 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -71,7 +71,7 @@ func (f FolderConfiguration) ModTimeWindow() time.Duration { if usage, err := disk.Usage(f.Filesystem().URI()); err != nil { dur = 2 * time.Second l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: err == "%v"`, f.Path, err) - } else if usage.Fstype == "" || strings.Contains(strings.ToLower(usage.Fstype), "fat") { + } else if usage.Fstype == "" || strings.Contains(strings.ToLower(usage.Fstype), "fat") || strings.Contains(strings.ToLower(usage.Fstype), "msdos") { dur = 2 * time.Second l.Debugf(`Detecting FS at "%v" on android: Setting mtime window to 2s: usage.Fstype == "%v"`, f.Path, usage.Fstype) } else { From 9d1ee2f7e03f4eacd9730cc3b31c8274f75c4b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Sun, 1 Nov 2020 13:15:20 +0100 Subject: [PATCH 18/42] gui: Fix another undefined variable access (fixes #7077) (#7078) --- gui/default/syncthing/core/syncthingController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/default/syncthing/core/syncthingController.js b/gui/default/syncthing/core/syncthingController.js index 1947de17539..c03b4d04e5f 100755 --- a/gui/default/syncthing/core/syncthingController.js +++ b/gui/default/syncthing/core/syncthingController.js @@ -1927,8 +1927,8 @@ angular.module('syncthing.core') // Bump time pendingFolder.time = (new Date()).toISOString(); - if (id in $scope.devices) { - $scope.devices[id].ignoredFolders.push(pendingFolder); + if (device in $scope.devices) { + $scope.devices[device].ignoredFolders.push(pendingFolder); $scope.saveConfig(); } }; From 7dc0c6ab436da784b3fa00c65fcbe8597f9c4e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Sun, 1 Nov 2020 14:29:55 +0100 Subject: [PATCH 19/42] lib/api: Allow OPTIONS method in CORS preflight request handling (ref #7017) (#7079) This allows for checking GUI / API availability without actually doing a GET or POST request. --- lib/api/api.go | 4 ++-- lib/api/api_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/api.go b/lib/api/api.go index 9d8791bf518..bcc685b202b 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -503,8 +503,8 @@ func corsMiddleware(next http.Handler, allowFrameLoading bool) http.Handler { if r.Method == "OPTIONS" { // Add a generous access-control-allow-origin header for CORS requests w.Header().Add("Access-Control-Allow-Origin", "*") - // Only GET/POST Methods are supported - w.Header().Set("Access-Control-Allow-Methods", "GET, POST") + // Only GET/POST/OPTIONS Methods are supported + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") // Only these headers can be set w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key") // The request is meant to be cached 10 minutes diff --git a/lib/api/api_test.go b/lib/api/api_test.go index 4ef45c95696..5fa0af3e2e1 100644 --- a/lib/api/api_test.go +++ b/lib/api/api_test.go @@ -1073,8 +1073,8 @@ func TestOptionsRequest(t *testing.T) { if resp.Header.Get("Access-Control-Allow-Origin") != "*" { t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Origin: *' header") } - if resp.Header.Get("Access-Control-Allow-Methods") != "GET, POST" { - t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Methods: GET, POST' header") + if resp.Header.Get("Access-Control-Allow-Methods") != "GET, POST, OPTIONS" { + t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Methods: GET, POST, OPTIONS' header") } if resp.Header.Get("Access-Control-Allow-Headers") != "Content-Type, X-API-Key" { t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Headers: Content-Type, X-API-KEY' header") From 4d1bcd718ce15c9d15205fe3bd383eda0a5a6e92 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Sun, 1 Nov 2020 21:36:54 +0100 Subject: [PATCH 20/42] lib/api: Fix /rest/config path and add methods to cors (ref #7001) (#7081) --- lib/api/api.go | 4 ++-- lib/api/api_test.go | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/api/api.go b/lib/api/api.go index bcc685b202b..e7e49b3f7f6 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -302,7 +302,7 @@ func (s *service) serve(ctx context.Context) { mut: sync.NewMutex(), } - configBuilder.registerConfig("/rest/config/") + configBuilder.registerConfig("/rest/config") configBuilder.registerConfigInsync("/rest/config/insync") configBuilder.registerFolders("/rest/config/folders") configBuilder.registerDevices("/rest/config/devices") @@ -504,7 +504,7 @@ func corsMiddleware(next http.Handler, allowFrameLoading bool) http.Handler { // Add a generous access-control-allow-origin header for CORS requests w.Header().Add("Access-Control-Allow-Origin", "*") // Only GET/POST/OPTIONS Methods are supported - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") // Only these headers can be set w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-API-Key") // The request is meant to be cached 10 minutes diff --git a/lib/api/api_test.go b/lib/api/api_test.go index 5fa0af3e2e1..de7e3a80442 100644 --- a/lib/api/api_test.go +++ b/lib/api/api_test.go @@ -403,6 +403,12 @@ func TestAPIServiceRequests(t *testing.T) { }, // /rest/config + { + URL: "/rest/config", + Code: 200, + Type: "application/json", + Prefix: "", + }, { URL: "/rest/config/folders", Code: 200, @@ -1073,8 +1079,8 @@ func TestOptionsRequest(t *testing.T) { if resp.Header.Get("Access-Control-Allow-Origin") != "*" { t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Origin: *' header") } - if resp.Header.Get("Access-Control-Allow-Methods") != "GET, POST, OPTIONS" { - t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Methods: GET, POST, OPTIONS' header") + if resp.Header.Get("Access-Control-Allow-Methods") != "GET, POST, PUT, PATCH, DELETE, OPTIONS" { + t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS' header") } if resp.Header.Get("Access-Control-Allow-Headers") != "Content-Type, X-API-Key" { t.Fatal("OPTIONS on /rest/system/status should return a 'Access-Control-Allow-Headers: Content-Type, X-API-KEY' header") From 5b9280c50f5670f2d4896e19025b285605d1ef9d Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Sun, 1 Nov 2020 21:37:31 +0100 Subject: [PATCH 21/42] build: Update notify (fixes #7063) (#7080) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 74ca0a18cf7..4fb669b7594 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 github.com/sasha-s/go-deadlock v0.2.0 github.com/shirou/gopsutil v2.20.7+incompatible - github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2 + github.com/syncthing/notify v0.0.0-20201101120444-a28a0bd0f5ee github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 github.com/thejerf/suture v3.0.2+incompatible github.com/urfave/cli v1.22.2 diff --git a/go.sum b/go.sum index 50d3d0275b9..6344990056e 100644 --- a/go.sum +++ b/go.sum @@ -329,6 +329,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2 h1:6tuEEEpg+mxM82E0YingzoXzXXISYR/o/7I9n573LWI= github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg= +github.com/syncthing/notify v0.0.0-20201101120444-a28a0bd0f5ee h1:Q2dajND8VmNqXOi+N3IQQP77VkuXMA7tvPzXosDS1vA= +github.com/syncthing/notify v0.0.0-20201101120444-a28a0bd0f5ee/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg= github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 h1:udtnv1cokhJYqnUfCMCppJ71bFN9VKfG1BQ6UsYZnx8= github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= From 7892547873c805ef4066f68ea1097d083a759725 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 3 Nov 2020 09:10:35 +0100 Subject: [PATCH 22/42] lib: Remove USE_BADGER experiment (#7089) This removes the switch for using a Badger database, because it has bugs that it seems there is no interest in fixing, and no actual bug tracker to track them in. It retains the actual implementation for the sole purpose of being able to do the conversion back to LevelDB if anyone is actually running with USE_BADGER. At some point in a couple of versions we can remove the implementation as well. --- lib/build/build.go | 1 - lib/db/backend/backend.go | 12 +----------- lib/db/db_test.go | 6 ------ lib/locations/locations.go | 5 ----- 4 files changed, 1 insertion(+), 23 deletions(-) diff --git a/lib/build/build.go b/lib/build/build.go index 5c8d7c33638..69a509b2117 100644 --- a/lib/build/build.go +++ b/lib/build/build.go @@ -43,7 +43,6 @@ var ( "STHASHING", "STNORESTART", "STNOUPGRADE", - "USE_BADGER", } ) diff --git a/lib/db/backend/backend.go b/lib/db/backend/backend.go index 2cdeaf49852..4a29a79cc56 100644 --- a/lib/db/backend/backend.go +++ b/lib/db/backend/backend.go @@ -131,24 +131,14 @@ const ( ) func Open(path string, tuning Tuning) (Backend, error) { - if os.Getenv("USE_BADGER") != "" { - l.Warnln("Using experimental badger db") - if err := maybeCopyDatabase(path, strings.Replace(path, locations.BadgerDir, locations.LevelDBDir, 1), OpenBadger, OpenLevelDBRO); err != nil { - return nil, err - } - return OpenBadger(path) - } - if err := maybeCopyDatabase(path, strings.Replace(path, locations.LevelDBDir, locations.BadgerDir, 1), OpenLevelDBAuto, OpenBadger); err != nil { return nil, err } + return OpenLevelDB(path, tuning) } func OpenMemory() Backend { - if os.Getenv("USE_BADGER") != "" { - return OpenBadgerMemory() - } return OpenLevelDBMemory() } diff --git a/lib/db/db_test.go b/lib/db/db_test.go index 2c32d422b1e..07c2ae7bc26 100644 --- a/lib/db/db_test.go +++ b/lib/db/db_test.go @@ -10,7 +10,6 @@ import ( "bytes" "context" "fmt" - "os" "testing" "github.com/syncthing/syncthing/lib/db/backend" @@ -801,11 +800,6 @@ func TestFlushRecursion(t *testing.T) { // Verify that a commit hook can write to the transaction without // causing another flush and thus recursion. - // Badger doesn't work like this. - if os.Getenv("USE_BADGER") != "" { - t.Skip("Not supported on Badger") - } - db := NewLowlevel(backend.OpenMemory()) defer db.Close() diff --git a/lib/locations/locations.go b/lib/locations/locations.go index 702683f268d..8e0e69ec838 100644 --- a/lib/locations/locations.go +++ b/lib/locations/locations.go @@ -53,11 +53,6 @@ const ( var baseDirs = make(map[BaseDirEnum]string, 3) func init() { - if os.Getenv("USE_BADGER") != "" { - // XXX: Replace the leveldb name with the badger name. - locationTemplates[Database] = strings.Replace(locationTemplates[Database], LevelDBDir, BadgerDir, 1) - } - userHome := userHomeDir() config := defaultConfigDir(userHome) baseDirs[UserHomeBaseDir] = userHome From 942b8ebb27c956ed819940b1683d088d294d934e Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 3 Nov 2020 09:11:00 +0100 Subject: [PATCH 23/42] build: Update dependencies (#7088) --- go.mod | 43 ++++---- go.sum | 330 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 297 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index 4fb669b7594..82a3864da87 100644 --- a/go.mod +++ b/go.mod @@ -3,53 +3,50 @@ module github.com/syncthing/syncthing require ( github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6 github.com/AudriusButkevicius/recli v0.0.5 - github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e github.com/calmh/xdr v1.1.0 - github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d - github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 // indirect + github.com/ccding/go-stun v0.1.2 + github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 github.com/d4l3k/messagediff v1.2.1 - github.com/dchest/siphash v1.2.1 + github.com/dchest/siphash v1.2.2 github.com/dgraph-io/badger/v2 v2.0.3 github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 github.com/getsentry/raven-go v0.2.0 - github.com/go-ldap/ldap/v3 v3.2.0 + github.com/go-ldap/ldap/v3 v3.2.4 github.com/go-ole/go-ole v1.2.4 // indirect github.com/gobwas/glob v0.2.3 github.com/gogo/protobuf v1.3.1 - github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 - github.com/golang/protobuf v1.4.2 - github.com/greatroar/blobloom v0.3.0 + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e + github.com/golang/protobuf v1.4.3 + github.com/greatroar/blobloom v0.5.0 github.com/jackpal/gateway v1.0.6 github.com/jackpal/go-nat-pmp v1.0.2 - github.com/julienschmidt/httprouter v1.2.0 + github.com/julienschmidt/httprouter v1.3.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/kr/pretty v0.2.0 // indirect - github.com/lib/pq v1.2.0 - github.com/lucas-clemente/quic-go v0.18.0 + github.com/lib/pq v1.8.0 + github.com/lucas-clemente/quic-go v0.18.1 github.com/maruel/panicparse v1.5.1 github.com/mattn/go-isatty v0.0.12 github.com/minio/sha256-simd v0.1.1 github.com/oschwald/geoip2-golang v1.4.0 github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.2.1 - github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 + github.com/prometheus/client_golang v1.8.0 + github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 github.com/sasha-s/go-deadlock v0.2.0 - github.com/shirou/gopsutil v2.20.7+incompatible + github.com/shirou/gopsutil v3.20.10+incompatible github.com/syncthing/notify v0.0.0-20201101120444-a28a0bd0f5ee github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 - github.com/thejerf/suture v3.0.2+incompatible - github.com/urfave/cli v1.22.2 + github.com/thejerf/suture v4.0.0+incompatible + github.com/urfave/cli v1.22.4 github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 - golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de - golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc - golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 - golang.org/x/text v0.3.3 - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 - google.golang.org/protobuf v1.25.0 // indirect + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 + golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 + golang.org/x/text v0.3.4 + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e ) go 1.14 diff --git a/go.sum b/go.sum index 6344990056e..84d13e21943 100644 --- a/go.sum +++ b/go.sum @@ -16,24 +16,40 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzS github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e h1:2augTYh6E+XoNrrivZJBadpThP/dsvYKj0nzqfQ8tM4= github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+YLEMIJsTb+NV6NexWICk5+AMSuz3ss= @@ -41,50 +57,68 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/calmh/xdr v1.1.0 h1:U/Dd4CXNLoo8EiQ4ulJUXkgO1/EyQLgDKLgpY1SOoJE= github.com/calmh/xdr v1.1.0/go.mod h1:E8sz2ByAdXC8MbANf1LCRYzedSnnc+/sXXJs/PVqoeg= -github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io= -github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/ccding/go-stun v0.1.2 h1:1CZhjVwfyO/jGxk06a+0OSOGBWZu588kuZQQO4nihsw= +github.com/ccding/go-stun v0.1.2/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 h1:8k9FLYBLKT+9v2HQJ/a95ZemmTx+/ltJcAiRhVushG8= -github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 h1:Wg96Dh0MLTanEaPO0OkGtUIaa2jOnShAIOVUIzRHUxo= github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= -github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I= +github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dgraph-io/badger/v2 v2.0.3 h1:inzdf6VF/NZ+tJ8RwwYMjJMvsOALTHYdozn0qSl6XJI= github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM= github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU= github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -93,28 +127,37 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-asn1-ber/asn1-ber v1.5.0 h1:/S4hO/AO6tLMlPX0oftGSOcdGJJN/MuYzfgWRMn199E= -github.com/go-asn1-ber/asn1-ber v1.5.0/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-ldap/ldap/v3 v3.2.0 h1:fkS0nXg43MZvU0UNTOGyQv60WdwHRXa1eX0CSzuKLvY= -github.com/go-ldap/ldap/v3 v3.2.0/go.mod h1:dtLsnBXnSLIsMRbCBuRpHflCGaYzZ5jn+x1q7XqMTKU= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-ldap/ldap/v3 v3.2.4 h1:PFavAq2xTgzo/loE8qNXcQaofAaqIpI4WgaLdv+1l3E= +github.com/go-ldap/ldap/v3 v3.2.4/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= @@ -135,64 +178,108 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/greatroar/blobloom v0.3.0 h1:TSf9vu9lZ840bnMXNFpFKe61AISBZL5a9uRL62KixCY= -github.com/greatroar/blobloom v0.3.0/go.mod h1:we9vO6GNYMmsNvCWINtZnQbcGEHUT6hGBAznNHd6RlE= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/greatroar/blobloom v0.5.0 h1:jNbCsgDpZ23AI6jgZsXm7oFatkFaLCxr+ZWzlYasONU= +github.com/greatroar/blobloom v0.5.0/go.mod h1:M+yFtr/P96aNZYDYowvNWL3WdDluSMK2PPPHN49LMw8= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/gateway v1.0.6 h1:/MJORKvJEwNVldtGVJC2p2cwCnsSoLn3hl3zxmZT7tk= github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lucas-clemente/quic-go v0.18.0 h1:JhQDdqxdwdmGdKsKgXi1+coHRoGhvU6z0rNzOJqZ/4o= -github.com/lucas-clemente/quic-go v0.18.0/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= +github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= @@ -202,83 +289,144 @@ github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5D github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/maruel/panicparse v1.5.1 h1:hUPcXI7ubtEqj/k+P34KsHQqb86zuVk7zBfkP6tBBPc= github.com/maruel/panicparse v1.5.1/go.mod h1:aOutY/MUjdj80R0AEVI9qE2zHqig+67t2ffUDDiLzAM= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= -github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w= -github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.20.10+incompatible h1:kQuRhh6h6y4luXvnmtu/lJEGtdJ3q8lbu9NQY99GP+o= +github.com/shirou/gopsutil v3.20.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -306,6 +454,11 @@ github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5k github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -313,10 +466,15 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= @@ -325,66 +483,96 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2 h1:6tuEEEpg+mxM82E0YingzoXzXXISYR/o/7I9n573LWI= -github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syncthing/notify v0.0.0-20201101120444-a28a0bd0f5ee h1:Q2dajND8VmNqXOi+N3IQQP77VkuXMA7tvPzXosDS1vA= github.com/syncthing/notify v0.0.0-20201101120444-a28a0bd0f5ee/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg= github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 h1:udtnv1cokhJYqnUfCMCppJ71bFN9VKfG1BQ6UsYZnx8= github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/thejerf/suture v3.0.2+incompatible h1:GtMydYcnK4zBJ0KL6Lx9vLzl6Oozb65wh252FTBxrvM= -github.com/thejerf/suture v3.0.2+incompatible/go.mod h1:ibKwrVj+Uzf3XZdAiNWUouPaAbSoemxOHLmJmwheEMc= +github.com/thejerf/suture v4.0.0+incompatible h1:luAwgEo87y1X30wEYa64N4SKMrsAm9qXRwNxnLVuuwg= +github.com/thejerf/suture v4.0.0+incompatible/go.mod h1:ibKwrVj+Uzf3XZdAiNWUouPaAbSoemxOHLmJmwheEMc= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI= github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -397,12 +585,16 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -411,22 +603,28 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 h1:YEu4SMq7D0cmT7CBbXfcH0NZeuChAXwsHe/9XueUO6o= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -434,10 +632,14 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -445,8 +647,16 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= @@ -454,6 +664,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -463,53 +674,66 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= From a38b370c8dcb4698a928498d24c24099f6550a6b Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Tue, 3 Nov 2020 12:29:33 +0100 Subject: [PATCH 24/42] lib/ur: Fix panics in failure-reporting (fixes #7090) (#7091) --- lib/ur/failurereporting.go | 57 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/ur/failurereporting.go b/lib/ur/failurereporting.go index e5eab99d894..30525104ffd 100644 --- a/lib/ur/failurereporting.go +++ b/lib/ur/failurereporting.go @@ -26,9 +26,11 @@ var ( // When a specific failure first occurs, it is delayed by minDelay. If // more of the same failures occurs those are further delayed and // aggregated for maxDelay. - minDelay = 10 * time.Second - maxDelay = time.Minute - sendTimeout = time.Minute + minDelay = 10 * time.Second + maxDelay = time.Minute + sendTimeout = time.Minute + evChanClosed = "failure event channel closed" + invalidEventDataType = "failure event data is not a string" ) type FailureReport struct { @@ -47,6 +49,7 @@ func NewFailureHandler(cfg config.Wrapper, evLogger events.Logger) FailureHandle cfg: cfg, evLogger: evLogger, optsChan: make(chan config.OptionsConfiguration), + buf: make(map[string]*failureStat), } h.Service = util.AsServiceWithError(h.serve, h.String()) return h @@ -57,7 +60,6 @@ type failureHandler struct { cfg config.Wrapper evLogger events.Logger optsChan chan config.OptionsConfiguration - evChan <-chan events.Event buf map[string]*failureStat } @@ -68,7 +70,10 @@ type failureStat struct { func (h *failureHandler) serve(ctx context.Context) error { go func() { - h.optsChan <- h.cfg.Options() + select { + case h.optsChan <- h.cfg.Options(): + case <-ctx.Done(): + } }() h.cfg.Subscribe(h) defer h.cfg.Unsubscribe(h) @@ -76,6 +81,7 @@ func (h *failureHandler) serve(ctx context.Context) error { var url string var err error var sub events.Subscription + var evChan <-chan events.Event timer := time.NewTimer(minDelay) resetTimer := make(chan struct{}) outer: @@ -86,25 +92,29 @@ outer: if opts.URAccepted > 0 { if sub == nil { sub = h.evLogger.Subscribe(events.Failure) - h.evChan = sub.C() + evChan = sub.C() } } else if sub != nil { sub.Unsubscribe() sub = nil + evChan = nil } url = opts.CRURL + "/failure" - case e := <-h.evChan: - descr := e.Data.(string) - if stat, ok := h.buf[descr]; ok { - stat.last = e.Time - stat.count++ - } else { - h.buf[descr] = &failureStat{ - first: e.Time, - last: e.Time, - count: 1, - } + case e, ok := <-evChan: + if !ok { + // Just to be safe - shouldn't ever happen, as + // evChan is set to nil when unsubscribing. + h.addReport(evChanClosed, time.Now()) + evChan = nil + continue + } + descr, ok := e.Data.(string) + if !ok { + // Same here, shouldn't ever happen. + h.addReport(invalidEventDataType, time.Now()) + continue } + h.addReport(descr, e.Time) case <-timer.C: reports := make([]FailureReport, 0, len(h.buf)) now := time.Now() @@ -141,6 +151,19 @@ outer: return err } +func (h *failureHandler) addReport(descr string, evTime time.Time) { + if stat, ok := h.buf[descr]; ok { + stat.last = evTime + stat.count++ + return + } + h.buf[descr] = &failureStat{ + first: evTime, + last: evTime, + count: 1, + } +} + func (h *failureHandler) VerifyConfiguration(_, _ config.Configuration) error { return nil } From 2b9cef3ae5fff590ed993871188f70ce2fbbc1d0 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Tue, 3 Nov 2020 12:29:33 +0100 Subject: [PATCH 25/42] lib/ur: Fix panics in failure-reporting (fixes #7090) (#7091) --- lib/ur/failurereporting.go | 57 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/lib/ur/failurereporting.go b/lib/ur/failurereporting.go index e5eab99d894..30525104ffd 100644 --- a/lib/ur/failurereporting.go +++ b/lib/ur/failurereporting.go @@ -26,9 +26,11 @@ var ( // When a specific failure first occurs, it is delayed by minDelay. If // more of the same failures occurs those are further delayed and // aggregated for maxDelay. - minDelay = 10 * time.Second - maxDelay = time.Minute - sendTimeout = time.Minute + minDelay = 10 * time.Second + maxDelay = time.Minute + sendTimeout = time.Minute + evChanClosed = "failure event channel closed" + invalidEventDataType = "failure event data is not a string" ) type FailureReport struct { @@ -47,6 +49,7 @@ func NewFailureHandler(cfg config.Wrapper, evLogger events.Logger) FailureHandle cfg: cfg, evLogger: evLogger, optsChan: make(chan config.OptionsConfiguration), + buf: make(map[string]*failureStat), } h.Service = util.AsServiceWithError(h.serve, h.String()) return h @@ -57,7 +60,6 @@ type failureHandler struct { cfg config.Wrapper evLogger events.Logger optsChan chan config.OptionsConfiguration - evChan <-chan events.Event buf map[string]*failureStat } @@ -68,7 +70,10 @@ type failureStat struct { func (h *failureHandler) serve(ctx context.Context) error { go func() { - h.optsChan <- h.cfg.Options() + select { + case h.optsChan <- h.cfg.Options(): + case <-ctx.Done(): + } }() h.cfg.Subscribe(h) defer h.cfg.Unsubscribe(h) @@ -76,6 +81,7 @@ func (h *failureHandler) serve(ctx context.Context) error { var url string var err error var sub events.Subscription + var evChan <-chan events.Event timer := time.NewTimer(minDelay) resetTimer := make(chan struct{}) outer: @@ -86,25 +92,29 @@ outer: if opts.URAccepted > 0 { if sub == nil { sub = h.evLogger.Subscribe(events.Failure) - h.evChan = sub.C() + evChan = sub.C() } } else if sub != nil { sub.Unsubscribe() sub = nil + evChan = nil } url = opts.CRURL + "/failure" - case e := <-h.evChan: - descr := e.Data.(string) - if stat, ok := h.buf[descr]; ok { - stat.last = e.Time - stat.count++ - } else { - h.buf[descr] = &failureStat{ - first: e.Time, - last: e.Time, - count: 1, - } + case e, ok := <-evChan: + if !ok { + // Just to be safe - shouldn't ever happen, as + // evChan is set to nil when unsubscribing. + h.addReport(evChanClosed, time.Now()) + evChan = nil + continue + } + descr, ok := e.Data.(string) + if !ok { + // Same here, shouldn't ever happen. + h.addReport(invalidEventDataType, time.Now()) + continue } + h.addReport(descr, e.Time) case <-timer.C: reports := make([]FailureReport, 0, len(h.buf)) now := time.Now() @@ -141,6 +151,19 @@ outer: return err } +func (h *failureHandler) addReport(descr string, evTime time.Time) { + if stat, ok := h.buf[descr]; ok { + stat.last = evTime + stat.count++ + return + } + h.buf[descr] = &failureStat{ + first: evTime, + last: evTime, + count: 1, + } +} + func (h *failureHandler) VerifyConfiguration(_, _ config.Configuration) error { return nil } From d0ccea04045e9b154970a92f8eafa0c76cb0f2e2 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Tue, 3 Nov 2020 19:09:32 +0100 Subject: [PATCH 26/42] lib/config: Sanity checks on MaxConcurrentWrites (ref #7064) (#7069) --- lib/config/config_test.go | 1 + lib/config/folderconfiguration.go | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 0008d5c8304..3d73912e46f 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -129,6 +129,7 @@ func TestDeviceConfig(t *testing.T) { WeakHashThresholdPct: 25, MarkerName: DefaultMarkerName, JunctionsAsDirs: true, + MaxConcurrentWrites: maxConcurrentWritesDefault, }, } diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index 4c08b9d128a..d92dd67f8d5 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -26,7 +26,11 @@ var ( ErrMarkerMissing = errors.New("folder marker missing (this indicates potential data loss, search docs/forum to get information about how to proceed)") ) -const DefaultMarkerName = ".stfolder" +const ( + DefaultMarkerName = ".stfolder" + maxConcurrentWritesDefault = 2 + maxConcurrentWritesLimit = 64 +) func NewFolderConfiguration(myID protocol.DeviceID, id, label string, fsType fs.FilesystemType, path string) FolderConfiguration { f := FolderConfiguration{ @@ -206,6 +210,12 @@ func (f *FolderConfiguration) prepare() { if f.MarkerName == "" { f.MarkerName = DefaultMarkerName } + + if f.MaxConcurrentWrites <= 0 { + f.MaxConcurrentWrites = maxConcurrentWritesDefault + } else if f.MaxConcurrentWrites > maxConcurrentWritesLimit { + f.MaxConcurrentWrites = maxConcurrentWritesLimit + } } // RequiresRestartOnly returns a copy with only the attributes that require From 33185fdeb5beb24352986f54bfa823dba7834668 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Wed, 4 Nov 2020 07:45:27 +0100 Subject: [PATCH 27/42] gui, man, authors: Update docs, translations, and contributors --- gui/default/assets/lang/lang-fr.json | 2 +- gui/default/assets/lang/lang-ko-KR.json | 16 ++++++++-------- gui/default/assets/lang/lang-zh-TW.json | 6 +++--- man/stdiscosrv.1 | 2 +- man/strelaysrv.1 | 2 +- man/syncthing-bep.7 | 2 +- man/syncthing-config.5 | 2 +- man/syncthing-device-ids.7 | 2 +- man/syncthing-event-api.7 | 2 +- man/syncthing-faq.7 | 2 +- man/syncthing-globaldisco.7 | 2 +- man/syncthing-localdisco.7 | 2 +- man/syncthing-networking.7 | 2 +- man/syncthing-relay.7 | 2 +- man/syncthing-rest-api.7 | 2 +- man/syncthing-security.7 | 2 +- man/syncthing-stignore.5 | 2 +- man/syncthing-versioning.7 | 2 +- man/syncthing.1 | 2 +- 19 files changed, 28 insertions(+), 28 deletions(-) diff --git a/gui/default/assets/lang/lang-fr.json b/gui/default/assets/lang/lang-fr.json index 4e1cc158826..4864deaffad 100644 --- a/gui/default/assets/lang/lang-fr.json +++ b/gui/default/assets/lang/lang-fr.json @@ -33,7 +33,7 @@ "Automatic upgrade now offers the choice between stable releases and release candidates.": "Le système de mise à jour automatique propose le choix entre versions stables et versions préliminaires.", "Automatic upgrades": "Mises à jour automatiques", "Automatic upgrades are always enabled for candidate releases.": "Les mises à jour automatiques sont toujours activées pour les versions préliminaires (-rc.N).", - "Automatically create or share folders that this device advertises at the default path.": "ATTENTION !!! Créer ou partager automatiquement dans le chemin par défaut les partages que cet appareil annonce.", + "Automatically create or share folders that this device advertises at the default path.": "ATTENTION, risque de sécurité/confidentialité !!! Créer ou partager automatiquement dans le chemin par défaut les partages auxquels cet appareil m'invite à participer. N'accordez ce privilège, éventuellement temporaire le temps de l'établissement, qu'à vos propres appareils.", "Available debug logging facilities:": "Outils de débogage disponibles :", "Be careful!": "Faites attention !", "Bugs": "Bugs", diff --git a/gui/default/assets/lang/lang-ko-KR.json b/gui/default/assets/lang/lang-ko-KR.json index cc8f3850674..38d9e8b6a06 100644 --- a/gui/default/assets/lang/lang-ko-KR.json +++ b/gui/default/assets/lang/lang-ko-KR.json @@ -56,7 +56,7 @@ "Copied from original": "원본에서 복사됨", "Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:", "Creating ignore patterns, overwriting an existing file at {%path%}.": "무시 패턴 만들기, {{path}}에 존재하는 파일을 덮어쓰기 합니다", - "Currently Shared With Devices": "Currently Shared With Devices", + "Currently Shared With Devices": "현재 공유된 기기들", "Danger!": "경고!", "Debugging Facilities": "디버깅 기능", "Default Folder Path": "기본 폴더 경로", @@ -71,7 +71,7 @@ "Device rate limits": "Device rate limits", "Device that last modified the item": "항목을 마지막으로 수정 한 기기", "Devices": "기기", - "Disable Crash Reporting": "Disable Crash Reporting", + "Disable Crash Reporting": "충돌 보고 해제", "Disabled": "비활성화", "Disabled periodic scanning and disabled watching for changes": "주기적 스캔을 사용 중지하고 변경 사항을 감시하지 않음", "Disabled periodic scanning and enabled watching for changes": "주기적 스캔을 사용 중지하고 변경 사항 감시 하기", @@ -91,8 +91,8 @@ "Downloaded": "다운로드됨", "Downloading": "다운로드 중", "Edit": "편집", - "Edit Device": "Edit Device", - "Edit Folder": "Edit Folder", + "Edit Device": "기기 수정", + "Edit Folder": "폴더 수정", "Editing {%path%}.": "{{path}} 수정하기.", "Enable Crash Reporting": "충돌 보고 활성화", "Enable NAT traversal": "NAT traversal 활성화", @@ -102,7 +102,7 @@ "Enter a non-privileged port number (1024 - 65535).": "비 특권 포트 번호를 입력하세요 (1024 - 65535).", "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.", "Enter ignore patterns, one per line.": "무시할 패턴을 한 줄에 하나씩 입력하세요.", - "Enter up to three octal digits.": "Enter up to three octal digits.", + "Enter up to three octal digits.": "최대 3자리의 8진수를 입력하세요.", "Error": "오류", "External File Versioning": "외부 파일 버전 관리", "Failed Items": "실패한 항목", @@ -220,7 +220,7 @@ "Preview Usage Report": "사용 보고서 미리보기", "Quick guide to supported patterns": "지원하는 패턴에 대한 빠른 도움말", "Random": "무작위", - "Receive Only": "Receive Only", + "Receive Only": "수신 전용", "Recent Changes": "최근 변경", "Reduced by ignore patterns": "무시 패턴으로 축소", "Release Notes": "릴리즈 노트", @@ -310,7 +310,7 @@ "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.": "다음과 같은 간격이 사용됩니다: 첫 한 시간 동안은 버전이 매 30초마다 유지되며, 첫 하루 동안은 매 시간, 첫 한 달 동안은 매 일마다 유지됩니다. 그리고 최대 날짜까지는 버전이 매 주마다 유지됩니다.", "The following items could not be synchronized.": "이 항목들은 동기화 할 수 없습니다.", "The following items were changed locally.": "The following items were changed locally.", - "The interval must be a positive number of seconds.": "The interval must be a positive number of seconds.", + "The interval must be a positive number of seconds.": "간격은 초 단위의 자연수여야 합니다.", "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.", "The maximum age must be a number and cannot be blank.": "최대 보존 기간은 숫자여야 하며 비워 둘 수 없습니다.", "The maximum time to keep a version (in days, set to 0 to keep versions forever).": "버전을 유지할 최대 시간을 지정합니다. 일단위이며 버전을 계속 유지하려면 0을 입력하세요,", @@ -338,7 +338,7 @@ "Unignore": "Unignore", "Unknown": "알 수 없음", "Unshared": "공유되지 않음", - "Unshared Devices": "Unshared Devices", + "Unshared Devices": "공유되지 않은 기기들", "Up to Date": "최신 데이터", "Updated": "업데이트 완료", "Upgrade": "업데이트", diff --git a/gui/default/assets/lang/lang-zh-TW.json b/gui/default/assets/lang/lang-zh-TW.json index 58a3ca14c56..7df5f49ea4a 100644 --- a/gui/default/assets/lang/lang-zh-TW.json +++ b/gui/default/assets/lang/lang-zh-TW.json @@ -47,7 +47,7 @@ "Comment, when used at the start of a line": "註解,當輸入在一行的開頭時", "Compression": "壓縮", "Configured": "已設定", - "Connected (Unused)": "Connected (Unused)", + "Connected (Unused)": "已連線(未使用)", "Connection Error": "連線錯誤", "Connection Type": "連線類型", "Connections": "連線", @@ -79,7 +79,7 @@ "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).", "Discard": "忽略", "Disconnected": "斷線", - "Disconnected (Unused)": "Disconnected (Unused)", + "Disconnected (Unused)": "斷線(未使用)", "Discovered": "已發現", "Discovery": "探索", "Discovery Failures": "探索失敗", @@ -204,7 +204,7 @@ "Pause": "暫停", "Pause All": "全部暫停", "Paused": "暫停", - "Paused (Unused)": "Paused (Unused)", + "Paused (Unused)": "暫停(未使用)", "Pending changes": "等待中的變動", "Periodic scanning at given interval and disabled watching for changes": "在一定的時間間隔,定期掃描及關閉觀察變動", "Periodic scanning at given interval and enabled watching for changes": "在一定的時間間隔,定期掃描及啟用觀察變動", diff --git a/man/stdiscosrv.1 b/man/stdiscosrv.1 index ec50197d6b4..04b8490a9ac 100644 --- a/man/stdiscosrv.1 +++ b/man/stdiscosrv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "STDISCOSRV" "1" "Oct 27, 2020" "v1" "Syncthing" +.TH "STDISCOSRV" "1" "Nov 02, 2020" "v1" "Syncthing" .SH NAME stdiscosrv \- Syncthing Discovery Server . diff --git a/man/strelaysrv.1 b/man/strelaysrv.1 index 5b46fd38de9..2e53b5d27d8 100644 --- a/man/strelaysrv.1 +++ b/man/strelaysrv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "STRELAYSRV" "1" "Oct 27, 2020" "v1" "Syncthing" +.TH "STRELAYSRV" "1" "Nov 02, 2020" "v1" "Syncthing" .SH NAME strelaysrv \- Syncthing Relay Server . diff --git a/man/syncthing-bep.7 b/man/syncthing-bep.7 index a0b897fbb08..7cad8f71efe 100644 --- a/man/syncthing-bep.7 +++ b/man/syncthing-bep.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-BEP" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-BEP" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-bep \- Block Exchange Protocol v1 . diff --git a/man/syncthing-config.5 b/man/syncthing-config.5 index 8d5f45153e7..5e74db334e3 100644 --- a/man/syncthing-config.5 +++ b/man/syncthing-config.5 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-CONFIG" "5" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-CONFIG" "5" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-config \- Syncthing Configuration . diff --git a/man/syncthing-device-ids.7 b/man/syncthing-device-ids.7 index 5ae61dc9ec1..ff3f20e4c72 100644 --- a/man/syncthing-device-ids.7 +++ b/man/syncthing-device-ids.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-DEVICE-IDS" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-DEVICE-IDS" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-device-ids \- Understanding Device IDs . diff --git a/man/syncthing-event-api.7 b/man/syncthing-event-api.7 index 120a643c4b1..1d4824c035b 100644 --- a/man/syncthing-event-api.7 +++ b/man/syncthing-event-api.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-EVENT-API" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-EVENT-API" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-event-api \- Event API . diff --git a/man/syncthing-faq.7 b/man/syncthing-faq.7 index 365badcb819..2a655fa4394 100644 --- a/man/syncthing-faq.7 +++ b/man/syncthing-faq.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-FAQ" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-FAQ" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-faq \- Frequently Asked Questions . diff --git a/man/syncthing-globaldisco.7 b/man/syncthing-globaldisco.7 index 5f0f791fd36..81c491acb8c 100644 --- a/man/syncthing-globaldisco.7 +++ b/man/syncthing-globaldisco.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-GLOBALDISCO" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-GLOBALDISCO" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-globaldisco \- Global Discovery Protocol v3 . diff --git a/man/syncthing-localdisco.7 b/man/syncthing-localdisco.7 index 599dabdf08b..8d1382988fc 100644 --- a/man/syncthing-localdisco.7 +++ b/man/syncthing-localdisco.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-LOCALDISCO" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-LOCALDISCO" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-localdisco \- Local Discovery Protocol v4 . diff --git a/man/syncthing-networking.7 b/man/syncthing-networking.7 index 80b21d2d1cd..af08badc49c 100644 --- a/man/syncthing-networking.7 +++ b/man/syncthing-networking.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-NETWORKING" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-NETWORKING" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-networking \- Firewall Setup . diff --git a/man/syncthing-relay.7 b/man/syncthing-relay.7 index 399767a7b61..fb1659a3bd3 100644 --- a/man/syncthing-relay.7 +++ b/man/syncthing-relay.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-RELAY" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-RELAY" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-relay \- Relay Protocol v1 . diff --git a/man/syncthing-rest-api.7 b/man/syncthing-rest-api.7 index b5f9cd5bbef..ac85ac29765 100644 --- a/man/syncthing-rest-api.7 +++ b/man/syncthing-rest-api.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-REST-API" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-REST-API" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-rest-api \- REST API . diff --git a/man/syncthing-security.7 b/man/syncthing-security.7 index 259e9a2550f..5b75fdb377c 100644 --- a/man/syncthing-security.7 +++ b/man/syncthing-security.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-SECURITY" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-SECURITY" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-security \- Security Principles . diff --git a/man/syncthing-stignore.5 b/man/syncthing-stignore.5 index 6381aa4d90d..8a945144b23 100644 --- a/man/syncthing-stignore.5 +++ b/man/syncthing-stignore.5 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-STIGNORE" "5" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-STIGNORE" "5" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-stignore \- Prevent files from being synchronized to other nodes . diff --git a/man/syncthing-versioning.7 b/man/syncthing-versioning.7 index 9b744f31dc0..519e1bb5642 100644 --- a/man/syncthing-versioning.7 +++ b/man/syncthing-versioning.7 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING-VERSIONING" "7" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING-VERSIONING" "7" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing-versioning \- Keep automatic backups of deleted files by other nodes . diff --git a/man/syncthing.1 b/man/syncthing.1 index 026d2aec1ba..5037398f23b 100644 --- a/man/syncthing.1 +++ b/man/syncthing.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "SYNCTHING" "1" "Oct 27, 2020" "v1" "Syncthing" +.TH "SYNCTHING" "1" "Nov 02, 2020" "v1" "Syncthing" .SH NAME syncthing \- Syncthing . From a08a1b6998ae7ee559c779c284be3c2d6d9aae4e Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Fri, 6 Nov 2020 14:21:37 +0100 Subject: [PATCH 28/42] lib/api: Fix debug endpoints (ref #7001) (#7092) --- lib/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/api.go b/lib/api/api.go index e7e49b3f7f6..97a604802e1 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -323,7 +323,7 @@ func (s *service) serve(ctx context.Context) { debugMux.HandleFunc("/rest/debug/cpuprof", s.getCPUProf) // duration debugMux.HandleFunc("/rest/debug/heapprof", s.getHeapProf) debugMux.HandleFunc("/rest/debug/support", s.getSupportBundle) - restMux.Handler(http.MethodGet, "/rest/debug/", s.whenDebugging(debugMux)) + restMux.Handler(http.MethodGet, "/rest/debug/*method", s.whenDebugging(debugMux)) // A handler that disables caching noCacheRestMux := noCacheMiddleware(metricsMiddleware(restMux)) From cc9ea9db8978c5ca9408ee784f47bfb25ef9c950 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Fri, 6 Nov 2020 14:22:20 +0100 Subject: [PATCH 29/42] lib/folder: Clear pull errors when nothing is needed anymore (#7093) --- lib/model/folder.go | 32 ++++++++++++++------- lib/model/folder_sendrecv.go | 48 ++++++++++++------------------- lib/model/folder_sendrecv_test.go | 1 - 3 files changed, 41 insertions(+), 40 deletions(-) diff --git a/lib/model/folder.go b/lib/model/folder.go index cc197433c5e..f9e2dacb8f5 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -55,8 +55,6 @@ type folder struct { scanTimer *time.Timer scanDelay chan time.Duration initialScanFinished chan struct{} - scanErrors []FileError - scanErrorsMut sync.Mutex versionCleanupInterval time.Duration versionCleanupTimer *time.Timer @@ -64,6 +62,10 @@ type folder struct { pullPause time.Duration pullFailTimer *time.Timer + scanErrors []FileError + pullErrors []FileError + errorsMut sync.Mutex + doInSyncChan chan syncRequest forcedRescanRequested chan struct{} @@ -106,12 +108,13 @@ func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg conf scanTimer: time.NewTimer(0), // The first scan should be done immediately. scanDelay: make(chan time.Duration), initialScanFinished: make(chan struct{}), - scanErrorsMut: sync.NewMutex(), versionCleanupInterval: time.Duration(cfg.Versioning.CleanupIntervalS) * time.Second, versionCleanupTimer: time.NewTimer(time.Duration(cfg.Versioning.CleanupIntervalS) * time.Second), pullScheduled: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a pull if we're busy when it comes. + errorsMut: sync.NewMutex(), + doInSyncChan: make(chan syncRequest), forcedRescanRequested: make(chan struct{}, 1), @@ -333,6 +336,10 @@ func (f *folder) pull() (success bool) { }) snap.Release() if abort { + // Clears pull failures on items that were needed before, but aren't anymore. + f.errorsMut.Lock() + f.pullErrors = nil + f.errorsMut.Unlock() return true } @@ -967,18 +974,18 @@ func (f *folder) String() string { } func (f *folder) newScanError(path string, err error) { - f.scanErrorsMut.Lock() + f.errorsMut.Lock() l.Infof("Scanner (folder %s, item %q): %v", f.Description(), path, err) f.scanErrors = append(f.scanErrors, FileError{ Err: err.Error(), Path: path, }) - f.scanErrorsMut.Unlock() + f.errorsMut.Unlock() } func (f *folder) clearScanErrors(subDirs []string) { - f.scanErrorsMut.Lock() - defer f.scanErrorsMut.Unlock() + f.errorsMut.Lock() + defer f.errorsMut.Unlock() if len(subDirs) == 0 { f.scanErrors = nil return @@ -997,9 +1004,14 @@ outer: } func (f *folder) Errors() []FileError { - f.scanErrorsMut.Lock() - defer f.scanErrorsMut.Unlock() - return append([]FileError{}, f.scanErrors...) + f.errorsMut.Lock() + defer f.errorsMut.Unlock() + scanLen := len(f.scanErrors) + errors := make([]FileError, scanLen+len(f.pullErrors)) + copy(errors[:scanLen], f.scanErrors) + copy(errors[scanLen:], f.pullErrors) + sort.Sort(fileErrorList(errors)) + return errors } // ScheduleForceRescan marks the file such that it gets rehashed on next scan, and schedules a scan. diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 705c957f38b..d42aa7fdcea 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -128,9 +128,7 @@ type sendReceiveFolder struct { blockPullReorderer blockPullReorderer writeLimiter *byteSemaphore - pullErrors map[string]string // actual exposed pull errors tempPullErrors map[string]string // pull errors that might be just transient - pullErrorsMut sync.Mutex } func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem, evLogger events.Logger, ioLimiter *byteSemaphore) service { @@ -140,7 +138,6 @@ func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matche queue: newJobQueue(), blockPullReorderer: newBlockPullReorderer(cfg.BlockPullOrder, model.id, cfg.DeviceIDs()), writeLimiter: newByteSemaphore(cfg.MaxConcurrentWrites), - pullErrorsMut: sync.NewMutex(), } f.folder.puller = f f.folder.Service = util.AsService(f.serve, f.String()) @@ -177,9 +174,9 @@ func (f *sendReceiveFolder) pull() bool { changed := 0 - f.pullErrorsMut.Lock() + f.errorsMut.Lock() f.pullErrors = nil - f.pullErrorsMut.Unlock() + f.errorsMut.Unlock() for tries := 0; tries < maxPullerIterations; tries++ { select { @@ -204,14 +201,20 @@ func (f *sendReceiveFolder) pull() bool { } } - f.pullErrorsMut.Lock() - f.pullErrors = f.tempPullErrors - f.tempPullErrors = nil - for path, err := range f.pullErrors { - l.Infof("Puller (folder %s, item %q): %v", f.Description(), path, err) + f.errorsMut.Lock() + pullErrNum := len(f.tempPullErrors) + if pullErrNum > 0 { + f.pullErrors = make([]FileError, 0, len(f.tempPullErrors)) + for path, err := range f.tempPullErrors { + l.Infof("Puller (folder %s, item %q): %v", f.Description(), path, err) + f.pullErrors = append(f.pullErrors, FileError{ + Err: err, + Path: path, + }) + } + f.tempPullErrors = nil } - pullErrNum := len(f.pullErrors) - f.pullErrorsMut.Unlock() + f.errorsMut.Unlock() if pullErrNum > 0 { l.Infof("%v: Failed to sync %v items", f.Description(), pullErrNum) @@ -229,9 +232,9 @@ func (f *sendReceiveFolder) pull() bool { // might have failed). One puller iteration handles all files currently // flagged as needed in the folder. func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int { - f.pullErrorsMut.Lock() + f.errorsMut.Lock() f.tempPullErrors = make(map[string]string) - f.pullErrorsMut.Unlock() + f.errorsMut.Unlock() snap := f.fset.Snapshot() defer snap.Release() @@ -1788,8 +1791,8 @@ func (f *sendReceiveFolder) newPullError(path string, err error) { return } - f.pullErrorsMut.Lock() - defer f.pullErrorsMut.Unlock() + f.errorsMut.Lock() + defer f.errorsMut.Unlock() // We might get more than one error report for a file (i.e. error on // Write() followed by Close()); we keep the first error as that is @@ -1807,19 +1810,6 @@ func (f *sendReceiveFolder) newPullError(path string, err error) { l.Debugf("%v new error for %v: %v", f, path, err) } -func (f *sendReceiveFolder) Errors() []FileError { - scanErrors := f.folder.Errors() - f.pullErrorsMut.Lock() - errors := make([]FileError, 0, len(f.pullErrors)+len(f.scanErrors)) - for path, err := range f.pullErrors { - errors = append(errors, FileError{path, err}) - } - f.pullErrorsMut.Unlock() - errors = append(errors, scanErrors...) - sort.Sort(fileErrorList(errors)) - return errors -} - // deleteItemOnDisk deletes the file represented by old that is about to be replaced by new. func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, snap *db.Snapshot, scanChan chan<- string) (err error) { defer func() { diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index 32a9b4f4d49..6827ed00adf 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -96,7 +96,6 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol model := setupModel(w) model.Supervisor.Stop() f := model.folderRunners[fcfg.ID].(*sendReceiveFolder) - f.pullErrors = make(map[string]string) f.tempPullErrors = make(map[string]string) f.ctx = context.Background() From d4ce0dfd847be683191095d749b5041e6c58cbb5 Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Mon, 9 Nov 2020 08:58:46 +0100 Subject: [PATCH 30/42] lib/model: Send indexes for newly shared folder (fixes #7098) (#7100) --- lib/model/indexsender.go | 37 ++++++++++------- lib/model/model.go | 85 +++++++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 49 deletions(-) diff --git a/lib/model/indexsender.go b/lib/model/indexsender.go index 288db08c4c5..d0ce79bb6b6 100644 --- a/lib/model/indexsender.go +++ b/lib/model/indexsender.go @@ -25,7 +25,6 @@ type indexSender struct { suture.Service conn protocol.Connection folder string - dev string fset *db.FileSet prevSequence int64 evLogger events.Logger @@ -38,8 +37,8 @@ type indexSender struct { func (s *indexSender) serve(ctx context.Context) { var err error - l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.dev, s.conn, s.prevSequence) - defer l.Debugf("Exiting indexSender for %s to %s at %s: %v", s.folder, s.dev, s.conn, err) + l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.conn.ID(), s.conn, s.prevSequence) + defer l.Debugf("Exiting indexSender for %s to %s at %s: %v", s.folder, s.conn.ID(), s.conn, err) // We need to send one index, regardless of whether there is something to send or not err = s.sendIndexTo(ctx) @@ -204,7 +203,7 @@ func (s *indexSender) sendIndexTo(ctx context.Context) error { } func (s *indexSender) String() string { - return fmt.Sprintf("indexSender@%p for %s to %s at %s", s, s.folder, s.dev, s.conn) + return fmt.Sprintf("indexSender@%p for %s to %s at %s", s, s.folder, s.conn.ID(), s.conn) } type indexSenderRegistry struct { @@ -239,15 +238,13 @@ func (r *indexSenderRegistry) add(folder config.FolderConfiguration, fset *db.Fi r.mut.Unlock() } -func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset *db.FileSet, startInfo *indexSenderStartInfo) { - if is, ok := r.indexSenders[folder.ID]; ok { - r.sup.RemoveAndWait(is.token, 0) - delete(r.indexSenders, folder.ID) - } - if _, ok := r.startInfos[folder.ID]; ok { - delete(r.startInfos, folder.ID) - } +func (r *indexSenderRegistry) addNew(folder config.FolderConfiguration, fset *db.FileSet) { + r.mut.Lock() + r.startLocked(folder.ID, fset, 0) + r.mut.Unlock() +} +func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset *db.FileSet, startInfo *indexSenderStartInfo) { myIndexID := fset.IndexID(protocol.LocalDeviceID) mySequence := fset.Sequence(protocol.LocalDeviceID) var startSequence int64 @@ -305,10 +302,22 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset fset.SetIndexID(r.deviceID, startInfo.remote.IndexID) } + r.startLocked(folder.ID, fset, startSequence) +} + +func (r *indexSenderRegistry) startLocked(folderID string, fset *db.FileSet, startSequence int64) { + if is, ok := r.indexSenders[folderID]; ok { + r.sup.RemoveAndWait(is.token, 0) + delete(r.indexSenders, folderID) + } + if _, ok := r.startInfos[folderID]; ok { + delete(r.startInfos, folderID) + } + is := &indexSender{ conn: r.conn, connClosed: r.closed, - folder: folder.ID, + folder: folderID, fset: fset, prevSequence: startSequence, evLogger: r.evLogger, @@ -317,7 +326,7 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset } is.Service = util.AsService(is.serve, is.String()) is.token = r.sup.Add(is) - r.indexSenders[folder.ID] = is + r.indexSenders[folderID] = is } // addPaused stores the given info to start an index sender once resume is called diff --git a/lib/model/model.go b/lib/model/model.go index 5464e1117c1..ead2cc8c831 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -478,18 +478,10 @@ func (m *model) restartFolder(from, to config.FolderConfiguration, cacheIgnoredF // Cache the (maybe) existing fset before it's removed by cleanupFolderLocked fset := m.folderFiles[folder] + fsetNil := fset == nil m.cleanupFolderLocked(from) - if to.Paused { - // Care needs to be taken because we already hold fmut and the lock order - // must be the same everywhere. As fmut is acquired first, this is fine. - m.pmut.RLock() - for _, r := range m.indexSenders { - r.pause(to.ID) - } - m.pmut.RUnlock() - } else { - fsetNil := fset == nil + if !to.Paused { if fsetNil { // Create a new fset. Might take a while and we do it under // locking, but it's unsafe to create fset:s concurrently so @@ -497,16 +489,31 @@ func (m *model) restartFolder(from, to config.FolderConfiguration, cacheIgnoredF fset = db.NewFileSet(folder, to.Filesystem(), m.db) } m.addAndStartFolderLocked(to, fset, cacheIgnoredFiles) - if fsetNil || from.Paused { - for _, devID := range to.DeviceIDs() { - indexSenders, ok := m.indexSenders[devID] - if !ok { - continue - } - indexSenders.resume(to, fset) - } + } + + // Care needs to be taken because we already hold fmut and the lock order + // must be the same everywhere. As fmut is acquired first, this is fine. + // toDeviceIDs := to.DeviceIDs() + m.pmut.RLock() + for _, id := range to.DeviceIDs() { + indexSenders, ok := m.indexSenders[id] + if !ok { + continue + } + // In case the folder was newly shared with us we already got a + // cluster config and wont necessarily get another soon - start + // sending indexes if connected. + isNew := !from.SharedWith(indexSenders.deviceID) + if isNew { + indexSenders.addNew(to, fset) + } + if to.Paused { + indexSenders.pause(to.ID) + } else if !isNew && (fsetNil || from.Paused) { + indexSenders.resume(to, fset) } } + m.pmut.RUnlock() var infoMsg string switch { @@ -527,7 +534,24 @@ func (m *model) newFolder(cfg config.FolderConfiguration, cacheIgnoredFiles bool m.fmut.Lock() defer m.fmut.Unlock() + + // In case this folder is new and was shared with us we already got a + // cluster config and wont necessarily get another soon - start sending + // indexes if connected. + if fset.Sequence(protocol.LocalDeviceID) == 0 { + m.pmut.RLock() + for _, id := range cfg.DeviceIDs() { + if is, ok := m.indexSenders[id]; ok { + if fset.Sequence(id) == 0 { + is.addNew(cfg, fset) + } + } + } + m.pmut.RUnlock() + } + m.addAndStartFolderLocked(cfg, fset, cacheIgnoredFiles) + } func (m *model) UsageReportingStats(report *contract.Report, version int, preview bool) { @@ -1060,25 +1084,22 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon continue } - var foundRemote, foundLocal bool - var remoteDeviceInfo, localDeviceInfo protocol.Device + deviceInfos := &indexSenderStartInfo{} for _, dev := range folder.Devices { if dev.ID == m.id { - localDeviceInfo = dev - foundLocal = true + deviceInfos.local = dev } else if dev.ID == deviceID { - remoteDeviceInfo = dev - foundRemote = true + deviceInfos.remote = dev } - if foundRemote && foundLocal { + if deviceInfos.local.ID != protocol.EmptyDeviceID && deviceInfos.remote.ID != protocol.EmptyDeviceID { break } } - if !foundRemote { + if deviceInfos.remote.ID == protocol.EmptyDeviceID { l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID, folder.Description()) return errMissingRemoteInClusterConfig } - if !foundLocal { + if deviceInfos.local.ID == protocol.EmptyDeviceID { l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID, folder.Description()) return errMissingLocalInClusterConfig } @@ -1090,10 +1111,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon } if cfg.Paused { - indexSenderRegistry.addPaused(cfg, &indexSenderStartInfo{ - local: localDeviceInfo, - remote: remoteDeviceInfo, - }) + indexSenderRegistry.addPaused(cfg, deviceInfos) continue } @@ -1110,10 +1128,7 @@ func (m *model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon tempIndexFolders = append(tempIndexFolders, folder.ID) } - indexSenderRegistry.add(cfg, fs, &indexSenderStartInfo{ - local: localDeviceInfo, - remote: remoteDeviceInfo, - }) + indexSenderRegistry.add(cfg, fs, deviceInfos) // We might already have files that we need to pull so let the // folder runner know that it should recheck the index data. From 1f1729ba439f79740f112665e19fc468009e8b3a Mon Sep 17 00:00:00 2001 From: Simon Frei Date: Mon, 9 Nov 2020 09:05:48 +0100 Subject: [PATCH 31/42] lib/model: Add done chan to track folder-lifetime (fixes #6664) (#7094) --- lib/model/folder.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/model/folder.go b/lib/model/folder.go index f9e2dacb8f5..e5219febd42 100644 --- a/lib/model/folder.go +++ b/lib/model/folder.go @@ -49,7 +49,8 @@ type folder struct { fset *db.FileSet ignores *ignore.Matcher modTimeWindow time.Duration - ctx context.Context + ctx context.Context // used internally, only accessible on serve lifetime + done chan struct{} // used externally, accessible regardless of serve scanInterval time.Duration scanTimer *time.Timer @@ -103,6 +104,7 @@ func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg conf fset: fset, ignores: ignores, modTimeWindow: cfg.ModTimeWindow(), + done: make(chan struct{}), scanInterval: time.Duration(cfg.RescanIntervalS) * time.Second, scanTimer: time.NewTimer(0), // The first scan should be done immediately. @@ -165,6 +167,7 @@ func (f *folder) serve(ctx context.Context) { for { select { case <-f.ctx.Done(): + close(f.done) return case <-f.pullScheduled: @@ -218,7 +221,10 @@ func (f *folder) Override() {} func (f *folder) Revert() {} func (f *folder) DelayScan(next time.Duration) { - f.Delay(next) + select { + case f.scanDelay <- next: + case <-f.done: + } } func (f *folder) ignoresUpdated() { @@ -258,8 +264,8 @@ func (f *folder) doInSync(fn func() error) error { select { case f.doInSyncChan <- req: return <-req.err - case <-f.ctx.Done(): - return f.ctx.Err() + case <-f.done: + return context.Canceled } } @@ -274,10 +280,6 @@ func (f *folder) Reschedule() { f.scanTimer.Reset(interval) } -func (f *folder) Delay(next time.Duration) { - f.scanDelay <- next -} - func (f *folder) getHealthErrorAndLoadIgnores() error { if err := f.getHealthErrorWithoutIgnores(); err != nil { return err @@ -925,7 +927,7 @@ func (f *folder) scanOnWatchErr() { err := f.watchErr f.watchMut.Unlock() if err != nil { - f.Delay(0) + f.DelayScan(0) } } From 0fb7cc186c0ba7262a6cd1404d6aa1c812b7f529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Wilczy=C5=84ski?= Date: Mon, 9 Nov 2020 17:15:22 +0900 Subject: [PATCH 32/42] gui: Add warning when JavaScript is disabled in Web browser (fixes #7099) (#7102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using a Web browser with JavaScript either disabled or unavailable, show a warning to let the user know that the Web GUI requires JS in order to operate. To achieve this, add a
that wraps both the navbar and the main content, and then move the CSS class ng-cloak from the element to that
. This way, only the JavaScript-dependent part is hidden when JS is unavailable, and not the whole website, as it is the case right now. Then, add a