Compare commits

..

67 Commits

Author SHA1 Message Date
Simon Frei
ebad9e2073 gui: Fix regression on refreshNeed (fixes #6560, ref #6452) (#6561) 2020-04-21 22:45:03 +02:00
Simon Frei
ab92f8520c cmd/syncthing, lib/db: Store upgrade info to throttle queries (fixes #6513) (#6514) 2020-04-13 10:21:07 +02:00
Jakob Borg
0e67c036bb lib/db: Make database GC a service, stop on Stop() (#6518)
This makes the GC runner a service that will stop fairly quickly when
told to.

As a bonus, STTRACE=app will print the service tree on the way out,
including any errors they've flagged.
2020-04-12 10:26:57 +02:00
Jakob Borg
046bbdfbd4 Merge branch 'release'
* release:
  lib/db: Don't get blocklists on drop and missing continue (ref #6457) (#6502)
  Revert "cmd/syncthing: Do auto-upgrade before startup (fixes #6384) (#6385)"
  lib/ur: Correct freaky error handling (fixes #6499) (#6500)
2020-04-08 10:49:59 +02:00
Jakob Borg
c6c74e8291 gui, man, authors: Update docs, translations, and contributors 2020-04-08 07:45:32 +02:00
Jakob Borg
59b1b0e1dc lib/osutil: Fix "atomic" rename on Windows (ref #6495, ref #6493) (#6510)
So, in a funny plot twist, it turns out that WriteFile in Go 1.13
doesn't actually set the read only bit on Windows when called with
permissions 0444 so my test was broken. With an improved test it turns
out that Rename does not, in fact, overwrite a read-only file on
Windows. This adds a fix for that.

(Rename might get improved in Go 1.15...)
2020-04-07 15:38:55 +02:00
Jakob Borg
4b17c511f9 cmd/stcrashreceiver: Enable (rough) affected users count (#6511)
Seeing thousands of reports is no use when we don't know if they
represent one poor user or thousands.
2020-04-07 13:19:49 +02:00
Simon Frei
df318ed370 lib/db: Don't get blocklists on drop and missing continue (ref #6457) (#6502) 2020-04-07 13:13:18 +02:00
Simon Frei
07ce3572a0 lib/ignore: Only skip for toplevel includes (fixes #6487) (#6508) 2020-04-07 10:23:38 +02:00
Jakob Borg
b64052bc26 Revert "build: Go 1.14 for the Debian etc builds"
This reverts commit d400e51422.
2020-04-07 09:37:39 +02:00
Jakob Borg
7956e7d0ef lib/{fs,scanner}: gofmt from Go 1.14 (#6509) 2020-04-07 09:31:29 +02:00
Jakob Borg
d400e51422 build: Go 1.14 for the Debian etc builds 2020-04-07 08:22:53 +02:00
greatroar
674a99e9ae cmd/strelaypoolsrv: Simplify LRU usage (#6507) 2020-04-06 12:43:56 +02:00
Jakob Borg
88cabb9e0a lib/ur: Correct freaky error handling (fixes #6499) (#6500) 2020-04-06 09:53:37 +02:00
greatroar
b7ba401c0b cmd/strelaypoolsrv: Fix race condition in caching (#6496)
Successful LRU cache lookups modify the cache's recency list, so
RWMutex.RLock isn't enough protection.

Secondarily, multiple concurrent lookups with the same key should not
create separate rate limiters, so release the lock only when presence
of the key in the cache has been ascertained.

Co-authored-by: greatroar <@>
2020-04-04 20:20:25 +01:00
Jakob Borg
7505ea79a0 lib/osutil: Don't remove before rename on Windows (ref #6493) (#6495)
This was needed in ancient times but not currently.
2020-04-04 14:17:16 +02:00
Kevin Bushiri
e1324a0e23 cmd/strelaypoolsrv: Use OpenStreetMap (fixes #6150) (#6459) 2020-04-04 13:48:20 +02:00
Jakob Borg
b7b9476e5a cmd/strelaysrv: Harmonize and improve log output (ref #6492) 2020-04-04 13:31:42 +02:00
Jakob Borg
d1db7e3dd2 cmd/strelaypoolsrv: Configurable request processors & queue len 2020-04-04 13:31:42 +02:00
Jakob Borg
362da59396 cmd/strelaypoolsrv: Expose check error to client, fix incorrect response code handling 2020-04-04 13:31:42 +02:00
Jakob Borg
66262392c3 cmd/strelaypoolsrv: Correctly account status codes, tweak status codes 2020-04-04 13:31:42 +02:00
Jakob Borg
48f9d323fa lib/api: Add LDAP search filters (fixes #5376) (#6488)
This adds the functionality to run a user search with a filter for LDAP
authentication. The search is done after successful bind, as the binding
user. The typical use case is to limit authentication to users who are
member of a group or under a certain OU. For example, to only match
users in the "Syncthing" group in otherwise default Active Directory
set up for example.com:

    <searchBaseDN>CN=Users,DC=example,DC=com</searchBaseDN>
    <searchFilter>(&amp;(sAMAccountName=%s)(memberOf=CN=Syncthing,CN=Users,DC=example,DC=com))</searchFilter>

The search filter is an "and" of two criteria (with the ampersand being
XML quoted),

- "(sAMAccountName=%s)" matches the user logging in
- "(memberOf=CN=Syncthing,CN=Users,DC=example,DC=com)" matches members
  of the group in question.

Authentication will only proceed if the search filter matches precisely
one user.
2020-04-04 11:33:43 +02:00
Simon Frei
f69c0b550c gui: Refactor out-of-sync modal (#6452) 2020-04-02 16:18:41 +02:00
Simon Frei
32245435e2 lib/model: Handle deleted items on RO for remote removes (fixes #6432) (#6464) 2020-04-02 16:14:25 +02:00
Jakob Borg
2658369051 gui: Expose LDAP config in advanced config editor (#6489)
One tiny step friendlier than vi on config.xml
2020-04-02 12:07:17 +02:00
Simon Mwepu
d50adb225b gui: Avoid validation error on closing folder editor (fixes #3808) (#6478) 2020-04-02 08:20:03 +02:00
Jakob Borg
7da898f2d6 go.mod: Use github.com/twmb/murmur3 for murmur3 (#6486)
Let's try this again shall we
2020-04-01 23:51:31 +02:00
Jakob Borg
0d919bd79c Revert "go.mod: Use github.com/twmb/murmur3 for murmur3"
"I shall not commit to master without testing all architectures on the
builder" * 100 on the black board
2020-04-01 21:16:15 +02:00
Jakob Borg
f91e90a94f go.mod: Use github.com/twmb/murmur3 for murmur3
It seems, like, maintained and stuff.
2020-04-01 21:04:43 +02:00
Jakob Borg
7ce20f197b gui, man, authors: Update docs, translations, and contributors 2020-04-01 07:45:33 +02:00
greatroar
2f26a95973 lib/db, lib/model: Code simplifications (#6484)
NamespacedKV.prefixedKey is still small enough to be inlined.
2020-03-31 14:32:24 +02:00
Simon Frei
123941cf62 lib/fs: Basic with empty root shouldn't point at / (#6483) 2020-03-31 13:56:07 +02:00
Jakob Borg
9c67d57c28 lib/api: Update ldap package (fixes #6479) (#6481) 2020-03-31 09:56:04 +02:00
mv1005
5b3466dc6e lib/versioner: Extended tests of intervals (#6462) 2020-03-31 00:14:05 +02:00
Michael Rienstra
bca6854c03 lib/versioner: Fix "30 days" interval (fixes #6410) (#6461) 2020-03-30 23:28:53 +02:00
greatroar
c930b2e9e2 lib/rand: Various fixes (#6474) 2020-03-30 23:26:28 +02:00
greatroar
d7a257b391 lib/util: Fix potential data race (#6477)
Co-authored-by: greatroar <@>
2020-03-30 14:10:08 +01:00
Alberto Donato
7709ac33a7 go.mod: Update jackpal/gateway dependency (fixes #5288) (#6469) 2020-03-30 11:11:55 +02:00
greatroar
1e2379df1b lib/protocol: faster Luhn algorithm and better testing (#6475)
The previous implementation was very generic; its tests didn't cover the
actual alphabet for device IDs.

Benchmark results on amd64:

name         old time/op    new time/op     delta
Luhnify-8      1.00µs ± 1%     0.28µs ± 4%   -72.38%  (p=0.000 n=9+10)
Unluhnify-8     992ns ± 2%      274ns ± 1%   -72.39%  (p=0.000 n=10+9)
2020-03-29 22:28:04 +02:00
greatroar
ea5c9176e1 lib/protocol: Remove unused channel Connection.preventSends (#6473)
Co-authored-by: greatroar <@>
2020-03-29 17:09:53 +01:00
greatroar
cc1b003f21 lib/weakhash: Fix speed reporting in benchmark (#6470) 2020-03-29 17:07:25 +02:00
Jakob Borg
38bd90e6f2 build: Simplify/correct Windows version tagging (fixes #6471) (#6472) 2020-03-29 16:51:50 +02:00
greatroar
1c47fae206 lib/ur: Use sysctl syscall to get RAM size on Mac (#6468) 2020-03-29 14:28:46 +02:00
Simon Frei
79a758be3c lib/model: Do Revert/Override synchronously (#6460) 2020-03-27 13:05:09 +01:00
Simon Frei
c7cf3ef899 lib/syncthing: Save version to db after upgrade ops are done (ref #6457) (#6458) 2020-03-26 16:58:21 +01:00
Jakob Borg
2c2e6cd0d5 cmd/ursrv: Minor heatmap tweaks 2020-03-26 15:19:05 +01:00
Simon Frei
b7dffc051e lib/model: Remove unused func (#6456) 2020-03-26 14:19:26 +01:00
Kevin Bushiri
963e9a4071 cmd/ursrv: Use OpenStreetMap and Leaflet for heat map (ref #6150) (#6454) 2020-03-26 12:32:14 +01:00
Jakob Borg
b28899ac07 cmd/ursrv: Provide cached locations.json 2020-03-25 14:19:35 +01:00
Jakob Borg
83f6da8dca authors: Fixup keevBush 2020-03-25 10:03:23 +01:00
Jakob Borg
1a29296d9d gui, man, authors: Update docs, translations, and contributors 2020-03-25 07:45:35 +01:00
Simon Frei
a7de4c68e3 go.mod: Update quic-go to 0.14.4 (#6453) 2020-03-24 21:12:57 +01:00
Simon Frei
7f23de4f03 all: Pass db intervals as args not env vars (#6448) 2020-03-24 13:53:20 +01:00
Jakob Borg
ca89f12be6 lib/api: Set ServerName on LDAPS connections (fixes #6450) (#6451)
tls.Dial needs it for certificate verification.
2020-03-24 12:56:43 +01:00
Simon Frei
ddfa82e990 lib/model: Unset local flag on deleted files (fixes #6436) (#6449) 2020-03-24 12:51:17 +01:00
Nicolas Perraut
61302c467c gui: Improve unused device status (fixes #6416) (#6445) 2020-03-22 19:30:18 +01:00
Simon Frei
d6b4873eed gui, lib/model: Fix for folder stats with r-o and ignoreDel (fixes #6430) (#6431) 2020-03-22 11:46:42 +01:00
Jakob Borg
1ea98a16b1 cmd/syncthing: Don't open browser on upgrade restarts (fixes #6437) (#6442)
We set the STRESTART environment when starting the inner process after
the first time, but this didn't persist when restarting the monitor
process. Now it does.
2020-03-22 11:39:09 +01:00
Jakob Borg
e2f3500df9 cmd/syncthing: Properly handle STNORESTART=1 (fixes #6440) (#6441)
Makes it truly equivalent to -no-restart, and also updates the option
descriptions to be more truthful.
2020-03-22 11:38:53 +01:00
Jakob Borg
8b025af1e5 Merge branch 'release'
* release:
  lib/db: Don't whack blocks when putting truncated file (#6434)
2020-03-20 12:08:16 +01:00
Jakob Borg
c4abe6f815 lib/db: Don't whack blocks when putting truncated file (#6434)
As of the latest database checker we are again putting files without
blocks. I'm not 100% convinced that's a great idea, but we also do it
for ignored files apparently so it looks like we probably should support
it. This adds an escape hatch that must be manually enabled...
2020-03-20 12:07:14 +01:00
Jakob Borg
4c5e9cf921 Merge branch 'release'
* release:
  lib/db, lib/syncthing: Repair db once on upgrade (ref #6425, #6427) (#6429)
  lib/db: Fix removeFromGlobal and no filenames in error (fixes #6427) (#6428)
  lib/db: Remove emptied global list in checkGlobals (fixes #6425) (#6426)
2020-03-19 16:05:39 +01:00
Simon Frei
74706bb02b lib/db, lib/syncthing: Repair db once on upgrade (ref #6425, #6427) (#6429) 2020-03-19 15:58:32 +01:00
Kevin Bushiri
5975772ed8 cmd/stdiscosrv: Only generate keypair if it doesn't exist (fixes #5809) (#6419) 2020-03-19 14:50:24 +01:00
Simon Frei
cf11fa4327 lib/db: Fix removeFromGlobal and no filenames in error (fixes #6427) (#6428) 2020-03-19 14:32:22 +01:00
Simon Frei
40580d8b9b lib/db: Remove emptied global list in checkGlobals (fixes #6425) (#6426) 2020-03-19 14:30:20 +01:00
Simon Frei
e25e71cdde cmd/syncthing, lib/locations: Separate data and config dirs (fixes #4924) (#6309) 2020-03-18 20:58:11 +01:00
107 changed files with 1540 additions and 903 deletions

View File

@@ -17,6 +17,7 @@ Aaron Bieber (qbit) <qbit@deftly.net>
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com> <adam@proactiveservices.co.uk>
Adel Qalieh (adelq) <aqalieh95@gmail.com> <adelq@users.noreply.github.com>
Alan Pope <alan@popey.com>
Alberto Donato <albertodonato@users.noreply.github.com>
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
Alex Xu <alex.hello71@gmail.com>
Alexander Graf (alex2108) <register-github@alex-graf.de>
@@ -95,6 +96,7 @@ georgespatton <georgespatton@users.noreply.github.com>
ghjklw <malo@jaffre.info>
Gilli Sigurdsson (gillisig) <gilli@vx.is>
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
greatroar <61184462+greatroar@users.noreply.github.com>
Han Boetes <han@boetes.org>
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
@@ -130,6 +132,7 @@ Keith Turner <kturner@apache.org>
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
Kevin Allen (ironmig) <kma1660@gmail.com>
Kevin Bushiri (keevBush) <keevbush@gmail.com> <36192217+keevBush@users.noreply.github.com>
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
klemens <ka7@github.com>
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
@@ -147,6 +150,7 @@ Marc Pujol (kilburn) <kilburn@la3.org>
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
marco-m <marco.molteni@laposte.net>
Marcus Legendre <marcus.legendre@gmail.com>
Mario Majila <mariustshipichik@gmail.com>
Mark Pulford (mpx) <mark@kyne.com.au>
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
Mateusz Ż <thedead4fun@live.com>
@@ -160,14 +164,17 @@ MaximAL <almaximal@ya.ru>
Maxime Thirouin <m@moox.io>
Michael Jephcote (Rewt0r) <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Ploujnikov (plouj) <ploujj@gmail.com>
Michael Rienstra <mrienstra@gmail.com>
Michael Tilli (pyfisch) <pyfisch@gmail.com>
Mike Boone <mike@boonedocks.net>
MikeLund <MikeLund@users.noreply.github.com>
Mingxuan Lin <gdlmx@users.noreply.github.com>
mv1005 <49659413+mv1005@users.noreply.github.com>
Nate Morrison (nrm21) <natemorrison@gmail.com>
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
Nico Stapelbroek <3368018+nstapelbroek@users.noreply.github.com>
Nicolas Braud-Santoni <nicolas@braud-santoni.eu>
Nicolas Perraut <n.perraut@gmail.com>
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
Nils Jakobi (thunderstorm99) <jakobi.nils@gmail.com>
Nitroretro <43112364+Nitroretro@users.noreply.github.com>

View File

@@ -631,20 +631,27 @@ func buildSnap(target target) {
func shouldBuildSyso(dir string) (string, error) {
type M map[string]interface{}
major, minor, patch, build := semanticVersion()
version := getVersion()
version = strings.TrimPrefix(version, "v")
major, minor, patch := semanticVersion()
bs, err := json.Marshal(M{
"FixedFileInfo": M{
"FileVersion": M{
"Major": major,
"Minor": minor,
"Patch": patch,
"Build": build,
},
"ProductVersion": M{
"Major": major,
"Minor": minor,
"Patch": patch,
},
},
"StringFileInfo": M{
"FileDescription": "Open Source Continuous File Synchronization",
"LegalCopyright": "The Syncthing Authors",
"ProductVersion": getVersion(),
"FileVersion": version,
"ProductVersion": version,
"ProductName": "Syncthing",
},
"IconPath": "assets/logo.ico",
@@ -864,22 +871,18 @@ func getVersion() string {
return "unknown-dev"
}
func semanticVersion() (major, minor, patch, build int) {
r := regexp.MustCompile(`v(?P<Major>\d+)\.(?P<Minor>\d+).(?P<Patch>\d+).*\+(?P<CommitsAhead>\d+)`)
func semanticVersion() (major, minor, patch int) {
r := regexp.MustCompile(`v(\d+)\.(\d+).(\d+)`)
matches := r.FindStringSubmatch(getVersion())
if len(matches) != 5 {
return 0, 0, 0, 0
if len(matches) != 4 {
return 0, 0, 0
}
var ints [4]int
for i := 1; i < 5; i++ {
value, err := strconv.Atoi(matches[i])
if err != nil {
return 0, 0, 0, 0
}
ints[i-1] = value
var ints [3]int
for i, s := range matches[1:] {
ints[i], _ = strconv.Atoi(s)
}
return ints[0], ints[1], ints[2], ints[3]
return ints[0], ints[1], ints[2]
}
func getBranchSuffix() string {

View File

@@ -31,12 +31,14 @@ var (
clientsMut sync.Mutex
)
func sendReport(dsn, path string, report []byte) error {
func sendReport(dsn, path string, report []byte, userID string) error {
pkt, err := parseReport(path, report)
if err != nil {
return err
}
pkt.Interfaces = append(pkt.Interfaces, &raven.User{ID: userID})
clientsMut.Lock()
defer clientsMut.Unlock()

View File

@@ -16,14 +16,19 @@ import (
"bytes"
"compress/gzip"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/syncthing/syncthing/lib/sha256"
)
const maxRequestSize = 1 << 20 // 1 MiB
@@ -145,9 +150,12 @@ func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWrite
// Send the report to Sentry
if r.dsn != "" {
// Remote ID
user := userIDFor(req)
go func() {
// There's no need for the client to have to wait for this part.
if err := sendReport(r.dsn, reportID, bs); err != nil {
if err := sendReport(r.dsn, reportID, bs, user); err != nil {
log.Println("Failed to send report:", err)
}
}()
@@ -158,3 +166,20 @@ func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWrite
func (r *crashReceiver) dirFor(base string) string {
return filepath.Join(base[0:2], base[2:4])
}
// userIDFor returns a string we can use as the user ID for the purpose of
// counting affected users. It's the truncated hash of a salt, the user
// remote IP, and the current month.
func userIDFor(req *http.Request) string {
addr := req.RemoteAddr
if fwd := req.Header.Get("x-forwarded-for"); fwd != "" {
addr = fwd
}
if host, _, err := net.SplitHostPort(addr); err == nil {
addr = host
}
now := time.Now().Format("200601")
salt := "stcrashreporter"
hash := sha256.Sum256([]byte(salt + addr + now))
return fmt.Sprintf("%x", hash[:8])
}

View File

@@ -98,14 +98,15 @@ func main() {
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
if os.IsNotExist(err) {
log.Println("Failed to load keypair. Generating one, this might take a while...")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv", 20*365)
if err != nil {
log.Fatalln("Failed to generate X509 key pair:", err)
}
} else if err != nil {
log.Fatalln("Failed to load keypair:", err)
}
devID := protocol.NewDeviceID(cert.Certificate[0])
log.Println("Server device ID is", devID)

View File

@@ -9,8 +9,15 @@
<meta name="author" content=""/>
<title>Relay stats</title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.0.13/css/all.css"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"/>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"/>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin=""></script>
<style>
#map {
@@ -38,7 +45,7 @@
<div class="container">
<h1>Relay Pool Data</h1>
<div ng-if="relays === undefined" class="text-center">
<img src="//cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif" alt=""/>
<img src="https://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif" alt=""/>
<p>Please wait while we gather data</p>
</div>
<div>
@@ -184,10 +191,9 @@
</div>
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?key=AIzaSyDk5WJ8s7ueLKb99X5DbQ-vkWtPDAKqYs0"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</body>
<script>
@@ -228,11 +234,12 @@
numProxies: 0,
uptimeSeconds: 0,
};
$scope.map = new google.maps.Map(document.getElementById('map'), {
zoom: 1,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
$scope.mapBounds = new google.maps.LatLngBounds();
$scope.map = L.map('map').setView([40.90296, 1.90925], 2);
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
attribution: 'Leaflet',
maxZoom: 17
}).addTo($scope.map);
$scope.tooltipTemplate = $('#infoTemplate').html();
$scope.usedLocations = {};
$scope.sortType = 'stats.numActiveSessions';
@@ -279,8 +286,9 @@
}
});
$scope.map.fitBounds($scope.mapBounds);
if ($scope.relays.length == 1) {
//Center to only relay with zoom
$scope.map.panTo(new L.LatLng(relays[0].location.latitude, relays[0].location.longitude));
$scope.map.setZoom(13);
}
});
@@ -300,44 +308,50 @@
var locParts = loc.split(',');
relay.marker = new google.maps.Marker({
map: $scope.map,
position: new google.maps.LatLng(locParts[0], locParts[1]),
relay.marker = new L.Marker([relay.location.latitude, relay.location.longitude],{
title: relay.url,
});
var scope = $rootScope.$new(true);
scope.relay = relay;
relay.marker.info = new google.maps.InfoWindow({
content: $compile($scope.tooltipTemplate)(scope)[0],
var icon = new L.Icon({
iconSize: [18, 28], // size of the icon
iconAnchor: [9, 28], // point of the icon which will correspond to marker's location
shadowAnchor: [0, 0], // the same for the shadow
popupAnchor: [0, -27], // popup anchor
shadowSize: [0,0],
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
});
relay.marker = new L.marker(new L.latLng(locParts[0], locParts[1]),{icon})
.bindPopup($compile($scope.tooltipTemplate)(scope)[0],{})
.on('mouseover', function (e) {
this.openPopup();
}).on('mouseout', function (e) {
this.closePopup();
}).addTo($scope.map);
relay.showMarker = function() {
relay.marker.info.open($scope.map, relay.marker);
relay.marker.openPopup();
}
relay.hideMarker = function() {
relay.marker.info.close();
relay.marker.closePopup();
}
}
relay.marker.addListener('mouseover', relay.showMarker);
relay.marker.addListener('mouseout', relay.hideMarker);
$scope.mapBounds.extend(relay.marker.position);
}
function addCircleToMap(relay) {
relay.marker.circle = new google.maps.Circle({
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
map: $scope.map,
center: relay.marker.position,
radius: ((relay.stats.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000
});
console.log(relay.location.latitude)
L.circle([relay.location.latitude, relay.location.longitude],
{
radius: ((relay.stats.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000,
color: "FF0000",
fillColor: "#FF0000",
fillOpacity: 0.35,
}).addTo($scope.map);
}
function constructURI(url) {

View File

@@ -10,7 +10,6 @@ import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
@@ -95,33 +94,35 @@ type result struct {
}
var (
testCert tls.Certificate
knownRelaysFile = filepath.Join(os.TempDir(), "strelaypoolsrv_known_relays")
listen = ":80"
dir string
evictionTime = time.Hour
debug bool
getLRUSize = 10 << 10
getLimitBurst = 10
getLimitAvg = 2
postLRUSize = 1 << 10
postLimitBurst = 2
postLimitAvg = 2
getLimit time.Duration
postLimit time.Duration
permRelaysFile string
ipHeader string
geoipPath string
proto string
statsRefresh = time.Minute / 2
testCert tls.Certificate
knownRelaysFile = filepath.Join(os.TempDir(), "strelaypoolsrv_known_relays")
listen = ":80"
dir string
evictionTime = time.Hour
debug bool
getLRUSize = 10 << 10
getLimitBurst = 10
getLimitAvg = 2
postLRUSize = 1 << 10
postLimitBurst = 2
postLimitAvg = 2
getLimit time.Duration
postLimit time.Duration
permRelaysFile string
ipHeader string
geoipPath string
proto string
statsRefresh = time.Minute / 2
requestQueueLen = 10
requestProcessors = 1
getMut = sync.NewRWMutex()
getMut = sync.NewMutex()
getLRUCache *lru.Cache
postMut = sync.NewRWMutex()
postMut = sync.NewMutex()
postLRUCache *lru.Cache
requests = make(chan request, 10)
requests chan request
mut = sync.NewRWMutex()
knownRelays = make([]*relay, 0)
@@ -134,6 +135,9 @@ const (
)
func main() {
log.SetOutput(os.Stdout)
log.SetFlags(log.Lshortfile)
flag.StringVar(&listen, "listen", listen, "Listen address")
flag.StringVar(&dir, "keys", dir, "Directory where http-cert.pem and http-key.pem is stored for TLS listening")
flag.BoolVar(&debug, "debug", debug, "Enable debug output")
@@ -149,9 +153,13 @@ func main() {
flag.StringVar(&geoipPath, "geoip", "GeoLite2-City.mmdb", "Path to GeoLite2-City database")
flag.StringVar(&proto, "protocol", "tcp", "Protocol used for listening. 'tcp' for IPv4 and IPv6, 'tcp4' for IPv4, 'tcp6' for IPv6")
flag.DurationVar(&statsRefresh, "stats-refresh", statsRefresh, "Interval at which to refresh relay stats")
flag.IntVar(&requestQueueLen, "request-queue", requestQueueLen, "Queue length for incoming test requests")
flag.IntVar(&requestProcessors, "request-processors", requestProcessors, "Number of request processor routines")
flag.Parse()
requests = make(chan request, requestQueueLen)
getLimit = 10 * time.Second / time.Duration(getLimitAvg)
postLimit = time.Minute / time.Duration(postLimitAvg)
@@ -167,7 +175,9 @@ func main() {
testCert = createTestCertificate()
go requestProcessor()
for i := 0; i < requestProcessors; i++ {
go requestProcessor()
}
// Load relays from cache in the background.
// Load them in a serial fashion to make sure any genuine requests
@@ -334,10 +344,10 @@ func mimeTypeForFile(file string) string {
func handleRequest(w http.ResponseWriter, r *http.Request) {
timer := prometheus.NewTimer(apiRequestsSeconds.WithLabelValues(r.Method))
lw := NewLoggingResponseWriter(w)
w = NewLoggingResponseWriter(w)
defer func() {
timer.ObserveDuration()
lw := w.(*loggingResponseWriter)
apiRequestsTotal.WithLabelValues(r.Method, strconv.Itoa(lw.statusCode)).Inc()
}()
@@ -397,7 +407,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
if debug {
log.Println("Failed to parse payload")
}
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
@@ -406,7 +416,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
if debug {
log.Println("Failed to parse URI", newRelay.URL)
}
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
@@ -415,7 +425,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
if debug {
log.Println("Failed to split URI", newRelay.URL)
}
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
@@ -491,11 +501,11 @@ func handleRelayTest(request request) {
if debug {
log.Println("Request for", request.relay)
}
if !client.TestRelay(context.TODO(), request.relay.uri, []tls.Certificate{testCert}, time.Second, 2*time.Second, 3) {
if err := client.TestRelay(context.TODO(), request.relay.uri, []tls.Certificate{testCert}, time.Second, 2*time.Second, 3); err != nil {
if debug {
log.Println("Test for relay", request.relay, "failed")
log.Println("Test for relay", request.relay, "failed:", err)
}
request.result <- result{errors.New("connection test failed"), 0}
request.result <- result{err, 0}
return
}
@@ -573,26 +583,21 @@ func evict(relay *relay) func() {
}
}
func limit(addr string, cache *lru.Cache, lock sync.RWMutex, intv time.Duration, burst int) bool {
func limit(addr string, cache *lru.Cache, lock sync.Mutex, intv time.Duration, burst int) bool {
if host, _, err := net.SplitHostPort(addr); err == nil {
addr = host
}
lock.RLock()
bkt, ok := cache.Get(addr)
lock.RUnlock()
if ok {
bkt := bkt.(*rate.Limiter)
if !bkt.Allow() {
// Rate limit
return true
}
} else {
lock.Lock()
cache.Add(addr, rate.NewLimiter(rate.Every(intv), burst))
lock.Unlock()
lock.Lock()
v, _ := cache.Get(addr)
bkt, ok := v.(*rate.Limiter)
if !ok {
bkt = rate.NewLimiter(rate.Every(intv), burst)
cache.Add(addr, bkt)
}
return false
lock.Unlock()
return !bkt.Allow()
}
func loadRelays(file string) []*relay {

View File

@@ -7,10 +7,15 @@ import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)
const (
httpStatusEnhanceYourCalm = 429
)
func poolHandler(pool string, uri *url.URL, mapping mapping) {
if debug {
log.Println("Joining", pool)
@@ -28,38 +33,62 @@ func poolHandler(pool string, uri *url.URL, mapping mapping) {
resp, err := httpClient.Post(pool, "application/json", &b)
if err != nil {
log.Println("Error joining pool", pool, err)
} else if resp.StatusCode == 500 {
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("Failed to join", pool, "due to an internal server error. Could not read response:", err)
} else {
log.Println("Failed to join", pool, "due to an internal server error:", string(bs))
}
resp.Body.Close()
} else if resp.StatusCode == 429 {
log.Println(pool, "under load, will retry in a minute")
log.Printf("Error joining pool %v: HTTP request: %v", pool, err)
time.Sleep(time.Minute)
continue
} else if resp.StatusCode == 401 {
log.Println(pool, "failed to join due to IP address not matching external address. Aborting")
return
} else if resp.StatusCode == 200 {
}
bs, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
log.Printf("Error joining pool %v: reading response: %v", pool, err)
time.Sleep(time.Minute)
continue
}
switch resp.StatusCode {
case http.StatusOK:
var x struct {
EvictionIn time.Duration `json:"evictionIn"`
}
err := json.NewDecoder(resp.Body).Decode(&x)
if err == nil {
if err := json.NewDecoder(resp.Body).Decode(&x); err == nil {
rejoin := x.EvictionIn - (x.EvictionIn / 5)
log.Println("Joined", pool, "rejoining in", rejoin)
log.Printf("Joined pool %s, rejoining in %v", pool, rejoin)
time.Sleep(rejoin)
continue
} else {
log.Println("Failed to deserialize response", err)
log.Printf("Joined pool %s, failed to deserialize response: %v", pool, err)
}
} else {
log.Println(pool, "unknown response type from server", resp.StatusCode)
case http.StatusInternalServerError:
log.Printf("Failed to join %v: server error", pool)
log.Printf("Response data: %s", bs)
time.Sleep(time.Minute)
continue
case http.StatusBadRequest:
log.Printf("Failed to join %v: request or check error", pool)
log.Printf("Response data: %s", bs)
time.Sleep(time.Minute)
continue
case httpStatusEnhanceYourCalm:
log.Printf("Failed to join %v: under load (rate limiting)", pool)
time.Sleep(time.Minute)
continue
case http.StatusUnauthorized:
log.Printf("Failed to join %v: IP address not matching external address", pool)
log.Println("Aborting")
return
default:
log.Printf("Failed to join %v: unexpected status code from server: %d", pool, resp.StatusCode)
log.Printf("Response data: %s", bs)
time.Sleep(time.Minute)
continue
}
time.Sleep(time.Hour)
}
}

View File

@@ -107,10 +107,10 @@ func main() {
connectToStdio(stdin, conn)
log.Println("Finished", conn.RemoteAddr(), conn.LocalAddr())
} else if test {
if client.TestRelay(ctx, uri, []tls.Certificate{cert}, time.Second, 2*time.Second, 4) {
if err := client.TestRelay(ctx, uri, []tls.Certificate{cert}, time.Second, 2*time.Second, 4); err == nil {
log.Println("OK")
} else {
log.Println("FAIL")
log.Println("FAIL:", err)
}
} else {
log.Fatal("Requires either join or connect")

View File

@@ -30,6 +30,7 @@ import (
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
@@ -65,6 +66,12 @@ I.e. to prefix each log line with date and time, set -logflags=3 (1 + 2 from
above). The value 0 is used to disable all of the above. The default is to
show time only (2).
Logging always happens to the command line (stdout) and optionally to the
file at the path specified by -logfile=path. In addition to an path, the special
values "default" and "-" may be used. The former logs to DATADIR/syncthing.log
(see -data-dir), which is the default on Windows, and the latter only to stdout,
no file, which is the default anywhere else.
Development Settings
--------------------
@@ -102,10 +109,7 @@ are mostly useful for developers. Use with care.
STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
STNORESTART Equivalent to the -no-restart argument. Disable the
Syncthing monitor process which handles restarts for some
configuration changes, upgrades, crashes and also log file
writing (stdout is still written).
STNORESTART Equivalent to the -no-restart argument.
STNOUPGRADE Disable automatic upgrades.
@@ -139,15 +143,27 @@ The following are valid values for the STTRACE variable:
%s`
)
// Environment options
var (
innerProcess = os.Getenv("STNORESTART") != "" || os.Getenv("STMONITORED") != ""
// Environment options
innerProcess = os.Getenv("STMONITORED") != ""
noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != ""
upgradeCheckInterval = 5 * time.Minute
upgradeRetryInterval = time.Hour
upgradeCheckKey = "lastUpgradeCheck"
upgradeTimeKey = "lastUpgradeTime"
upgradeVersionKey = "lastUpgradeVersion"
errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance")
errTooEarlyUpgradeCheck = fmt.Errorf("last upgrade check happened less than %v ago, skipping", upgradeCheckInterval)
errTooEarlyUpgrade = fmt.Errorf("last upgrade happened less than %v ago, skipping", upgradeRetryInterval)
)
type RuntimeOptions struct {
syncthing.Options
homeDir string
confDir string
dataDir string
resetDatabase bool
showVersion bool
showPaths bool
@@ -195,11 +211,13 @@ func defaultRuntimeOptions() RuntimeOptions {
options.logFlags = logger.DebugFlags
}
if runtime.GOOS != "windows" {
// On non-Windows, we explicitly default to "-" which means stdout. On
// Windows, the blank options.logFile will later be replaced with the
// default path, unless the user has manually specified "-" or
// something else.
// On non-Windows, we explicitly default to "-" which means stdout. On
// Windows, the "default" options.logFile will later be replaced with the
// default path, unless the user has manually specified "-" or
// something else.
if runtime.GOOS == "windows" {
options.logFile = "default"
} else {
options.logFile = "-"
}
@@ -212,11 +230,13 @@ func parseCommandLineOptions() RuntimeOptions {
flag.StringVar(&options.generateDir, "generate", "", "Generate key and config in specified dir, then exit")
flag.StringVar(&options.guiAddress, "gui-address", options.guiAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
flag.StringVar(&options.guiAPIKey, "gui-apikey", options.guiAPIKey, "Override GUI API key")
flag.StringVar(&options.confDir, "home", "", "Set configuration directory")
flag.StringVar(&options.homeDir, "home", "", "Set configuration and data directory")
flag.StringVar(&options.confDir, "config", "", "Set configuration directory (config and keys)")
flag.StringVar(&options.dataDir, "data", "", "Set data directory (database and logs)")
flag.IntVar(&options.logFlags, "logflags", options.logFlags, "Select information in log line prefix (see below)")
flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Disable monitor process, managed restarts and log file writing")
flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash")
flag.BoolVar(&options.resetDatabase, "reset-database", false, "Reset the database, forcing a full rescan and resync")
flag.BoolVar(&options.ResetDeltaIdxs, "reset-deltas", false, "Reset delta index IDs, forcing a full index exchange")
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
@@ -230,7 +250,7 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.Verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout).")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (see below).")
flag.IntVar(&options.logMaxSize, "log-max-size", options.logMaxSize, "Maximum size of any file (zero to disable log rotation).")
flag.IntVar(&options.logMaxFiles, "log-max-old-files", options.logMaxFiles, "Number of old files to keep (zero to keep only current).")
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
@@ -252,6 +272,17 @@ func parseCommandLineOptions() RuntimeOptions {
return options
}
func setLocation(enum locations.BaseDirEnum, loc string) error {
if !filepath.IsAbs(loc) {
var err error
loc, err = filepath.Abs(loc)
if err != nil {
return err
}
}
return locations.SetBaseDir(enum, loc)
}
func main() {
options := parseCommandLineOptions()
l.SetFlags(options.logFlags)
@@ -269,25 +300,33 @@ func main() {
osutil.HideConsole()
}
if options.confDir != "" {
// Not set as default above because the string can be really long.
if !filepath.IsAbs(options.confDir) {
var err error
options.confDir, err = filepath.Abs(options.confDir)
if err != nil {
l.Warnln("Failed to make options path absolute:", err)
os.Exit(syncthing.ExitError.AsInt())
}
// Not set as default above because the strings can be really long.
var err error
homeSet := options.homeDir != ""
confSet := options.confDir != ""
dataSet := options.dataDir != ""
switch {
case dataSet != confSet:
err = errors.New("either both or none of -conf and -data must be given, use -home to set both at once")
case homeSet && dataSet:
err = errors.New("-home must not be used together with -conf and -data")
case homeSet:
if err = setLocation(locations.ConfigBaseDir, options.homeDir); err == nil {
err = setLocation(locations.DataBaseDir, options.homeDir)
}
if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil {
l.Warnln(err)
os.Exit(syncthing.ExitError.AsInt())
case dataSet:
if err = setLocation(locations.ConfigBaseDir, options.confDir); err == nil {
err = setLocation(locations.DataBaseDir, options.dataDir)
}
}
if err != nil {
l.Warnln("Command line options:", err)
os.Exit(syncthing.ExitError.AsInt())
}
if options.logFile == "" {
// Blank means use the default logfile location. We must set this
// *after* expandLocations above.
if options.logFile == "default" || options.logFile == "" {
// We must set this *after* expandLocations above.
// Handling an empty value is for backwards compatibility (<1.4.1).
options.logFile = locations.Get(locations.LogFile)
}
@@ -359,14 +398,31 @@ func main() {
}
if options.doUpgradeCheck {
checkUpgrade()
if _, err := checkUpgrade(); err != nil {
l.Warnln("Checking for upgrade:", err)
os.Exit(exitCodeForUpgrade(err))
}
return
}
if options.doUpgrade {
release := checkUpgrade()
performUpgrade(release)
return
release, err := checkUpgrade()
if err == nil {
// Use leveldb database locks to protect against concurrent upgrades
ldb, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
if err != nil {
err = upgradeViaRest()
} else {
_ = ldb.Close()
err = upgrade.To(release)
}
}
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitCodeForUpgrade(err))
}
l.Infof("Upgraded to %q", release.Tag)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
if options.resetDatabase {
@@ -462,45 +518,28 @@ func debugFacilities() string {
return b.String()
}
func checkUpgrade() upgrade.Release {
type errNoUpgrade struct {
current, latest string
}
func (e errNoUpgrade) Error() string {
return fmt.Sprintf("no upgrade available (current %q >= latest %q).", e.current, e.latest)
}
func checkUpgrade() (upgrade.Release, error) {
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
opts := cfg.Options()
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(syncthing.ExitError.AsInt())
return upgrade.Release{}, err
}
if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
noUpgradeMessage := "No upgrade available (current %q >= latest %q)."
l.Infof(noUpgradeMessage, build.Version, release.Tag)
os.Exit(syncthing.ExitNoUpgradeAvailable.AsInt())
return upgrade.Release{}, errNoUpgrade{build.Version, release.Tag}
}
l.Infof("Upgrade available (current %q < latest %q)", build.Version, release.Tag)
return release
}
func performUpgrade(release upgrade.Release) {
// Use leveldb database locks to protect against concurrent upgrades
_, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
if err == nil {
err = upgrade.To(release)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infof("Upgraded to %q", release.Tag)
} else {
l.Infoln("Attempting upgrade through running Syncthing...")
err = upgradeViaRest()
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infoln("Syncthing upgrading")
os.Exit(syncthing.ExitUpgrade.AsInt())
}
return release, nil
}
func upgradeViaRest() error {
@@ -568,10 +607,21 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
os.Exit(syncthing.ExitError.AsInt())
}
if runtimeOptions.unpaused {
setPauseState(cfg, false)
} else if runtimeOptions.paused {
setPauseState(cfg, true)
// Candidate builds should auto upgrade. Make sure the option is set,
// unless we are in a build where it's disabled or the STNOUPGRADE
// environment variable is set.
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
if opts := cfg.Options(); opts.AutoUpgradeIntervalH == 0 || opts.AutoUpgradeIntervalH > 24 {
opts.AutoUpgradeIntervalH = 12
// Set the option into the config as well, as the auto upgrade
// loop expects to read a valid interval from there.
cfg.SetOptions(opts)
cfg.Save()
}
// We don't tweak the user's choice of upgrading to pre-releases or
// not, as otherwise they cannot step off the candidate channel.
}
dbFile := locations.Get(locations.Database)
@@ -581,6 +631,35 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
os.Exit(1)
}
// Check if auto-upgrades should be done and if yes, do an initial
// upgrade immedately. The auto-upgrade routine can only be started
// later after App is initialised.
shouldAutoUpgrade := shouldUpgrade(cfg, runtimeOptions)
if shouldAutoUpgrade {
// try to do upgrade directly and log the error if relevant.
release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
if err == nil {
err = upgrade.To(release)
}
if err != nil {
if _, ok := err.(errNoUpgrade); ok || err == errTooEarlyUpgradeCheck || err == errTooEarlyUpgrade {
l.Debugln("Initial automatic upgrade:", err)
} else {
l.Infoln("Initial automatic upgrade:", err)
}
} else {
l.Infof("Upgraded to %q, exiting now.", release.Tag)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
}
if runtimeOptions.unpaused {
setPauseState(cfg, false)
} else if runtimeOptions.paused {
setPauseState(cfg, true)
}
appOpts := runtimeOptions.Options
if runtimeOptions.auditEnabled {
appOpts.AuditWriter = auditWriter(runtimeOptions.auditFile)
@@ -589,9 +668,19 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
secs, _ := strconv.Atoi(t)
appOpts.DeadlockTimeoutS = secs
}
if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
appOpts.DBRecheckInterval = dur
}
if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil {
appOpts.DBIndirectGCInterval = dur
}
app := syncthing.New(cfg, ldb, evLogger, cert, appOpts)
if shouldAutoUpgrade {
go autoUpgrade(cfg, app, evLogger)
}
setupSignalHandling(app)
if len(os.Getenv("GOMAXPROCS")) == 0 {
@@ -614,31 +703,6 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
go standbyMonitor(app)
}
// Candidate builds should auto upgrade. Make sure the option is set,
// unless we are in a build where it's disabled or the STNOUPGRADE
// environment variable is set.
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
if opts := cfg.Options(); opts.AutoUpgradeIntervalH == 0 || opts.AutoUpgradeIntervalH > 24 {
opts.AutoUpgradeIntervalH = 12
// Set the option into the config as well, as the auto upgrade
// loop expects to read a valid interval from there.
cfg.SetOptions(opts)
cfg.Save()
}
// We don't tweak the user's choice of upgrading to pre-releases or
// not, as otherwise they cannot step off the candidate channel.
}
if opts := cfg.Options(); opts.AutoUpgradeIntervalH > 0 {
if runtimeOptions.NoUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
} else {
go autoUpgrade(cfg, app, evLogger)
}
}
if err := app.Start(); err != nil {
os.Exit(syncthing.ExitError.AsInt())
}
@@ -771,8 +835,22 @@ func standbyMonitor(app *syncthing.App) {
}
}
func shouldUpgrade(cfg config.Wrapper, runtimeOptions RuntimeOptions) bool {
if upgrade.DisabledByCompilation {
return false
}
if opts := cfg.Options(); opts.AutoUpgradeIntervalH < 0 {
return false
}
if runtimeOptions.NoUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
return false
}
return true
}
func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger) {
timer := time.NewTimer(0)
timer := time.NewTimer(upgradeCheckInterval)
sub := evLogger.Subscribe(events.DeviceConnected)
for {
select {
@@ -827,6 +905,26 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger)
}
}
func initialAutoUpgradeCheck(misc *db.NamespacedKV) (upgrade.Release, error) {
if last, ok, err := misc.Time(upgradeCheckKey); err == nil && ok && time.Since(last) < upgradeCheckInterval {
return upgrade.Release{}, errTooEarlyUpgradeCheck
}
_ = misc.PutTime(upgradeCheckKey, time.Now())
release, err := checkUpgrade()
if err != nil {
return upgrade.Release{}, err
}
if lastVersion, ok, err := misc.String(upgradeVersionKey); err == nil && ok && lastVersion == release.Tag {
// Only check time if we try to upgrade to the same release.
if lastTime, ok, err := misc.Time(upgradeTimeKey); err == nil && ok && time.Since(lastTime) < upgradeRetryInterval {
return upgrade.Release{}, errTooEarlyUpgrade
}
}
_ = misc.PutString(upgradeVersionKey, release.Tag)
_ = misc.PutTime(upgradeTimeKey, time.Now())
return release, nil
}
// cleanConfigDirectory removes old, unused configuration and index formats, a
// suitable time after they have gone out of fashion.
func cleanConfigDirectory() {
@@ -893,3 +991,10 @@ func setPauseState(cfg config.Wrapper, paused bool) {
os.Exit(syncthing.ExitError.AsInt())
}
}
func exitCodeForUpgrade(err error) int {
if _, ok := err.(errNoUpgrade); ok {
return syncthing.ExitNoUpgradeAvailable.AsInt()
}
return syncthing.ExitError.AsInt()
}

View File

@@ -311,6 +311,11 @@ func copyStdout(stdout io.Reader, dst io.Writer) {
}
func restartMonitor(args []string) error {
// Set the STRESTART environment variable to indicate to the next
// process that this is a restart and not initial start. This prevents
// opening the browser on startup.
os.Setenv("STRESTART", "yes")
if runtime.GOOS != "windows" {
// syscall.Exec is the cleanest way to restart on Unixes as it
// replaces the current process with the new one, keeping the pid and

View File

@@ -754,6 +754,7 @@ func main() {
http.HandleFunc("/movement.json", withDB(db, movementHandler))
http.HandleFunc("/performance.json", withDB(db, performanceHandler))
http.HandleFunc("/blockstats.json", withDB(db, blockStatsHandler))
http.HandleFunc("/locations.json", withDB(db, locationsHandler))
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
go cacheRefresher(db)
@@ -765,9 +766,10 @@ func main() {
}
var (
cacheData []byte
cacheTime time.Time
cacheMut sync.Mutex
cachedIndex []byte
cachedLocations []byte
cacheTime time.Time
cacheMut sync.Mutex
)
const maxCacheTime = 15 * time.Minute
@@ -791,8 +793,15 @@ func refreshCacheLocked(db *sql.DB) error {
if err != nil {
return err
}
cacheData = buf.Bytes()
cachedIndex = buf.Bytes()
cacheTime = time.Now()
locs := rep["locations"].(map[location]int)
wlocs := make([]weightedLocation, 0, len(locs))
for loc, w := range locs {
wlocs = append(wlocs, weightedLocation{loc, w})
}
cachedLocations, _ = json.Marshal(wlocs)
return nil
}
@@ -810,13 +819,29 @@ func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(cacheData)
w.Write(cachedIndex)
} else {
http.Error(w, "Not found", 404)
return
}
}
func locationsHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
cacheMut.Lock()
defer cacheMut.Unlock()
if time.Since(cacheTime) > maxCacheTime {
if err := refreshCacheLocked(db); err != nil {
log.Println(err)
http.Error(w, "Template Error", http.StatusInternalServerError)
return
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Write(cachedLocations)
}
func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
@@ -1016,8 +1041,13 @@ func inc(storage map[string]int, key string, i interface{}) {
}
type location struct {
Latitude float64
Longitude float64
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
}
type weightedLocation struct {
location
Weight int `json:"weight"`
}
func getReport(db *sql.DB) map[string]interface{} {

View File

@@ -17,7 +17,11 @@ found in the LICENSE file.
<link href="static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="static/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=visualization&key=AIzaSyDk5WJ8s7ueLKb99X5DbQ-vkWtPDAKqYs0"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/heatmapjs@2.0.2/heatmap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet-heatmap@1.0.0/leaflet-heatmap.js"></script>
<style type="text/css">
body {
margin: 40px;
@@ -49,7 +53,6 @@ found in the LICENSE file.
google.setOnLoadCallback(drawMovementChart);
google.setOnLoadCallback(drawBlockStatsChart);
google.setOnLoadCallback(drawPerformanceCharts);
google.setOnLoadCallback(drawHeatMap);
function drawVersionChart() {
var jsonData = $.ajax({url: "summary.json", dataType:"json", async: false}).responseText;
@@ -211,47 +214,46 @@ found in the LICENSE file.
var locations = [];
{{range $location, $weight := .locations}}
locations.push({location: new google.maps.LatLng({{- $location.Latitude -}}, {{- $location.Longitude -}}), weight: {{- $weight -}}});
locations.push({lat:{{- $location.Latitude -}},lng:{{- $location.Longitude -}},count:Math.min(100, {{- $weight -}})});
{{- end}}
function drawHeatMap() {
if (locations.length == 0) {
return;
}
var mapBounds = new google.maps.LatLngBounds();
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 1,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var heatmap = new google.maps.visualization.HeatmapLayer({
var testData = {
data: locations
};
var baseLayer = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
attribution: '...',
maxZoom: 18
}
);
var cfg = {
"radius": 1,
"minOpacity": .25,
"maxOpacity": .8,
"scaleRadius": true,
"useLocalExtrema": true,
latField: 'lat',
lngField: 'lng',
valueField: 'count',
gradient: {
'.1': 'cyan',
'.8': 'blue',
'.95': 'red'
}
};
var heatmapLayer = new HeatmapOverlay(cfg);
var map = new L.Map('map', {
center: new L.LatLng(25, 0),
zoom: 1,
layers: [baseLayer, heatmapLayer]
});
heatmap.set('radius', 10);
heatmap.set('maxIntensity', 20);
heatmap.set('gradient', [
'rgba(0, 255, 255, 0)',
'rgba(0, 255, 255, 1)',
'rgba(0, 191, 255, 1)',
'rgba(0, 127, 255, 1)',
'rgba(0, 63, 255, 1)',
'rgba(0, 0, 255, 1)',
'rgba(0, 0, 223, 1)',
'rgba(0, 0, 191, 1)',
'rgba(0, 0, 159, 1)',
'rgba(0, 0, 127, 1)',
'rgba(63, 0, 91, 1)',
'rgba(127, 0, 63, 1)',
'rgba(191, 0, 31, 1)',
'rgba(255, 0, 0, 1)'
]);
heatmap.setMap(map);
for (var x = 0; x < locations.length; x++) {
mapBounds.extend(locations[x].location);
}
map.fitBounds(mapBounds);
if (locations.length == 1) {
map.setZoom(13);
}
heatmapLayer.setData(testData);
}
</script>
</head>
@@ -296,7 +298,7 @@ found in the LICENSE file.
{{if .locations}}
<div class="img-thumbnail" id="map" style="width: 1130px; height: 400px; padding: 10px;"></div>
<p class="text-muted">
Heatmap max intensity is capped at 20 reports within a location.
Heatmap max intensity is capped at 100 reports within a location.
</p>
<div class="panel panel-default">
<div class="panel-heading">
@@ -651,6 +653,7 @@ found in the LICENSE file.
</p>
<script type="text/javascript">
$('[data-toggle="tooltip"]').tooltip({html:true});
drawHeatMap();
</script>
</body>
</html>

View File

@@ -2,7 +2,7 @@
Name=Start Syncthing
GenericName=File synchronization
Comment=Starts the main syncthing process in the background.
Exec=/usr/bin/syncthing -no-browser -logfile=~/.config/syncthing/syncthing.log
Exec=/usr/bin/syncthing -no-browser -logfile=default
Icon=syncthing
Terminal=false
Type=Application

17
go.mod
View File

@@ -12,22 +12,20 @@ require (
github.com/d4l3k/messagediff v1.2.1
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.1.7
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-20190702054246-869f871628b6
github.com/golang/mock v1.3.1 // indirect
github.com/jackpal/gateway v1.0.5
github.com/jackpal/gateway v1.0.6
github.com/jackpal/go-nat-pmp v1.0.2
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/pretty v0.2.0 // indirect
github.com/lib/pq v1.2.0
github.com/lucas-clemente/quic-go v0.12.1
github.com/lucas-clemente/quic-go v0.14.4
github.com/maruel/panicparse v1.3.0
github.com/mattn/go-isatty v0.0.11
github.com/minio/sha256-simd v0.1.1
github.com/onsi/ginkgo v1.9.0 // indirect
github.com/onsi/gomega v1.6.0 // indirect
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
@@ -43,16 +41,15 @@ require (
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
github.com/willf/bitset v1.1.10 // indirect
github.com/willf/bloom v2.0.3+incompatible
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76
golang.org/x/text v0.3.2
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/ldap.v2 v2.5.1
)
go 1.13
// https://github.com/spaolacci/murmur3/pull/30
replace github.com/spaolacci/murmur3 v1.1.0 => github.com/calmh/murmur3 v1.1.1-0.20200226160057-74e9af8f47ac
replace github.com/spaolacci/murmur3 v1.1.0 => github.com/twmb/murmur3 v1.1.3

49
go.sum
View File

@@ -7,6 +7,8 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIO
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
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/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILjVyslFbc4jl1w5TWuvvslFD/nDfR2H8tVaMVLrEY=
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
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=
@@ -48,8 +50,12 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
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.1.7 h1:aHjuWTgZsnxjMgqzx0JHwNqz4jBYZTcNarbPFkW1Oww=
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
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-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
@@ -66,8 +72,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
@@ -82,8 +86,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -95,18 +99,20 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
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/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/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.12.1 h1:BPITli+6KnKogtTxBk2aS4okr5dUHz2LtIDAP1b8UL4=
github.com/lucas-clemente/quic-go v0.12.1/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
github.com/lucas-clemente/quic-go v0.14.4 h1:LdXgELrB93kvTqbZyEf+mcsGvmyE+EHsLFz1dqbR5PI=
github.com/lucas-clemente/quic-go v0.14.4/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgVpFfizUfW5WMaqyU=
github.com/marten-seemann/chacha20 v0.2.0 h1:f40vqzzx+3GdOmzQoItkLX5WLvHgPgyYqFFIO5Gh4hQ=
github.com/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE=
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI=
github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/marten-seemann/qtls v0.4.1 h1:YlT8QP3WCCvvok7MGEZkMldXbyqgr8oFg5/n8Gtbkks=
github.com/marten-seemann/qtls v0.4.1/go.mod h1:pxVXcHHw1pNIt8Qo0pwSYQEoZ8yYOOPXTCZLQQunvRc=
github.com/maruel/panicparse v1.3.0 h1:1Ep/RaYoSL1r5rTILHQQbyzHG8T4UP5ZbQTYTo4bdDc=
github.com/maruel/panicparse v1.3.0/go.mod h1:vszMjr5QQ4F5FSRfraldcIA/BCw5xrdLL+zEcU2nRBs=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
@@ -130,12 +136,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.9.0 h1:SZjF721BByVj8QH636/8S2DnX4n0Re3SteMmw3N+tzc=
github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.6.0 h1:8XTW0fcJZEq9q+Upcyws4JSGua2MFysCL5xkaSgHc+M=
github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
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=
@@ -145,8 +147,6 @@ github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCr
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.0 h1:J8lpUdobwIeCI7OiSxHqEwJUKvJwicL5+3v1oe2Yb4k=
github.com/pkg/errors v0.9.0/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -184,8 +184,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
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/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
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=
@@ -200,6 +198,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
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/twmb/murmur3 v1.1.3 h1:D83U0XYKcHRYwYIpBKf3Pks91Z0Byda/9SJ8B6EMRcA=
github.com/twmb/murmur3 v1.1.3/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
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=
@@ -211,15 +211,15 @@ github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPy
github.com/willf/bloom v2.0.3+incompatible h1:QDacWdqcAUI1MPOwIQZRy9kOR7yxfyEmxX8Wdm2/JPA=
github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/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/net v0.0.0-20180906233101-161cd47e91fd/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-20190228165749-92fc7df08ae7/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
@@ -228,7 +228,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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=
@@ -237,10 +236,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
@@ -255,19 +253,14 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZe
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
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/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
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/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
"Compression": "Компресиране",
"Configured": "Настроен",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Грешка при свързването",
"Connection Type": "Вид връзка",
"Connections": "Връзки",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодичните сканирания са деактивирани и задаването на наблюдение за промени е неуспешно, ще опита пак след 1мин:",
"Discard": "Discard",
"Disconnected": "Не е свързано",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Открит",
"Discovery": "Откриване",
"Discovery Failures": "Грешка в откриването",
@@ -219,6 +221,7 @@
"Pause": "Пауза",
"Pause All": "Пауза на всички",
"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 enabled watching for changes": "Периодично сканиране, през определен интервал, и мониторинг за промени",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Comentar, quant s'utilitza al principi d'una línia",
"Compression": "Compresió",
"Configured": "Configurat",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Error de connexió",
"Connection Type": "Tipus de connexió",
"Connections": "Connexions",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Desactivat l'escaneig periòdic i errada al rastreig continu de canvis, es reintentarà cada 1 minut:",
"Discard": "Descartar",
"Disconnected": "Desconnectat",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Descobert",
"Discovery": "Descobriment",
"Discovery Failures": "Fallades al Descobriment",
@@ -219,6 +221,7 @@
"Pause": "Pausa",
"Pause All": "Pausa Tot",
"Paused": "Pausat",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Canvis pendents",
"Periodic scanning at given interval and disabled watching for changes": "Escaneig periòdic a l'interval determinat i desactivat el rastreig continu de canvis",
"Periodic scanning at given interval and enabled watching for changes": "Escaneig periòdic a l'interval determinat i activat el rastreig continu de canvis",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Pokud použito na jeho začátku, je řádek považován za komentář",
"Compression": "Komprese",
"Configured": "Nastaveno",
"Connected (Unused)": "Připojeno (nepoužité)",
"Connection Error": "Chyba připojení",
"Connection Type": "Typ připojení",
"Connections": "Spojení",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Periodické skenování vypnuto; nastavení sledování změn se nezdařilo, opětovný pokus každou 1 min:",
"Discard": "Zahodit",
"Disconnected": "Odpojeno",
"Disconnected (Unused)": "Odpojeno (nepoužité)",
"Discovered": "Objeveno",
"Discovery": "Oznamování",
"Discovery Failures": "Nezdary při oznamování",
@@ -219,6 +221,7 @@
"Pause": "Pozastavit",
"Pause All": "Pozastavit vše",
"Paused": "Pozastaveno",
"Paused (Unused)": "Pozastaveno (nepoužité)",
"Pending changes": "Čekající změny",
"Periodic scanning at given interval and disabled watching for changes": "Periodické skenování podle zadaného intervalu; sledování změn vypnuto",
"Periodic scanning at given interval and enabled watching for changes": "Periodické skenování podle zadaného intervalu; sledování změn zapnuto",

View File

@@ -50,6 +50,7 @@
"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)",
"Connection Error": "Tilslutnings fejl",
"Connection Type": "Tilslutningstype",
"Connections": "Forbindelser",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Deaktiverede periodisk skanning fra og lykkedes ikke med at opsætte overvågning af ændringer; prøver igen hvert minut:",
"Discard": "Behold ikke",
"Disconnected": "Ikke tilsluttet",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Opdaget",
"Discovery": "Opslag",
"Discovery Failures": "Fejl ved opdagelse",
@@ -219,6 +221,7 @@
"Pause": "Pause",
"Pause All": "Sæt alt på pause",
"Paused": "På pause",
"Paused (Unused)": "Paused (Unused)",
"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",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
"Compression": "Komprimierung",
"Configured": "Konfiguriert",
"Connected (Unused)": "Verbunden (Nicht genutzt)",
"Connection Error": "Verbindungsfehler",
"Connection Type": "Verbindungstyp",
"Connections": "Verbindungen",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Deaktivierter periodischer Scann, fehlgeschlagene überprüfen auf Änderungen und erneuter versuch in 1 Min:",
"Discard": "Verwerfen",
"Disconnected": "Getrennt",
"Disconnected (Unused)": "Getrennt (Nicht genutzt)",
"Discovered": "Ermittelt",
"Discovery": "Gerätesuche",
"Discovery Failures": "Gerätesuchfehler",
@@ -219,6 +221,7 @@
"Pause": "Pause",
"Pause All": "Alles pausieren",
"Paused": "Pausiert",
"Paused (Unused)": "Pausiert (Nicht genutzt)",
"Pending changes": "Ausstehende Änderungen",
"Periodic scanning at given interval and disabled watching for changes": "Periodisches Scannen bei angegebenen Intervall und deaktivierter Überwachung von Änderungen",
"Periodic scanning at given interval and enabled watching for changes": "Periodisches Scannen bei angegebenen Intervall und aktivierter Überwachung von Änderungen",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Σχόλιο, όταν χρησιμοποιείται στην αρχή μιας γραμμής",
"Compression": "Συμπίεση",
"Configured": "Βάσει ρύθμισης",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Σφάλμα σύνδεσης",
"Connection Type": "Τύπος Σύνδεσης",
"Connections": "Συνδέσεις",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Έχει απενεργοποιηθεί η τακτική σάρωση και απέτυχε η ενεργοποίηση επιτήρησης αλλαγών. Γίνεται νέα προσπάθεια κάθε 1m:",
"Discard": "Discard",
"Disconnected": "Αποσυνδεδεμένη",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Βάσει ανεύρεσης",
"Discovery": "Ανεύρεση συσκευών",
"Discovery Failures": "Αποτυχίες ανεύρεσης συσκευών",
@@ -219,6 +221,7 @@
"Pause": "Παύση",
"Pause All": "Παύση όλων",
"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 enabled watching for changes": "Τακτική σάρωση ανά καθορισμένο διάστημα και ενεργοποίηση επιτήρησης αλλαγών",

View File

@@ -50,6 +50,7 @@
"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",
@@ -82,6 +83,7 @@
"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:",
"Discard": "Discard",
"Disconnected": "Disconnected",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Discovered",
"Discovery": "Discovery",
"Discovery Failures": "Discovery Failures",
@@ -219,6 +221,7 @@
"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",

View File

@@ -50,6 +50,7 @@
"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",
@@ -82,6 +83,7 @@
"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:",
"Discard": "Discard",
"Disconnected": "Disconnected",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Discovered",
"Discovery": "Discovery",
"Discovery Failures": "Discovery Failures",
@@ -161,6 +163,7 @@
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"LDAP": "LDAP",
"Largest First": "Largest First",
"Last File Received": "Last File Received",
"Last Scan": "Last Scan",
@@ -219,6 +222,7 @@
"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",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Komento, kiam uzita ĉe la komenco de lineo",
"Compression": "Densigo",
"Configured": "Agordita",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Eraro de Konekto",
"Connection Type": "Tipo de Konekto",
"Connections": "Konektoj",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Malebligita perioda skanado kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
"Discard": "Forĵeti",
"Disconnected": "Malkonektita",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Malkovrita",
"Discovery": "Malkovro",
"Discovery Failures": "Malsukcesoj de Malkovro",
@@ -219,6 +221,7 @@
"Pause": "Paŭzu",
"Pause All": "Paŭzu Ĉion",
"Paused": "Paŭzita",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Pritraktataj ŝanĝoj",
"Periodic scanning at given interval and disabled watching for changes": "Perioda skanado ĉe donita intervalo kaj malebligita rigardado je ŝanĝoj",
"Periodic scanning at given interval and enabled watching for changes": "Perioda skanado ĉe donita intervalo kaj ebligita rigardado je ŝanĝoj",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Comentar, cuando se usa al comienzo de una línea",
"Compression": "Compresión",
"Configured": "Configurado",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Error de conexión",
"Connection Type": "Tipo de conexión",
"Connections": "Conexiones",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Desactivado el escaneo periódico y falló la activación de la vigilancia de cambios, reintentando cada 1 minuto:",
"Discard": "Descartar",
"Disconnected": "Desconectado",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Descubierto",
"Discovery": "Descubrimiento",
"Discovery Failures": "Fallos de Descubrimiento",
@@ -219,6 +221,7 @@
"Pause": "Pausar",
"Pause All": "Pausar todo",
"Paused": "Pausado",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Cambios pendientes",
"Periodic scanning at given interval and disabled watching for changes": "Escaneo periódico en un intervalo determinado y desactivada la vigilancia de cambios",
"Periodic scanning at given interval and enabled watching for changes": "Escaneo periódico en un intervalo determinado y activada la vigilancia de cambios",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Comentar, cuando se usa al comienzo de una línea",
"Compression": "Compresión",
"Configured": "Configurado",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Error de conexión",
"Connection Type": "Tipo de conexión",
"Connections": "Conexiones",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Se desactivó el escaneo periódico y falló la configuración para detectar cambios, volviendo a intentarlo cada 1 m:",
"Discard": "Descartar",
"Disconnected": "Desconectado",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Descubierto",
"Discovery": "Descubrimiento",
"Discovery Failures": "Fallos de Descubrimiento",
@@ -219,6 +221,7 @@
"Pause": "Pausar",
"Pause All": "Pausar todo",
"Paused": "Pausado",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Cambios pendientes",
"Periodic scanning at given interval and disabled watching for changes": "Escaneando periódicamente a un intervalo dado y detección de cambios desactivada",
"Periodic scanning at given interval and enabled watching for changes": "Escaneando periódicamente a un intervalo dado y detección de cambios activada",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Kommentti, käytettäessä rivin alussa",
"Compression": "Pakkaus",
"Configured": "Konfiguroitu",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Yhteysvirhe",
"Connection Type": "Yhteyden tyyppi",
"Connections": "Yhteydet",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Ajoitettu skannaus pois päältä. Muutosten seurannan käyttöönotto epäonnistui, yritetään uudelleen minuutin välein:",
"Discard": "Hylkää",
"Disconnected": "Yhteys katkaistu",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Löydetty",
"Discovery": "Etsintä",
"Discovery Failures": "Etsinnässä tapahtuneet virheet",
@@ -219,6 +221,7 @@
"Pause": "Keskeytä",
"Pause All": "Keskeytä kaikki",
"Paused": "Keskeytetty",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Odottavia muutoksia",
"Periodic scanning at given interval and disabled watching for changes": "Ajoitettu skannaus päällä. Muutosten seuranta pois päältä",
"Periodic scanning at given interval and enabled watching for changes": "Ajoitettu skannaus ja muutosten seuranta päällä",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Commentaire lorsque utilisé en début de ligne",
"Compression": "Compression",
"Configured": "Configurée",
"Connected (Unused)": "Connecté (Non utilisé)",
"Connection Error": "Erreur de connexion",
"Connection Type": "Type de connexion",
"Connections": "Connexions",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Analyse périodique désactivée et échec d'activation de la surveillance des changements. Nouvel essai toutes les 1mn :",
"Discard": "Rejeter",
"Disconnected": "Déconnecté",
"Disconnected (Unused)": "Déconnecté (Non utilisé)",
"Discovered": "Découvert",
"Discovery": "Découverte",
"Discovery Failures": "Échecs de découverte",
@@ -219,6 +221,7 @@
"Pause": "Pause",
"Pause All": "Tout suspendre",
"Paused": "En pause",
"Paused (Unused)": "En pause (Non utilisé)",
"Pending changes": "Modifications en attente",
"Periodic scanning at given interval and disabled watching for changes": "Analyse périodique à intervalle défini et surveillance des changements désactivée.",
"Periodic scanning at given interval and enabled watching for changes": "Analyse périodique à intervalle défini et surveillance des changements activée.",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Kommentaar, wannear as brûkt by it begjin fan in rige",
"Compression": "Kompresje",
"Configured": "Konfigureart",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Ferbiningsflater",
"Connection Type": "Ferbiningstype",
"Connections": "Ferbinings",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Periodic scanning útskeakele en it ynskeakeljen fan it yn'e gaten hâlden fan feroarings is mislearre, wurd eltse 1m opnij besocht:",
"Discard": "Fuortsmite\n",
"Disconnected": "Ferbining ferbrutsen",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Untdekt",
"Discovery": "Untdekking",
"Discovery Failures": "Untdekkingsflaters",
@@ -219,6 +221,7 @@
"Pause": "Skoftsje",
"Pause All": "Alles skoftsje",
"Paused": "Skoftet",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Wachtet op feroarings",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning op opjûn ynterfal en feroarings wurde net yn'e gaten hâlden.",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning op opjûn ynterfal en feroarings wurde yn'e gaten hâlden.",

View File

@@ -31,7 +31,7 @@
"Are you sure you want to remove device {%name%}?": "Biztos, hogy el akarod távolítani az eszközt: {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Biztos, hogy el akarod távolítani a mappát: {{label}}?",
"Are you sure you want to restore {%count%} files?": "Biztos, hogy vissza akarod állítani a(z) {{count}} fájlt?",
"Are you sure you want to upgrade?": "Valóban frissíthető?",
"Are you sure you want to upgrade?": "Biztos, hogy frissíteni akarod?",
"Auto Accept": "Automatikus elfogadás",
"Automatic Crash Reporting": "Automatikus összeomlás-jelentés",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Az automatikus frissítés most lehetőséget kínál a stabil és az előzetes kiadások közötti választásra.",
@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
"Compression": "Tömörítés",
"Configured": "Beállított",
"Connected (Unused)": "Kapcsolódva (használaton kívül)",
"Connection Error": "Kapcsolódási hiba",
"Connection Type": "Kapcsolattípus",
"Connections": "Kapcsolatok",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "A periodikus átnézés letiltva és a változások keresésének beállítása sikertelen, 1 percenként újrapróbálkozás:",
"Discard": "Elvetés",
"Disconnected": "Kapcsolat bontva",
"Disconnected (Unused)": "Kapcsolat bontva (használaton kívül)",
"Discovered": "Felfedezett",
"Discovery": "Felfedezés",
"Discovery Failures": "Felfedezési hibák",
@@ -219,6 +221,7 @@
"Pause": "Szünet",
"Pause All": "Mindent szüneteltet",
"Paused": "Szünetel",
"Paused (Unused)": "Szüneteltetve (használaton kívül)",
"Pending changes": "Várakozó módosítások",
"Periodic scanning at given interval and disabled watching for changes": "Periodikus átnézés a megadott időközönként és a változások keresése letiltva",
"Periodic scanning at given interval and enabled watching for changes": "Periodikus átnézés a megadott időközönként és a változások keresése engedélyezve",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
"Compression": "Compressione",
"Configured": "Configurato",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Errore di Connessione",
"Connection Type": "Tipo di Connessione",
"Connections": "Connessioni",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Scansione periodica disabilitata e configurazione fallita del monitoraggio cambiamenti, nuovo tentativo ogni 1m:",
"Discard": "Scartare",
"Disconnected": "Disconnesso",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Individuato",
"Discovery": "Individuazione",
"Discovery Failures": "Individuazione Fallita",
@@ -219,6 +221,7 @@
"Pause": "Pausa",
"Pause All": "Pausa Tutti",
"Paused": "In Pausa",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Modifiche in attesa",
"Periodic scanning at given interval and disabled watching for changes": "Scansione periodica a intervalli determinati e monitoraggio cambiamenti disabilitata",
"Periodic scanning at given interval and enabled watching for changes": "Scansione periodica a intervalli determinati e monitoraggio cambiamenti abilitata",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "行頭で使用するとコメント行になります",
"Compression": "圧縮",
"Configured": "設定値",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "接続エラー",
"Connection Type": "接続種別",
"Connections": "接続",
@@ -82,6 +83,7 @@
"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:",
"Discard": "破棄",
"Disconnected": "切断中",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "探索結果",
"Discovery": "探索サーバー",
"Discovery Failures": "探索サーバーへの接続失敗",
@@ -219,6 +221,7 @@
"Pause": "一時停止",
"Pause All": "すべて一時停止",
"Paused": "一時停止中",
"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": "定期スキャンと変更の監視はいずれも有効です",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
"Compression": "압축",
"Configured": "설정됨",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "연결 에러",
"Connection Type": "연결 종류",
"Connections": "연결",
@@ -82,6 +83,7 @@
"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:",
"Discard": "Discard",
"Disconnected": "연결 끊김",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "탐색됨",
"Discovery": "탐색",
"Discovery Failures": "탐색 실패",
@@ -219,6 +221,7 @@
"Pause": "일시 중지",
"Pause All": "모두 일시 중지",
"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",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
"Compression": "Kompresija",
"Configured": "Sukonfigūruotas",
"Connected (Unused)": "Prisijungęs (Nenaudojamas)",
"Connection Error": "Susijungimo klaida",
"Connection Type": "Ryšio tipas",
"Connections": "Ryšiai",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Išjungtas periodinis nuskaitymas ir nepavykęs nustatyti pakeitimų stebėjimas, bandoma iš naujo kas 1 min.:",
"Discard": "Atmesti",
"Disconnected": "Atsijungęs",
"Disconnected (Unused)": "Atsijungęs (Nenaudojamas)",
"Discovered": "Atrastas",
"Discovery": "Lokacija",
"Discovery Failures": "Matomumo nesėkmės",
@@ -219,6 +221,7 @@
"Pause": "Pristabdyti",
"Pause All": "Pristabdyti visus",
"Paused": "Pristabdyta",
"Paused (Unused)": "Pristabdytas (Nenaudojamas)",
"Pending changes": "Laukiantys pakeitimai",
"Periodic scanning at given interval and disabled watching for changes": "Periodinis nuskaitymas nurodytu intervalu ir išjungtas pakeitimų stebėjimas",
"Periodic scanning at given interval and enabled watching for changes": "Periodinis nuskaitymas nurodytu intervalu ir įjungtas pakeitimų stebėjimas",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
"Compression": "Komprimering",
"Configured": "Oppsatt",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Tilkoblingsfeil",
"Connection Type": "Tilkoblingstype",
"Connections": "Tilkoblinger",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Skrudde av periodisk skanning og mislyktes i oppsett av oppsyn med endringer, prøver igjen hvert minutt:",
"Discard": "Kasser",
"Disconnected": "Frakoblet",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Oppdaget",
"Discovery": "Oppslag",
"Discovery Failures": "Oppslagsfeil",
@@ -219,6 +221,7 @@
"Pause": "Oppholde",
"Pause All": "Sett alt på pause",
"Paused": "Oppholdt",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Påventende endringer",
"Periodic scanning at given interval and disabled watching for changes": "Periodisk skanning på gitte intervaller og avskrudd oppsyn med endringer",
"Periodic scanning at given interval and enabled watching for changes": "Periodisk skanning på gitte intervall og påskrudd oppsyn med endringer",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Opmerking, wanneer gebruikt aan het begin van een regel",
"Compression": "Compressie",
"Configured": "Geconfigureerd",
"Connected (Unused)": "Verbonden (niet gebruikt)",
"Connection Error": "Verbindingsfout",
"Connection Type": "Soort verbinding",
"Connections": "Verbindingen",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Periodiek scannen uitgeschakeld en instellen van opvolgen van wijzigingen mislukt. Elke minuut wordt opnieuw geprobeerd:",
"Discard": "Verwerpen",
"Disconnected": "Niet verbonden",
"Disconnected (Unused)": "Niet verbonden (niet gebruikt)",
"Discovered": "Ontdekt",
"Discovery": "Ontdekking",
"Discovery Failures": "Ontdekkingsproblemen",
@@ -219,6 +221,7 @@
"Pause": "Pauzeren",
"Pause All": "Alles pauzeren",
"Paused": "Gepauzeerd",
"Paused (Unused)": "Gepauzeerd (niet gebruikt)",
"Pending changes": "Wijzigingen in afwachting",
"Periodic scanning at given interval and disabled watching for changes": "Periodiek scannen met opgegeven interval; opvolgen van wijzigingen uitgeschakeld",
"Periodic scanning at given interval and enabled watching for changes": "Periodiek scannen met opgegeven interval; opvolgen van wijzigingen ingeschakeld",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
"Compression": "Kompresja",
"Configured": "Skonfigurowane",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Błąd połączenia",
"Connection Type": "Rodzaj połączenia",
"Connections": "Połączenia",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Wyłączono okresowe skanowanie i nie udało się skonfigurować obserwowania zmian, powtórzę co minutę:",
"Discard": "Odrzuć",
"Disconnected": "Rozłączony",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Odkryte",
"Discovery": "Odnajdywanie",
"Discovery Failures": "Błędy odnajdowania",
@@ -219,6 +221,7 @@
"Pause": "Zatrzymaj",
"Pause All": "Zatrzymaj wszystkie",
"Paused": "Zatrzymany",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Oczekujące zmiany",
"Periodic scanning at given interval and disabled watching for changes": "Okresowe skanowanie w podanym przedziale czasu i wyłączone obserwowanie zmian",
"Periodic scanning at given interval and enabled watching for changes": "Okresowe skanowanie w podanym przedziale czasu i włączone obserwowanie zmian",

View File

@@ -28,12 +28,12 @@
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anônimo de uso mudou. Gostaria de usar o formato novo?",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados em um apresentador também serão adicionados a este dispositivo.",
"Are you sure you want to remove device {%name%}?": "Tem certeza de que deseja remover o dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Tem certeza de que deseja remover a pasta {{name}}?",
"Are you sure you want to restore {%count%} files?": "Tem certeza de que deseja restaurar {{count}} arquivo(s)?",
"Are you sure you want to upgrade?": "Are you sure you want to upgrade?",
"Are you sure you want to remove device {%name%}?": "Deseja mesmo remover o dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Deseja mesmo remover a pasta {{name}}?",
"Are you sure you want to restore {%count%} files?": "Deseja mesmo restaurar {{count}} arquivo(s)?",
"Are you sure you want to upgrade?": "Deseja mesmo fazer a atualização?",
"Auto Accept": "Aceitar automaticamente",
"Automatic Crash Reporting": "Automatic Crash Reporting",
"Automatic Crash Reporting": "Relatório automático de falhas",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "A atualização automática agora oferece a escolha entre versões estáveis e candidatas ao lançamento.",
"Automatic upgrades": "Atualizações automáticas",
"Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.",
@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Comentário, se usado no início de uma linha",
"Compression": "Compressão",
"Configured": "Configurado",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Erro de conexão",
"Connection Type": "Tipo da conexão",
"Connections": "Conexões",
@@ -58,9 +59,9 @@
"Copied from original": "Copiado do original",
"Copyright © 2014-2016 the following Contributors:": "Direitos reservados © 2014-2016 aos seguintes colaboradores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 dos seguintes Colaboradores:",
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 dos Contribuintes:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Criando filtros, sobrescrevendo o arquivo {{path}}.",
"Currently Shared With Devices": "Currently Shared With Devices",
"Currently Shared With Devices": "Compartilhado com outros dispositivos",
"Danger!": "Perigo!",
"Debugging Facilities": "Facilidades de depuração",
"Default Folder Path": "Caminho padrão da pasta",
@@ -75,13 +76,14 @@
"Device rate limits": "Device rate limits",
"Device that last modified the item": "Dispositivo que modificou o item pela última vez",
"Devices": "Dispositivos",
"Disable Crash Reporting": "Disable Crash Reporting",
"Disable Crash Reporting": "Desabilitar relatório de falhas",
"Disabled": "Desabilitado",
"Disabled periodic scanning and disabled watching for changes": "Verificação periódica e verificação automática de mudanças desabilitadas",
"Disabled periodic scanning and enabled watching for changes": "Verificação periódica desabilitada. Verificação automática de mudanças habilitada.",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Verificação periódica desabilitada. Não foi possível configurar a verificação automática de mudanças, tentando novamente a cada 1 minuto:",
"Discard": "Discard",
"Discard": "Descartar",
"Disconnected": "Desconectado",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Descoberto",
"Discovery": "Descoberta",
"Discovery Failures": "Falhas na descoberta",
@@ -97,7 +99,7 @@
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Editing {%path%}.": "Editando {{path}}.",
"Enable Crash Reporting": "Enable Crash Reporting",
"Enable Crash Reporting": "Habilitar relatório de falhas",
"Enable NAT traversal": "Habilitar NAT",
"Enable Relaying": "Habilitar retransmissão",
"Enabled": "Habilitado",
@@ -152,9 +154,9 @@
"Ignore": "Ignorar",
"Ignore Patterns": "Filtros",
"Ignore Permissions": "Ignorar permissões",
"Ignored Devices": "Ignored Devices",
"Ignored Folders": "Ignored Folders",
"Ignored at": "Ignored at",
"Ignored Devices": "Dispositivos ignorados",
"Ignored Folders": "Pastas ignoradas",
"Ignored at": "Ignorada em",
"Incoming Rate Limit (KiB/s)": "Limite de velocidade de recepção (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "A configuração incorreta poderá causar danos aos seus dados e tornar o Syncthing inoperante.",
"Introduced By": "Introduzido por",
@@ -168,15 +170,15 @@
"Later": "Depois",
"Latest Change": "Última mudança",
"Learn more": "Saiba mais",
"Limit": "Limit",
"Limit": "Limite",
"Listeners": "Escutadores",
"Loading data...": "Carregando dados...",
"Loading...": "Carregando",
"Local Additions": "Local Additions",
"Local Additions": "Acréscimos locais",
"Local Discovery": "Descoberta local",
"Local State": "Estado local",
"Local State (Total)": "Estado local (total)",
"Locally Changed Items": "Locally Changed Items",
"Locally Changed Items": "Itens modificados localmente",
"Log": "Log",
"Log tailing paused. Click here to continue.": "Observação do log pausada. Clique aqui para continuar.",
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
@@ -219,7 +221,8 @@
"Pause": "Pausar",
"Pause All": "Pausar todas",
"Paused": "Em pausa",
"Pending changes": "Pending changes",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Modificações pendentes",
"Periodic scanning at given interval and disabled watching for changes": "Verificação periódica habilitada no intervalo escolhido. Verificação automática de mudanças desabilitada",
"Periodic scanning at given interval and enabled watching for changes": "Verificação periódica habilitada no intervalo escolhido. Verificação automática de mudanças habilitada",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Verificação periódica habilitada no intervalo escolhido. Não foi possível configurar a verificação automática de mudanças, tentando novamente a cada 1 minuto:",
@@ -229,7 +232,7 @@
"Please wait": "Aguarde",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefixo indicando que o arquivo pode ser removido caso esteja impedindo a remoção do seu diretório",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefixo indicando que o filtro deve ser igualado sem distinção entre maiúsculas e minúsculas",
"Preparing to Sync": "Preparing to Sync",
"Preparing to Sync": "Preparando para sincronizar",
"Preview": "Visualizar",
"Preview Usage Report": "Visualizar relatório de uso",
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
@@ -257,16 +260,16 @@
"Resume": "Resumir",
"Resume All": "Resumir Todas",
"Reused": "Reutilizado",
"Revert Local Changes": "Revert Local Changes",
"Revert Local Changes": "Reverter mudanças locais",
"Running": "Rodando",
"Save": "Salvar",
"Scan Time Remaining": "Tempo de verificação restante",
"Scanning": "Verificando",
"See external versioner help for supported templated command line parameters.": "Consulte a ajuda sobre versionamento externo para modelos de parâmetros de linha de comando aceitos.",
"See external versioning help for supported templated command line parameters.": "Consulte a ajuda sobre versionamento externo para modelos de parâmetros de linha de comando aceitos.",
"Select All": "Select All",
"Select All": "Selecionar tudo",
"Select a version": "Selecione uma versão",
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
"Select additional devices to share this folder with.": "Selecione outros dispositivos com os quais a pasta será compartilhada.",
"Select latest version": "Escolher a última versão",
"Select oldest version": "Escolher a versão mais antiga",
"Select the devices to share this folder with.": "Selecione os dispositivos com os quais esta pasta será compartilhada.",
@@ -307,7 +310,7 @@
"Syncing": "Sincronizando",
"Syncthing has been shut down.": "O Syncthing foi desligado.",
"Syncthing includes the following software or portions thereof:": "O Syncthing inclui os seguintes programas ou partes deles:",
"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.": "O Syncthing é um software de código aberto licenciado pela MPL v2.0.",
"Syncthing is restarting.": "O Syncthing está sendo reiniciado.",
"Syncthing is upgrading.": "O Syncthing está sendo atualizado.",
"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.",
@@ -381,7 +384,7 @@
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Aviso: este caminho é o diretório pai da pasta \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Atenção, este caminho é um subdiretório de uma pasta já existente: \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Aviso: este caminho é um subdiretório da pasta \"{{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.",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Aviso: caso você esteja usando um observador externo como o {{syncthingInotify}}, tenha certeza de que ele está desativado.",
"Watch for Changes": "Observar alterações",
"Watching for Changes": "Observando alterações",
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
"Compression": "Compressão",
"Configured": "Configurado",
"Connected (Unused)": "Ligado (não usado)",
"Connection Error": "Erro de ligação",
"Connection Type": "Tipo de ligação",
"Connections": "Ligações",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Desactivada a verificação periódica e falha ao preparar a vigilância de alterações, tentando novamente a cada minuto:",
"Discard": "Descartar",
"Disconnected": "Desconectado",
"Disconnected (Unused)": "Desligado (não usado)",
"Discovered": "Descoberto",
"Discovery": "Pesquisa",
"Discovery Failures": "Falhas da pesquisa",
@@ -219,6 +221,7 @@
"Pause": "Pausar",
"Pause All": "Pausar todas",
"Paused": "Em pausa",
"Paused (Unused)": "Em pausa (não usado)",
"Pending changes": "Alterações pendentes",
"Periodic scanning at given interval and disabled watching for changes": "Verificação periódica no intervalo dado e desactivada a vigilância de alterações",
"Periodic scanning at given interval and enabled watching for changes": "Verificação periódica no intervalo dado e activada a vigilância de alterações",

View File

@@ -31,7 +31,7 @@
"Are you sure you want to remove device {%name%}?": "Вы уверены, что хотите удалить устройство {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Вы уверены, что хотите удалить папку {{label}}?",
"Are you sure you want to restore {%count%} files?": "Вы уверены, что хотите восстановить {{count}} файлов?",
"Are you sure you want to upgrade?": "Are you sure you want to upgrade?",
"Are you sure you want to upgrade?": "Вы уверены, что хотите обновить?",
"Auto Accept": "Автопринятие",
"Automatic Crash Reporting": "Automatic Crash Reporting",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматическое обновление теперь предлагает выбор между стабильными выпусками и кандидатами в релизы.",
@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
"Compression": "Сжатие",
"Configured": "Сконфигурировано",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Ошибка подключения",
"Connection Type": "Тип соединения",
"Connections": "Подключения",
@@ -75,13 +76,14 @@
"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": "Периодическое сканирование отключено, отслеживание изменений включено",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодическое сканирование отключено, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
"Discard": "Отменить",
"Disconnected": "Нет соединения",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Обнаружено",
"Discovery": "Обнаружение",
"Discovery Failures": "Ошибки обнаружения",
@@ -97,7 +99,7 @@
"Edit Folder": "Редактирование папки",
"Editing": "Редактирование",
"Editing {%path%}.": "Правка {{path}}.",
"Enable Crash Reporting": "Enable Crash Reporting",
"Enable Crash Reporting": "Включить отчёты о сбоях",
"Enable NAT traversal": "Включить NAT traversal",
"Enable Relaying": "Включить релеи",
"Enabled": "Включено",
@@ -118,7 +120,7 @@
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Права на файлы игнорируются при поиске изменений. Используется на файловой системе FAT.",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Когда Syncthing изменяет или удаляет файлы, они помещаются в папку .stversions",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Файлы перемещаются в папку .stversions после их замены или удаления системой Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Когда Syncthing изменяет или удаляет файлы, их версии с таймштампами помещаются в папку .stversions",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Когда Syncthing изменяет или удаляет файлы, их версии с отметками времени помещаются в папку .stversions",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Файлы с временнОй меткой версии помещаются в папку .stversions при их замене или удалении 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 synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Файлы синхронизируются из группы, но изменения, сделанные на этом устройстве, не будут отправлены на другие устройства группы.",
@@ -147,7 +149,7 @@
"Global Discovery Servers": "Серверы глобального обнаружения",
"Global State": "Глобальное состояние",
"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.",
"Ignore": "Игнорировать",
"Ignore Patterns": "Шаблоны игнорирования",
@@ -219,12 +221,13 @@
"Pause": "Пауза",
"Pause All": "Приостановить все",
"Paused": "Приостановлено",
"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": "Периодическое сканирование с заданным интервалом и включено отслеживание изменений",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Периодическое сканирование с заданным интервалом, не удалось включить отслеживание изменений, повторная попытка каждую минуту.",
"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 wait": "Пожалуйста, подождите",
"Prefix indicating that the file can be deleted if preventing directory removal": "Префикс указывает, что файл может быть удалён, если он мешает удалить папку",
@@ -374,8 +377,8 @@
"Versions": "Версии",
"Versions Path": "Путь к версиям",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Версии удаляются автоматически, если они существуют дольше максимального срока или превышают разрешённое количество файлов за интервал.",
"Waiting to Scan": "Waiting to Scan",
"Waiting to Sync": "Waiting to Sync",
"Waiting to Scan": "Ожидание сканирования",
"Waiting to Sync": "Ожидание синхронизации",
"Waiting to scan": "Ожидание сканирования",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolder}}».",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolderLabel}}» ({{otherFolder}}).",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Komentár, keď použité na začiatku riadku",
"Compression": "Kompresia",
"Configured": "Nakonfigurované",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Chyba pripojenia",
"Connection Type": "Typ pripojenia",
"Connections": "Spojenia",
@@ -82,6 +83,7 @@
"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:",
"Discard": "Zahodiť",
"Disconnected": "Odpojené",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Zistené",
"Discovery": "Zisťovanie",
"Discovery Failures": "Zlyhania zisťovania",
@@ -219,6 +221,7 @@
"Pause": "Pozastaviť",
"Pause All": "Pozastaviť všetky",
"Paused": "Pozastavené",
"Paused (Unused)": "Paused (Unused)",
"Pending changes": "Čakajúce zmeny",
"Periodic scanning at given interval and disabled watching for changes": "Periodické skenovanie v zvolenom rozsahu a vypnuté sledovanie zmien.",
"Periodic scanning at given interval and enabled watching for changes": "Periodické skenovanie v zvolenom rozsahu a zapnuté sledovanie zmien.",

View File

@@ -1,5 +1,5 @@
{
"A device with that ID is already added.": "En enhet med det ID:t är redan tillagt.",
"A device with that ID is already added.": "En enhet med detta ID har redan lagts tillagt.",
"A negative number of days doesn't make sense.": "Ett negativt antal dagar är inte rimligt.",
"A new major version may not be compatible with previous versions.": "En ny huvudversion kan eventuellt vara inkompatibel med tidigare versioner.",
"API Key": "API-nyckel",
@@ -18,7 +18,7 @@
"Advanced": "Avancerat",
"Advanced Configuration": "Avancerad konfiguration",
"Advanced settings": "Avancerade inställningar",
"All Data": "All data",
"All Data": "Alla data",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistiksrapportering?",
"Allowed Networks": "Tillåtna nätverk",
"Alphabetic": "Alfabetisk",
@@ -50,10 +50,11 @@
"Comment, when used at the start of a line": "Kommentara, vid användning i början av en rad.",
"Compression": "Komprimering",
"Configured": "Konfigurerad",
"Connected (Unused)": "Ansluten (oanvänd)",
"Connection Error": "Anslutningsproblem",
"Connection Type": "Anslutningstyp",
"Connections": "Anslutningar",
"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.": "Kontinuerligt utkik efter ändringar är nu tillgängligt för Syncthing. Detta kommer att upptäcka ändringar på disken och utfärda en skanning på endast de ändrade sökvägarna. Fördelarna är att förändringar sprids snabbare och att mindre fullständiga skanningar krävs.",
"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.": "Kontinuerligt bevakning av ändringar är nu tillgängligt inom Syncthing. Detta kommer att upptäcka ändringar på disken och utfärda en skanning på endast de ändrade sökvägarna. Fördelarna är att förändringar sprids snabbare och att mindre fullständiga skanningar krävs.",
"Copied from elsewhere": "Kopierat från annanstans",
"Copied from original": "Kopierat från original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 följande bidragare:",
@@ -64,7 +65,7 @@
"Danger!": "Fara!",
"Debugging Facilities": "Felsökningsanläggningar",
"Default Folder Path": "Standard mappsökväg",
"Deleted": "Raderade",
"Deleted": "Borttagna",
"Deselect All": "Avmarkera alla",
"Deselect devices to stop sharing this folder with.": "Avmarkera enheter för att sluta dela den här mappen med.",
"Device": "Enhet",
@@ -79,15 +80,16 @@
"Disabled": "Inaktiverad",
"Disabled periodic scanning and disabled watching for changes": "Inaktiverad periodisk skanning och inaktiverad övervakning av ändringar",
"Disabled periodic scanning and enabled watching for changes": "Inaktiverad periodisk skanning och aktiverad övervakning av ändringar",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Inaktiverad periodisk skanning och misslyckades att ställa in övervakning av ändringar, försök igen varje 1m:",
"Discard": "Kassera",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Inaktiverad periodisk skanning och misslyckades att ställa in övervakning av ändringar, försöker igen varje 1m:",
"Discard": "Kasta",
"Disconnected": "Frånkopplad",
"Disconnected (Unused)": "Frånkopplad (oanvänd)",
"Discovered": "Upptäckt",
"Discovery": "Annonsering",
"Discovery Failures": "Upptäcktsmisslyckanden",
"Do not restore": "Återställ inte",
"Do not restore all": "Återställ inte allt",
"Do you want to enable watching for changes for all your folders?": "Vill du aktivera håll utkik efter ändringar på alla dina mappar?",
"Do you want to enable watching for changes for all your folders?": "Vill du aktivera bevakning av ändringar på alla dina mappar?",
"Documentation": "Dokumentation",
"Download Rate": "Hämtningshastighet",
"Downloaded": "Hämtat",
@@ -103,23 +105,23 @@
"Enabled": "Aktiverad",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Ange ett icke-negativt antal (t.ex., \"2.35\") och välj en enhet. Procenttalen är som en del av den totala diskstorleken.",
"Enter a non-privileged port number (1024 - 65535).": "Ange ett icke-privilegierat portnummer (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade (\"tcp://ip:port\", \"tcp://host:port\")-adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade adresser (\"tcp://ip:port\", \"tcp://host:port\") eller \"dynamic\" för att utföra automatisk upptäckt av adressen.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade (\"tcp://ip:port\", \"tcp://host:port\") adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade (\"tcp://ip:port\", \"tcp://host:port\") adresser eller \"dynamic\" för att utföra automatisk upptäckt av adressen.",
"Enter ignore patterns, one per line.": "Ange mönster att ignorera, en per rad.",
"Enter up to three octal digits.": "Ange upp till tre oktala siffror.",
"Error": "Fel",
"External File Versioning": "Extern filversionshantering",
"Failed Items": "Misslyckade objekt",
"Failed to load ignore patterns": "Misslyckades med att ladda ignorera mönster",
"Failed to setup, retrying": "Misslyckades med att ställa in, försök igen",
"Failed to load ignore patterns": "Det gick inte att läsa in ignorera mönster",
"Failed to setup, retrying": "Det gick inte att ställa in, försöker igen",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Misslyckande med att ansluta till IPv6-servrar förväntas om ingen IPv6-anslutning finns.",
"File Pull Order": "Filhämtningsprioritering",
"File Versioning": "Filversionshantering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras under sökning efter förändringar. Används på FAT-filsystem.",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen vid byte eller raderas av Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen när de ersätts eller raderas av Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till datumstämplade versioner i en .stversions-mapp när de ersätts eller raderas av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersätts eller raderas av Syncthing.",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen vid byte eller tas bort av Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen när de ersätts eller tas bort av Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till datumstämplade versioner i en .stversions-mapp när de ersätts eller tas bort av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersätts eller tas bort av Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Filer synkroniseras från klustret, men alla ändringar som görs lokalt skickas inte till andra enheter.",
"Filesystem Notifications": "filsystemsnotifieringar",
@@ -133,7 +135,7 @@
"Folder Type": "Mapptyp",
"Folders": "Mappar",
"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(a) återkommande skanningsintervall(er)",
"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",
@@ -170,8 +172,8 @@
"Learn more": "Ta reda på mer",
"Limit": "Gräns",
"Listeners": "Lyssnare",
"Loading data...": "Laddar data...",
"Loading...": "Laddar...",
"Loading data...": "Läser in data...",
"Loading...": "Läser in...",
"Local Additions": "Lokala tillägg",
"Local Discovery": "Lokal annonsering",
"Local State": "Lokalt tillstånd",
@@ -179,13 +181,13 @@
"Locally Changed Items": "Lokalt ändrade objekt",
"Log": "Logg",
"Log tailing paused. Click here to continue.": "Loggning pausad. Klicka här för att fortsätta.",
"Log tailing paused. Scroll to bottom continue.": "Loggning pausad. Rulla till botten för att fortsätta.",
"Log tailing paused. Scroll to the bottom to continue.": "Log fortsättning pausad. Bläddra till botten för att fortsätta.",
"Log tailing paused. Scroll to bottom continue.": "Loggning pausad. Bläddra till botten för att fortsätta.",
"Log tailing paused. Scroll to the bottom to continue.": "Loggning pausad. Bläddra till botten för att fortsätta.",
"Logs": "Loggar",
"Major Upgrade": "Större uppgradering",
"Mass actions": "Massåtgärder",
"Master": "Huvud",
"Maximum Age": "Maximum ålder",
"Maximum Age": "Högsta ålder",
"Metadata Only": "Endast metadata",
"Minimum Free Disk Space": "Minsta ledigt diskutrymme",
"Mod. Device": "Enhet som utförde ändring",
@@ -198,7 +200,7 @@
"Newest First": "Nyast först",
"No": "Nej",
"No File Versioning": "Ingen filversionshantering",
"No files will be deleted as a result of this operation.": "Inga filer kommer att raderas till följd av denna operation.",
"No files will be deleted as a result of this operation.": "Inga filer kommer att tas bort till följd av denna operation.",
"No upgrades": "Inga uppgraderingar",
"Normal": "Normal",
"Notice": "Observera",
@@ -219,14 +221,15 @@
"Pause": "Pausa",
"Pause All": "Pausa alla",
"Paused": "Pausad",
"Paused (Unused)": "Pausad (oanvänd)",
"Pending changes": "Väntar på ändringar",
"Periodic scanning at given interval and disabled watching for changes": "Periodisk skanning i givet intervall och inaktiverad övervakning av ändringar",
"Periodic scanning at given interval and enabled watching for changes": "Periodisk skanning i givet intervall och aktiverad övervakning av ändringar",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning i givet intervall och misslyckades med att ställa in utkik efter ändringar, försök igen var 1m:",
"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.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ställ in ett grafiska gränssnittets användarautentisering och lösenord i inställningsdialogrutan.",
"Please wait": "Var god vänta",
"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 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",
"Preparing to Sync": "Förberedelser för synkronisering",
@@ -312,7 +315,7 @@
"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 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 begäran. Uppdatera sidan eller starta om Syncthing om problemet kvarstår.",
"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 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.",
@@ -330,7 +333,7 @@
"The following items could not be synchronized.": "Följande objekt kunde inte synkroniseras.",
"The following items were changed locally.": "Följande objekt ändrades lokalt.",
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, ställ in på 0 för att behålla versioner för alltid).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Minimum ledigt diskutrymme i procent måste vara en icke negativ siffra mellan 0 och 100 (inklusive).",
"The number of days must be a number and cannot be blank.": "Antalet dagar måste vara en siffra och får inte vara tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "Antalet dagar som filer ligger kvar i papperskorgen. Noll betyder för alltid.",
@@ -373,7 +376,7 @@
"Version": "Version",
"Versions": "Versioner",
"Versions Path": "Sökväg för versioner",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner raderas automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i intervallet.",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner tas bort automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i intervallet.",
"Waiting to Scan": "Väntar på att skanna",
"Waiting to Sync": "Väntar på att synkronisera",
"Waiting to scan": "Väntar på att skanna",
@@ -382,9 +385,9 @@
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en undermapp till en befintlig mapp \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Varning, denna sökväg är en undermapp av en befintlig mapp \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Varning: Om du använder en extern bevakare som {{syncthingInotify}}, bör du se till att den är inaktiverad.",
"Watch for Changes": "Håll utkik efter ändringar",
"Watching for Changes": "Håller utkik efter ändringar",
"Watching for changes discovers most changes without periodic scanning.": "Hålla utkik efter ändringar upptäcker de flesta förändringar utan periodisk skanning.",
"Watch for Changes": "Bevaka ändringar",
"Watching for Changes": "Bevakar ändringar",
"Watching for changes discovers most changes without periodic scanning.": "Bevakar ändringar upptäcker de flesta ändringar utan periodisk skanning.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet, kom ihåg att den här enheten måste läggas till på den andra enheten också.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny mapp, tänk på att mapp-ID knyter ihop mappar mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
"Yes": "Ja",
@@ -393,7 +396,7 @@
"You can read more about the two release channels at the link below.": "Du kan läsa mer om de två publiceringsskanalerna på länken nedan.",
"You have no ignored devices.": "Du har inga ignorerade enheter.",
"You have no ignored folders.": "Du har inga ignorerade mappar.",
"You have unsaved changes. Do you really want to discard them?": "Du har ändringar som inte sparats. Vill du verkligen kassera dem?",
"You have unsaved changes. Do you really want to discard them?": "Du har osparade ändringar. Vill du verkligen kasta dem?",
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"days": "dagar",
"directories": "mappar",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "Коментар, якщо використовується на початку рядка",
"Compression": "Стиснення",
"Configured": "Налаштовано",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "Помилка з’єднання",
"Connection Type": "Тип з*єднання",
"Connections": "З'єднання",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Відключено періодичне сканування та не вдається налаштувати перегляд змін, повторення кожну 1 хв:",
"Discard": "Відхилити",
"Disconnected": "З’єднання відсутнє",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "Виявлено",
"Discovery": "Сервери координації NAT",
"Discovery Failures": "Помилки виявлення",
@@ -219,6 +221,7 @@
"Pause": "Пауза",
"Pause All": "Призупинити все",
"Paused": "Призупинено",
"Paused (Unused)": "Paused (Unused)",
"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",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "注释,在行首使用",
"Compression": "压缩",
"Configured": "已配置",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "连接出错",
"Connection Type": "连接类型",
"Connections": "连接",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "已禁用定期扫描但设置更改监视失败,正在以每 1m 一次重试:",
"Discard": "丢弃",
"Disconnected": "连接已断开",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "已发现",
"Discovery": "设备发现",
"Discovery Failures": "设备发现错误",
@@ -219,6 +221,7 @@
"Pause": "暂停",
"Pause All": "全部暂停",
"Paused": "已暂停",
"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": "正以给定的间隔定期扫描并已启用更改监视",

View File

@@ -50,6 +50,7 @@
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
"Compression": "壓縮",
"Configured": "已設定",
"Connected (Unused)": "Connected (Unused)",
"Connection Error": "連線錯誤",
"Connection Type": "連線類型",
"Connections": "連線",
@@ -82,6 +83,7 @@
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "已停用定期掃描,無法設定觀察變動,每 1 分鐘重試:",
"Discard": "忽略",
"Disconnected": "斷線",
"Disconnected (Unused)": "Disconnected (Unused)",
"Discovered": "已發現",
"Discovery": "探索",
"Discovery Failures": "探索失敗",
@@ -219,6 +221,7 @@
"Pause": "暫停",
"Pause All": "全部暫停",
"Paused": "暫停",
"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": "在一定的時間間隔,定期掃描及啟用觀察變動",

View File

@@ -405,7 +405,7 @@
<tr ng-if="folder.type == 'receiveonly' && canRevert(folder.id)">
<th><span class="fas fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Locally Changed Items</span></th>
<td class="text-right">
<a href="" ng-click="showLocalChanged(folder.id)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyBytes | binary}}B</a>
<a href="" ng-click="showLocalChanged(folder.id)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="folder.type != 'sendreceive'">
@@ -656,12 +656,14 @@
<identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon>
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right text-{{deviceClass(deviceCfg)}}">
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="unused-insync"><span class="hidden-xs" translate>Connected (Unused)</span><span class="visible-xs" aria-label="{{'Connected (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)
</span>
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs" aria-label="{{'Paused' | translate}}"><i class="fas fa-fw fa-pause"></i></span></span>
<span ng-switch-when="unused-paused"><span class="hidden-xs" translate>Paused (Unused)</span><span class="visible-xs" aria-label="{{'Paused (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs" aria-label="{{'Disconnected' | translate}}"><i class="fas fa-fw fa-power-off"></i></span></span>
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs" aria-label="{{'Unused' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
<span ng-switch-when="unused-disconnected"><span class="hidden-xs" translate>Disconnected (Unused)</span><span class="visible-xs" aria-label="{{'Disconnected (Unused)' | translate}}"><i class="fas fa-fw fa-unlink"></i></span></span>
</span>
<span>{{deviceName(deviceCfg)}}</span>
</h4>

View File

@@ -14,7 +14,7 @@
<p translate>Copyright &copy; 2014-2019 the following Contributors:</p>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, dependabot-preview[bot], Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alessandro G., Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, André Colomb, Anjan Momi, Antoine Lamielle, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Han Boetes, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jacob, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Tilli, Mike Boone, MikeLund, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Niels Peter Roest, Nils Jakobi, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tomasz Wilczyński, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, otbutz, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, André Colomb, Anjan Momi, Antoine Lamielle, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Han Boetes, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jacob, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan Cross, Jose Manuel Delicado, Jörg Thalheim, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tomasz Wilczyński, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, chenrui, chucic, dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, mv1005, otbutz, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
</div>
</div>
<hr />

View File

@@ -42,9 +42,8 @@ angular.module('syncthing.core')
$scope.folderStats = {};
$scope.progress = {};
$scope.version = {};
$scope.needed = [];
$scope.neededCurrentPage = 1;
$scope.neededPageSize = 10;
$scope.needed = {}
$scope.neededFolder = '';
$scope.failed = {};
$scope.localChanged = {};
$scope.scanProgress = {};
@@ -299,12 +298,12 @@ angular.module('syncthing.core')
for (var folder in $scope.progress) {
if (!(folder in progress)) {
if ($scope.neededFolder === folder) {
refreshNeed(folder);
$scope.refreshNeed($scope.needed.page, $scope.needed.perpage);
}
} else if ($scope.neededFolder === folder) {
for (file in $scope.progress[folder]) {
if (!(file in progress[folder])) {
refreshNeed(folder);
$scope.refreshNeed($scope.needed.page, $scope.needed.perpage);
break;
}
}
@@ -580,18 +579,16 @@ angular.module('syncthing.core')
}).error($scope.emitHTTPError);
}
function refreshNeed(folder) {
$scope.refreshNeed = function (page, perpage) {
if (!$scope.neededFolder) {
return;
}
var url = urlbase + "/db/need?folder=" + encodeURIComponent(folder);
url += "&page=" + $scope.neededCurrentPage;
url += "&perpage=" + $scope.neededPageSize;
var url = urlbase + "/db/need?folder=" + encodeURIComponent($scope.neededFolder);
url += "&page=" + page;
url += "&perpage=" + perpage;
$http.get(url).success(function (data) {
if ($scope.neededFolder === folder) {
console.log("refreshNeed", folder, data);
parseNeeded(data);
}
console.log("refreshNeed", $scope.neededFolder, data);
parseNeeded(data);
}).error($scope.emitHTTPError);
}
@@ -611,6 +608,7 @@ angular.module('syncthing.core')
}
function parseNeeded(data) {
$scope.needed = data;
var merged = [];
data.progress.forEach(function (item) {
item.type = "progress";
@@ -627,7 +625,7 @@ angular.module('syncthing.core')
item.action = needAction(item);
merged.push(item);
});
$scope.needed = merged;
$scope.needed.items = merged;
}
function pathJoin(base, name) {
@@ -682,16 +680,6 @@ angular.module('syncthing.core')
});
};
$scope.neededPageChanged = function (page) {
$scope.neededCurrentPage = page;
refreshNeed($scope.neededFolder);
};
$scope.neededChangePageSize = function (perpage) {
$scope.neededPageSize = perpage;
refreshNeed($scope.neededFolder);
};
$scope.refreshFailed = function (page, perpage) {
if (!$scope.failed || !$scope.failed.folder) {
return;
@@ -938,8 +926,10 @@ angular.module('syncthing.core')
};
$scope.deviceStatus = function (deviceCfg) {
var status = '';
if ($scope.deviceFolders(deviceCfg).length === 0) {
return 'unused';
status = 'unused-';
}
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
@@ -947,27 +937,22 @@ angular.module('syncthing.core')
}
if (deviceCfg.paused) {
return 'paused';
return status + 'paused';
}
if ($scope.connections[deviceCfg.deviceID].connected) {
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
return 'insync';
return status + 'insync';
} else {
return 'syncing';
}
}
// Disconnected
return 'disconnected';
return status + 'disconnected';
};
$scope.deviceClass = function (deviceCfg) {
if ($scope.deviceFolders(deviceCfg).length === 0) {
// Unused
return 'warning';
}
if (typeof $scope.connections[deviceCfg.deviceID] === 'undefined') {
return 'info';
}
@@ -1825,7 +1810,7 @@ angular.module('syncthing.core')
$scope.saveFolder = function () {
$('#editFolder').modal('hide');
var folderCfg = $scope.currentFolder;
var folderCfg = angular.copy($scope.currentFolder);
folderCfg.devices = [];
folderCfg.selectedDevices[$scope.myID] = true;
for (var deviceID in folderCfg.selectedDevices) {
@@ -2226,11 +2211,10 @@ angular.module('syncthing.core')
$scope.showNeed = function (folder) {
$scope.neededFolder = folder;
refreshNeed(folder);
$scope.refreshNeed(1, 10);
$('#needed').modal().one('hidden.bs.modal', function () {
$scope.neededFolder = undefined;
$scope.needed = undefined;
$scope.neededCurrentPage = 1;
$scope.neededFolder = '';
});
};
@@ -2380,8 +2364,8 @@ angular.module('syncthing.core')
$scope.bumpFile = function (folder, file) {
var url = urlbase + "/db/prio?folder=" + encodeURIComponent(folder) + "&file=" + encodeURIComponent(file);
// In order to get the right view of data in the response.
url += "&page=" + $scope.neededCurrentPage;
url += "&perpage=" + $scope.neededPageSize;
url += "&page=" + $scope.needed.page;
url += "&perpage=" + $scope.needed.perpage;
$http.post(url).success(function (data) {
if ($scope.neededFolder === folder) {
console.log("bumpFile", folder, data);

View File

@@ -46,6 +46,25 @@
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="ldapHeading" data-toggle="collapse" data-parent="#advancedAccordion" href="#ldapConfig" aria-expanded="false" aria-controls="ldapConfig" style="cursor: pointer;">
<h4 class="panel-title" tabindex="0" translate>LDAP</h4>
</div>
<div id="ldapConfig" class="panel-collapse collapse" role="tabpanel" aria-labelledby="ldapHeading">
<div class="panel-body">
<form class="form-horizontal" role="form">
<div ng-repeat="(key, value) in advancedConfig.ldap" ng-if="inputTypeFor(key, value) != 'skip'" class="form-group">
<label for="ldapInput{{$index}}" class="col-sm-4 control-label">{{key | uncamel}}</label>
<div class="col-sm-8">
<input ng-if="inputTypeFor(key, value) == 'list'" id="ldapInput{{$index}}" class="form-control" type="text" ng-model="advancedConfig.ldap[key]" ng-list />
<input ng-if="inputTypeFor(key, value) != 'list'" id="ldapInput{{$index}}" class="form-control" type="{{inputTypeFor(key, value)}}" ng-model="advancedConfig.ldap[key]" />
</div>
</div>
</form>
</div>
</div>
</div>
<div class="panel panel-default" ng-repeat="folder in advancedConfig.folders">
<div class="panel-heading" role="tab" id="folder{{$index}}Heading" data-toggle="collapse" data-parent="#advancedAccordion" href="#folder{{$index}}Config" aria-expanded="false" aria-controls="folder{{$index}}Config" style="cursor: pointer;">
<h4 ng-if="folder.label.length == 0" class="panel-title" tabindex="0">

View File

@@ -14,7 +14,7 @@
<table class="table table-striped table-condensed">
<tr dir-paginate="f in needed | itemsPerPage: neededPageSize" current-page="neededCurrentPage" total-items="model[neededFolder].needTotalItems" pagination-id="needed">
<tr dir-paginate="f in needed.items | itemsPerPage: needed.perpage" current-page="needed.page" total-items="model[neededFolder].needTotalItems" pagination-id="needed">
<!-- Icon -->
<td class="small-data col-xs-2">
@@ -56,10 +56,10 @@
</tr>
</table>
<dir-pagination-controls on-page-change="neededPageChanged(newPageNumber)" pagination-id="needed"></dir-pagination-controls>
<dir-pagination-controls on-page-change="refreshNeed(newPageNumber, needed.perpage)" pagination-id="needed" ></dir-pagination-controls>
<ul class="pagination pull-right">
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: neededPageSize == option }">
<a href="#" ng-click="neededChangePageSize(option)">{{option}}</a>
<li ng-repeat="option in [10, 25, 50]" ng-class="{ active: needed.perpage == option }">
<a href="#" ng-click="refreshNeed(needed.page, option)">{{option}}</a>
</li>
</ul>
<div class="clearfix"></div>

View File

@@ -11,16 +11,17 @@ import (
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"net/http"
"strings"
"time"
ldap "github.com/go-ldap/ldap/v3"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sync"
"golang.org/x/crypto/bcrypt"
ldap "gopkg.in/ldap.v2"
)
var (
@@ -130,10 +131,16 @@ func authStatic(username string, password string, configUser string, configPassw
func authLDAP(username string, password string, cfg config.LDAPConfiguration) bool {
address := cfg.Address
hostname, _, err := net.SplitHostPort(address)
if err != nil {
hostname = address
}
var connection *ldap.Conn
var err error
if cfg.Transport == config.LDAPTransportTLS {
connection, err = ldap.DialTLS("tcp", address, &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify})
connection, err = ldap.DialTLS("tcp", address, &tls.Config{
ServerName: hostname,
InsecureSkipVerify: cfg.InsecureSkipVerify,
})
} else {
connection, err = ldap.Dial("tcp", address)
}
@@ -159,6 +166,35 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
return false
}
if cfg.SearchFilter == "" && cfg.SearchBaseDN == "" {
// We're done here.
return true
}
if cfg.SearchFilter == "" || cfg.SearchBaseDN == "" {
l.Warnln("LDAP configuration: both searchFilter and searchBaseDN must be set, or neither.")
return false
}
// If a search filter and search base is set we do an LDAP search for
// the user. If this matches precisely one user then we are good to go.
// The search filter uses the same %s interpolation as the bind DN.
searchString := fmt.Sprintf(cfg.SearchFilter, username)
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
const timeLimit = 60 // Search for up to a minute...
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
res, err := connection.Search(searchReq)
if err != nil {
l.Warnln("LDAP Search:", err)
return false
}
if len(res.Entries) != 1 {
l.Infof("Wrong number of LDAP search results, %d != 1", len(res.Entries))
return false
}
return true
}

View File

@@ -11,6 +11,8 @@ type LDAPConfiguration struct {
BindDN string `xml:"bindDN,omitempty" json:"bindDN"`
Transport LDAPTransport `xml:"transport,omitempty" json:"transport"`
InsecureSkipVerify bool `xml:"insecureSkipVerify,omitempty" json:"insecureSkipVerify" default:"false"`
SearchBaseDN string `xml:"searchBaseDN,omitempty" json:"searchBaseDN"`
SearchFilter string `xml:"searchFilter,omitempty" json:"searchFilter"`
}
func (c LDAPConfiguration) Copy() LDAPConfiguration {

View File

@@ -8,13 +8,15 @@ package db
import (
"bytes"
"context"
"encoding/binary"
"os"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
"github.com/thejerf/suture"
"github.com/willf/bloom"
)
@@ -31,47 +33,70 @@ const (
// Use indirection for the block list when it exceeds this many entries
blocksIndirectionCutoff = 3
recheckDefaultInterval = 30 * 24 * time.Hour
)
var indirectGCInterval = indirectGCDefaultInterval
func init() {
if dur, err := time.ParseDuration(os.Getenv("STGCINDIRECTEVERY")); err == nil {
indirectGCInterval = dur
}
}
// Lowlevel is the lowest level database interface. It has a very simple
// purpose: hold the actual backend database, and the in-memory state
// that belong to that database. In the same way that a single on disk
// database can only be opened once, there should be only one Lowlevel for
// any given backend.
type Lowlevel struct {
*suture.Supervisor
backend.Backend
folderIdx *smallIndex
deviceIdx *smallIndex
keyer keyer
gcMut sync.RWMutex
gcKeyCount int
gcStop chan struct{}
folderIdx *smallIndex
deviceIdx *smallIndex
keyer keyer
gcMut sync.RWMutex
gcKeyCount int
indirectGCInterval time.Duration
recheckInterval time.Duration
}
func NewLowlevel(backend backend.Backend) *Lowlevel {
func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel {
db := &Lowlevel{
Backend: backend,
folderIdx: newSmallIndex(backend, []byte{KeyTypeFolderIdx}),
deviceIdx: newSmallIndex(backend, []byte{KeyTypeDeviceIdx}),
gcMut: sync.NewRWMutex(),
gcStop: make(chan struct{}),
Supervisor: suture.New("db.Lowlevel", suture.Spec{
// Only log restarts in debug mode.
Log: func(line string) {
l.Debugln(line)
},
PassThroughPanics: true,
}),
Backend: backend,
folderIdx: newSmallIndex(backend, []byte{KeyTypeFolderIdx}),
deviceIdx: newSmallIndex(backend, []byte{KeyTypeDeviceIdx}),
gcMut: sync.NewRWMutex(),
indirectGCInterval: indirectGCDefaultInterval,
recheckInterval: recheckDefaultInterval,
}
for _, opt := range opts {
opt(db)
}
db.keyer = newDefaultKeyer(db.folderIdx, db.deviceIdx)
go db.gcRunner()
db.Add(util.AsService(db.gcRunner, "db.Lowlevel/gcRunner"))
return db
}
func (db *Lowlevel) Close() error {
close(db.gcStop)
return db.Backend.Close()
type Option func(*Lowlevel)
// WithRecheckInterval sets the time interval in between metadata recalculations
// and consistency checks.
func WithRecheckInterval(dur time.Duration) Option {
return func(db *Lowlevel) {
if dur > 0 {
db.recheckInterval = dur
}
}
}
// WithIndirectGCInterval sets the time interval in between GC runs.
func WithIndirectGCInterval(dur time.Duration) Option {
return func(db *Lowlevel) {
if dur > 0 {
db.indirectGCInterval = dur
}
}
}
// ListFolders returns the list of folders currently in the database
@@ -494,12 +519,12 @@ func (db *Lowlevel) dropPrefix(prefix []byte) error {
return t.Commit()
}
func (db *Lowlevel) gcRunner() {
func (db *Lowlevel) gcRunner(ctx context.Context) {
// Calculate the time for the next GC run. Even if we should run GC
// directly, give the system a while to get up and running and do other
// stuff first. (We might have migrations and stuff which would be
// better off running before GC.)
next := db.timeUntil(indirectGCTimeKey, indirectGCInterval)
next := db.timeUntil(indirectGCTimeKey, db.indirectGCInterval)
if next < time.Minute {
next = time.Minute
}
@@ -509,14 +534,14 @@ func (db *Lowlevel) gcRunner() {
for {
select {
case <-db.gcStop:
case <-ctx.Done():
return
case <-t.C:
if err := db.gcIndirect(); err != nil {
if err := db.gcIndirect(ctx); err != nil {
l.Warnln("Database indirection GC failed:", err)
}
db.recordTime(indirectGCTimeKey)
t.Reset(db.timeUntil(indirectGCTimeKey, indirectGCInterval))
t.Reset(db.timeUntil(indirectGCTimeKey, db.indirectGCInterval))
}
}
}
@@ -541,7 +566,7 @@ func (db *Lowlevel) timeUntil(key string, every time.Duration) time.Duration {
return sleepTime
}
func (db *Lowlevel) gcIndirect() error {
func (db *Lowlevel) gcIndirect(ctx context.Context) error {
// The indirection GC uses bloom filters to track used block lists and
// versions. This means iterating over all items, adding their hashes to
// the filter, then iterating over the indirected items and removing
@@ -581,6 +606,12 @@ func (db *Lowlevel) gcIndirect() error {
}
defer it.Release()
for it.Next() {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
var bl BlocksHashOnly
if err := bl.Unmarshal(it.Value()); err != nil {
return err
@@ -604,6 +635,12 @@ func (db *Lowlevel) gcIndirect() error {
defer it.Release()
matchedBlocks := 0
for it.Next() {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
key := blockListKey(it.Key())
if blockFilter.Test(key.BlocksHash()) {
matchedBlocks++
@@ -670,7 +707,7 @@ func (db *Lowlevel) loadMetadataTracker(folder string) *metadataTracker {
return db.getMetaAndCheck(folder)
}
if age := time.Since(meta.Created()); age > databaseRecheckInterval {
if age := time.Since(meta.Created()); age > db.recheckInterval {
l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age)
return db.getMetaAndCheck(folder)
}

View File

@@ -16,21 +16,16 @@ import (
// NamespacedKV is a simple key-value store using a specific namespace within
// a leveldb.
type NamespacedKV struct {
db *Lowlevel
prefix []byte
db backend.Backend
prefix string
}
// NewNamespacedKV returns a new NamespacedKV that lives in the namespace
// specified by the prefix.
func NewNamespacedKV(db *Lowlevel, prefix string) *NamespacedKV {
prefixBs := []byte(prefix)
// After the conversion from string the cap will be larger than the len (in Go 1.11.5,
// 32 bytes cap for small strings). We need to cut it down to ensure append() calls
// on the prefix make a new allocation.
prefixBs = prefixBs[:len(prefixBs):len(prefixBs)]
func NewNamespacedKV(db backend.Backend, prefix string) *NamespacedKV {
return &NamespacedKV{
db: db,
prefix: prefixBs,
prefix: prefix,
}
}
@@ -130,7 +125,7 @@ func (n NamespacedKV) Delete(key string) error {
}
func (n NamespacedKV) prefixedKey(key string) []byte {
return append(n.prefix, []byte(key)...)
return []byte(n.prefix + key)
}
// Well known namespaces that can be instantiated without knowing the key
@@ -138,18 +133,18 @@ func (n NamespacedKV) prefixedKey(key string) []byte {
// NewDeviceStatisticsNamespace creates a KV namespace for device statistics
// for the given device.
func NewDeviceStatisticsNamespace(db *Lowlevel, device string) *NamespacedKV {
func NewDeviceStatisticsNamespace(db backend.Backend, device string) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeDeviceStatistic)+device)
}
// NewFolderStatisticsNamespace creates a KV namespace for folder statistics
// for the given folder.
func NewFolderStatisticsNamespace(db *Lowlevel, folder string) *NamespacedKV {
func NewFolderStatisticsNamespace(db backend.Backend, folder string) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeFolderStatistic)+folder)
}
// NewMiscDateNamespace creates a KV namespace for miscellaneous metadata.
func NewMiscDataNamespace(db *Lowlevel) *NamespacedKV {
func NewMiscDataNamespace(db backend.Backend) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeMiscData))
}

View File

@@ -167,7 +167,7 @@ func reset(n *NamespacedKV) {
}
defer tr.Release()
it, err := tr.NewPrefixIterator(n.prefix)
it, err := tr.NewPrefixIterator([]byte(n.prefix))
if err != nil {
return
}

View File

@@ -13,7 +13,6 @@
package db
import (
"os"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
@@ -62,14 +61,6 @@ type FileIntf interface {
// continue iteration, false to stop.
type Iterator func(f FileIntf) bool
var databaseRecheckInterval = 30 * 24 * time.Hour
func init() {
if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
databaseRecheckInterval = dur
}
}
func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet {
return &FileSet{
folder: folder,
@@ -342,9 +333,7 @@ func (s *Snapshot) NeedSize() Counts {
return result
}
// LocalChangedFiles returns a paginated list of currently needed files in
// progress, queued, and to be queued on next puller iteration, as well as the
// total number of files currently needed.
// LocalChangedFiles returns a paginated list of files that were changed locally.
func (s *Snapshot) LocalChangedFiles(page, perpage int) []FileInfoTruncated {
if s.ReceiveOnlyChangedSize().TotalItems() == 0 {
return nil

View File

@@ -140,6 +140,14 @@ func (f FileInfoTruncated) ConvertToDeletedFileInfo(by protocol.ShortID) protoco
return file
}
// ConvertDeletedToFileInfo converts a deleted truncated file info to a regular file info
func (f FileInfoTruncated) ConvertDeletedToFileInfo() protocol.FileInfo {
if !f.Deleted {
panic("ConvertDeletedToFileInfo must only be called on deleted items")
}
return f.copyToFileInfo()
}
// copyToFileInfo just copies all members of FileInfoTruncated to protocol.FileInfo
func (f FileInfoTruncated) copyToFileInfo() protocol.FileInfo {
return protocol.FileInfo{

View File

@@ -30,6 +30,10 @@ type BasicFilesystem struct {
}
func newBasicFilesystem(root string) *BasicFilesystem {
if root == "" {
root = "." // Otherwise "" becomes "/" below
}
// The reason it's done like this:
// C: -> C:\ -> C:\ (issue that this is trying to fix)
// C:\somedir -> C:\somedir\ -> C:\somedir

View File

@@ -514,6 +514,10 @@ func TestNewBasicFilesystem(t *testing.T) {
t.Skip("non-windows root paths")
}
currentDir, err := filepath.Abs(".")
if err != nil {
t.Fatal(err)
}
testCases := []struct {
input string
expectedRoot string
@@ -521,7 +525,8 @@ func TestNewBasicFilesystem(t *testing.T) {
}{
{"/foo/bar/baz", "/foo/bar/baz", "/foo/bar/baz"},
{"/foo/bar/baz/", "/foo/bar/baz", "/foo/bar/baz"},
{"", "/", "/"},
{"", currentDir, currentDir},
{".", currentDir, currentDir},
{"/", "/", "/"},
}

View File

@@ -17,33 +17,35 @@ type errorFilesystem struct {
uri string
}
func (fs *errorFilesystem) Chmod(name string, mode FileMode) error { return fs.err }
func (fs *errorFilesystem) Lchown(name string, uid, gid int) error { return fs.err }
func (fs *errorFilesystem) Chtimes(name string, atime time.Time, mtime time.Time) error { return fs.err }
func (fs *errorFilesystem) Create(name string) (File, error) { return nil, fs.err }
func (fs *errorFilesystem) CreateSymlink(target, name string) error { return fs.err }
func (fs *errorFilesystem) DirNames(name string) ([]string, error) { return nil, fs.err }
func (fs *errorFilesystem) Lstat(name string) (FileInfo, error) { return nil, fs.err }
func (fs *errorFilesystem) Mkdir(name string, perm FileMode) error { return fs.err }
func (fs *errorFilesystem) MkdirAll(name string, perm FileMode) error { return fs.err }
func (fs *errorFilesystem) Open(name string) (File, error) { return nil, fs.err }
func (fs *errorFilesystem) OpenFile(string, int, FileMode) (File, error) { return nil, fs.err }
func (fs *errorFilesystem) ReadSymlink(name string) (string, error) { return "", fs.err }
func (fs *errorFilesystem) Remove(name string) error { return fs.err }
func (fs *errorFilesystem) RemoveAll(name string) error { return fs.err }
func (fs *errorFilesystem) Rename(oldname, newname string) error { return fs.err }
func (fs *errorFilesystem) Stat(name string) (FileInfo, error) { return nil, fs.err }
func (fs *errorFilesystem) SymlinksSupported() bool { return false }
func (fs *errorFilesystem) Walk(root string, walkFn WalkFunc) error { return fs.err }
func (fs *errorFilesystem) Unhide(name string) error { return fs.err }
func (fs *errorFilesystem) Hide(name string) error { return fs.err }
func (fs *errorFilesystem) Glob(pattern string) ([]string, error) { return nil, fs.err }
func (fs *errorFilesystem) SyncDir(name string) error { return fs.err }
func (fs *errorFilesystem) Roots() ([]string, error) { return nil, fs.err }
func (fs *errorFilesystem) Usage(name string) (Usage, error) { return Usage{}, fs.err }
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
func (fs *errorFilesystem) URI() string { return fs.uri }
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
func (fs *errorFilesystem) Chmod(name string, mode FileMode) error { return fs.err }
func (fs *errorFilesystem) Lchown(name string, uid, gid int) error { return fs.err }
func (fs *errorFilesystem) Chtimes(name string, atime time.Time, mtime time.Time) error {
return fs.err
}
func (fs *errorFilesystem) Create(name string) (File, error) { return nil, fs.err }
func (fs *errorFilesystem) CreateSymlink(target, name string) error { return fs.err }
func (fs *errorFilesystem) DirNames(name string) ([]string, error) { return nil, fs.err }
func (fs *errorFilesystem) Lstat(name string) (FileInfo, error) { return nil, fs.err }
func (fs *errorFilesystem) Mkdir(name string, perm FileMode) error { return fs.err }
func (fs *errorFilesystem) MkdirAll(name string, perm FileMode) error { return fs.err }
func (fs *errorFilesystem) Open(name string) (File, error) { return nil, fs.err }
func (fs *errorFilesystem) OpenFile(string, int, FileMode) (File, error) { return nil, fs.err }
func (fs *errorFilesystem) ReadSymlink(name string) (string, error) { return "", fs.err }
func (fs *errorFilesystem) Remove(name string) error { return fs.err }
func (fs *errorFilesystem) RemoveAll(name string) error { return fs.err }
func (fs *errorFilesystem) Rename(oldname, newname string) error { return fs.err }
func (fs *errorFilesystem) Stat(name string) (FileInfo, error) { return nil, fs.err }
func (fs *errorFilesystem) SymlinksSupported() bool { return false }
func (fs *errorFilesystem) Walk(root string, walkFn WalkFunc) error { return fs.err }
func (fs *errorFilesystem) Unhide(name string) error { return fs.err }
func (fs *errorFilesystem) Hide(name string) error { return fs.err }
func (fs *errorFilesystem) Glob(pattern string) ([]string, error) { return nil, fs.err }
func (fs *errorFilesystem) SyncDir(name string) error { return fs.err }
func (fs *errorFilesystem) Roots() ([]string, error) { return nil, fs.err }
func (fs *errorFilesystem) Usage(name string) (Usage, error) { return Usage{}, fs.err }
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
func (fs *errorFilesystem) URI() string { return fs.uri }
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
return nil, nil, fs.err
}

View File

@@ -67,16 +67,11 @@ func (p Pattern) allowsSkippingIgnoredDirs() bool {
if p.pattern[0] != '/' {
return false
}
// Double asterisk everywhere in the path except at the end is bad
if strings.Contains(strings.TrimSuffix(p.pattern, "**"), "**") {
if strings.Contains(p.pattern[1:], "/") {
return false
}
// Any wildcards anywhere except for the last path component are bad
lastSep := strings.LastIndex(p.pattern, "/")
if lastSep == -1 {
return true
}
return p.pattern[:lastSep] == glob.QuoteMeta(p.pattern[:lastSep])
// Double asterisk everywhere in the path except at the end is bad
return !strings.Contains(strings.TrimSuffix(p.pattern, "**"), "**")
}
type Result uint8

View File

@@ -1110,10 +1110,10 @@ func TestSkipIgnoredDirs(t *testing.T) {
{`!/t*t`, true},
{`!/t?t`, true},
{`!/**`, true},
{`!/parent/test`, true},
{`!/parent/t[eih]t`, true},
{`!/parent/t*t`, true},
{`!/parent/t?t`, true},
{`!/parent/test`, false},
{`!/parent/t[eih]t`, false},
{`!/parent/t*t`, false},
{`!/parent/t?t`, false},
{`!/**.mp3`, false},
{`!/pa*nt/test`, false},
{`!/pa[sdf]nt/t[eih]t`, false},
@@ -1150,6 +1150,17 @@ func TestSkipIgnoredDirs(t *testing.T) {
if !pats.SkipIgnoredDirs() {
t.Error("SkipIgnoredDirs should be true")
}
stignore = `
!/foo/ign*
*
`
if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
t.Fatal(err)
}
if pats.SkipIgnoredDirs() {
t.Error("SkipIgnoredDirs should be false")
}
}
func TestEmptyPatterns(t *testing.T) {

View File

@@ -39,11 +39,23 @@ const (
type BaseDirEnum string
const (
// Overridden by -home flag
ConfigBaseDir BaseDirEnum = "config"
HomeBaseDir BaseDirEnum = "home"
DataBaseDir BaseDirEnum = "data"
// User's home directory, *not* -home flag
UserHomeBaseDir BaseDirEnum = "userHome"
)
// Platform dependent directories
var baseDirs = make(map[BaseDirEnum]string, 3)
func init() {
userHome := userHomeDir()
config := defaultConfigDir(userHome)
baseDirs[UserHomeBaseDir] = userHome
baseDirs[ConfigBaseDir] = config
baseDirs[DataBaseDir] = defaultDataDir(userHome, config)
err := expandLocations()
if err != nil {
fmt.Println(err)
@@ -68,11 +80,7 @@ func GetBaseDir(baseDir BaseDirEnum) string {
return baseDirs[baseDir]
}
// Platform dependent directories
var baseDirs = map[BaseDirEnum]string{
ConfigBaseDir: defaultConfigDir(), // Overridden by -home flag
HomeBaseDir: homeDir(), // User's home directory, *not* -home flag
}
var databaseDirname = "index-v0.14.0.db"
// Use the variables from baseDirs here
var locationTemplates = map[LocationEnum]string{
@@ -81,13 +89,13 @@ var locationTemplates = map[LocationEnum]string{
KeyFile: "${config}/key.pem",
HTTPSCertFile: "${config}/https-cert.pem",
HTTPSKeyFile: "${config}/https-key.pem",
Database: "${config}/index-v0.14.0.db",
LogFile: "${config}/syncthing.log", // -logfile on Windows
CsrfTokens: "${config}/csrftokens.txt",
PanicLog: "${config}/panic-${timestamp}.log",
AuditLog: "${config}/audit-${timestamp}.log",
Database: "${data}/" + databaseDirname,
LogFile: "${data}/syncthing.log", // -logfile on Windows
CsrfTokens: "${data}/csrftokens.txt",
PanicLog: "${data}/panic-${timestamp}.log",
AuditLog: "${data}/audit-${timestamp}.log",
GUIAssets: "${config}/gui",
DefFolder: "${home}/Sync",
DefFolder: "${userHome}/Sync",
}
var locations = make(map[LocationEnum]string)
@@ -114,7 +122,7 @@ func expandLocations() error {
// defaultConfigDir returns the default configuration directory, as figured
// out by various the environment variables present on each platform, or dies
// trying.
func defaultConfigDir() string {
func defaultConfigDir(userHome string) string {
switch runtime.GOOS {
case "windows":
if p := os.Getenv("LocalAppData"); p != "" {
@@ -123,34 +131,53 @@ func defaultConfigDir() string {
return filepath.Join(os.Getenv("AppData"), "Syncthing")
case "darwin":
dir, err := fs.ExpandTilde("~/Library/Application Support/Syncthing")
if err != nil {
fmt.Println(err)
panic("Failed to get default config dir")
}
return dir
return filepath.Join(userHome, "Library/Application Support/Syncthing")
default:
if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
return filepath.Join(xdgCfg, "syncthing")
}
dir, err := fs.ExpandTilde("~/.config/syncthing")
if err != nil {
fmt.Println(err)
panic("Failed to get default config dir")
}
return dir
return filepath.Join(userHome, ".config/syncthing")
}
}
// homeDir returns the user's home directory, or dies trying.
func homeDir() string {
home, err := fs.ExpandTilde("~")
// defaultDataDir returns the default data directory, which usually is the
// config directory but might be something else.
func defaultDataDir(userHome, config string) string {
switch runtime.GOOS {
case "windows", "darwin":
return config
default:
// If a database exists at the "normal" location, use that anyway.
if _, err := os.Lstat(filepath.Join(config, databaseDirname)); err == nil {
return config
}
// Always use this env var, as it's explicitly set by the user
if xdgHome := os.Getenv("XDG_DATA_HOME"); xdgHome != "" {
return filepath.Join(xdgHome, "syncthing")
}
// Only use the XDG default, if a syncthing specific dir already
// exists. Existence of ~/.local/share is not deemed enough, as
// it may also exist erroneously on non-XDG systems.
xdgDefault := filepath.Join(userHome, ".local/share/syncthing")
if _, err := os.Lstat(xdgDefault); err == nil {
return xdgDefault
}
// FYI: XDG_DATA_DIRS is not relevant, as it is for system-wide
// data dirs, not user specific ones.
return config
}
}
// userHomeDir returns the user's home directory, or dies trying.
func userHomeDir() string {
userHome, err := fs.ExpandTilde("~")
if err != nil {
fmt.Println(err)
panic("Failed to get user home dir")
}
return home
return userHome
}
func GetTimestamped(key LocationEnum) string {

View File

@@ -50,7 +50,6 @@ type folder struct {
scanInterval time.Duration
scanTimer *time.Timer
scanNow chan rescanRequest
scanDelay chan time.Duration
initialScanFinished chan struct{}
scanErrors []FileError
@@ -58,6 +57,8 @@ type folder struct {
pullScheduled chan struct{}
doInSyncChan chan syncRequest
watchCancel context.CancelFunc
watchChan chan []string
restartWatchChan chan struct{}
@@ -67,9 +68,9 @@ type folder struct {
puller puller
}
type rescanRequest struct {
subdirs []string
err chan error
type syncRequest struct {
fn func() error
err chan error
}
type puller interface {
@@ -90,13 +91,14 @@ func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg conf
scanInterval: time.Duration(cfg.RescanIntervalS) * time.Second,
scanTimer: time.NewTimer(time.Millisecond), // The first scan should be done immediately.
scanNow: make(chan rescanRequest),
scanDelay: make(chan time.Duration),
initialScanFinished: make(chan struct{}),
scanErrorsMut: sync.NewMutex(),
pullScheduled: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a pull if we're busy when it comes.
doInSyncChan: make(chan syncRequest),
watchCancel: func() {},
restartWatchChan: make(chan struct{}, 1),
watchMut: sync.NewMutex(),
@@ -172,9 +174,9 @@ func (f *folder) serve(ctx context.Context) {
l.Debugln(f, "Scanning due to timer")
f.scanTimerFired()
case req := <-f.scanNow:
l.Debugln(f, "Scanning due to request")
req.err <- f.scanSubdirs(req.subdirs)
case req := <-f.doInSyncChan:
l.Debugln(f, "Running something due to request")
req.err <- req.fn()
case next := <-f.scanDelay:
l.Debugln(f, "Delaying scan")
@@ -224,13 +226,19 @@ func (f *folder) Jobs(_, _ int) ([]string, []string, int) {
func (f *folder) Scan(subdirs []string) error {
<-f.initialScanFinished
req := rescanRequest{
subdirs: subdirs,
err: make(chan error),
return f.doInSync(func() error { return f.scanSubdirs(subdirs) })
}
// doInSync allows to run functions synchronously in folder.serve from exported,
// asynchronously called methods.
func (f *folder) doInSync(fn func() error) error {
req := syncRequest{
fn: fn,
err: make(chan error, 1),
}
select {
case f.scanNow <- req:
case f.doInSyncChan <- req:
return <-req.err
case <-f.ctx.Done():
return f.ctx.Err()
@@ -548,6 +556,20 @@ func (f *folder) scanSubdirs(subDirs []string) error {
batch.append(nf)
changes++
}
// Check for deleted, locally changed items that noone else has.
if f.localFlags&protocol.FlagLocalReceiveOnly == 0 {
return true
}
if !fi.IsDeleted() || !fi.IsReceiveOnlyChanged() || len(snap.Availability(fi.FileName())) > 0 {
return true
}
nf := fi.(db.FileInfoTruncated).ConvertDeletedToFileInfo()
nf.LocalFlags = 0
nf.Version = protocol.Vector{}
batch.append(nf)
changes++
return true
})

View File

@@ -64,6 +64,12 @@ func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matche
}
func (f *receiveOnlyFolder) Revert() {
f.doInSync(func() error { f.revert(); return nil })
}
func (f *receiveOnlyFolder) revert() {
l.Infof("Reverting folder %v", f.Description)
f.setState(FolderScanning)
defer f.setState(FolderIdle)
@@ -89,6 +95,7 @@ func (f *receiveOnlyFolder) Revert() {
return true
}
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
if len(fi.Version.Counters) == 1 && fi.Version.Counters[0].ID == f.shortID {
// We are the only device mentioned in the version vector so the
// file must originate here. A revert then means to delete it.
@@ -113,7 +120,6 @@ func (f *receiveOnlyFolder) Revert() {
// either, so we will not create a conflict copy of our local
// changes.
fi.Version = protocol.Vector{}
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
}
batch = append(batch, fi)

View File

@@ -263,6 +263,69 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
}
}
func TestRecvOnlyDeletedRemoteDrop(t *testing.T) {
// Get us a model up and running
m, f := setupROFolder(t)
ffs := f.Filesystem()
defer cleanupModel(m)
// Create some test data
must(t, ffs.MkdirAll(".stfolder", 0755))
oldData := []byte("hello\n")
knownFiles := setupKnownFiles(t, ffs, oldData)
// Send an index update for the known stuff
m.Index(device1, "ro", knownFiles)
f.updateLocalsFromScanning(knownFiles)
// Scan the folder.
must(t, m.ScanFolder("ro"))
// Everything should be in sync.
size := globalSize(t, m, "ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = localSize(t, m, "ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = needSize(t, m, "ro")
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = receiveOnlyChangedSize(t, m, "ro")
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
// Delete our file
must(t, ffs.Remove(knownFiles[1].Name))
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
if size.Deleted != 1 {
t.Fatalf("Receive only: expected 1 deleted: %+v", size)
}
// Drop the remote
f.fset.Drop(device1)
must(t, m.ScanFolder("ro"))
size = receiveOnlyChangedSize(t, m, "ro")
if size.Deleted != 0 {
t.Fatalf("Receive only: expected no deleted: %+v", size)
}
}
func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
t.Helper()
@@ -305,11 +368,13 @@ func setupROFolder(t *testing.T) (*model, *receiveOnlyFolder) {
t.Helper()
w := createTmpWrapper(defaultCfg)
cfg := w.RawCopy()
fcfg := testFolderConfigFake()
fcfg.ID = "ro"
fcfg.Label = "ro"
fcfg.Type = config.FolderTypeReceiveOnly
w.SetFolder(fcfg)
cfg.Folders = []config.FolderConfiguration{fcfg}
w.Replace(cfg)
m := newModel(w, myID, "syncthing", "dev", db.NewLowlevel(backend.OpenMemory()), nil)
m.ServeBackground()

View File

@@ -97,7 +97,15 @@ func (f *sendOnlyFolder) pull() bool {
}
func (f *sendOnlyFolder) Override() {
f.doInSync(func() error { f.override(); return nil })
}
func (f *sendOnlyFolder) override() {
l.Infof("Overriding global state on folder %v", f.Description)
f.setState(FolderScanning)
defer f.setState(FolderIdle)
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
snap := f.fset.Snapshot()
@@ -132,5 +140,4 @@ func (f *sendOnlyFolder) Override() {
if len(batch) > 0 {
f.updateLocalsFromScanning(batch)
}
f.setState(FolderIdle)
}

View File

@@ -1359,14 +1359,9 @@ func verifyBuffer(buf []byte, block protocol.BlockInfo) error {
if len(buf) != int(block.Size) {
return fmt.Errorf("length mismatch %d != %d", len(buf), block.Size)
}
hf := sha256.New()
_, err := hf.Write(buf)
if err != nil {
return err
}
hash := hf.Sum(nil)
if !bytes.Equal(hash, block.Hash) {
hash := sha256.Sum256(buf)
if !bytes.Equal(hash[:], block.Hash) {
return fmt.Errorf("hash mismatch %x != %x", hash, block.Hash)
}

View File

@@ -109,6 +109,12 @@ func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, e
res["localFiles"], res["localDirectories"], res["localSymlinks"], res["localDeleted"], res["localBytes"], res["localTotalItems"] = local.Files, local.Directories, local.Symlinks, local.Deleted, local.Bytes, local.TotalItems()
fcfg, haveFcfg := c.cfg.Folder(folder)
if haveFcfg && fcfg.IgnoreDelete {
need.Deleted = 0
}
need.Bytes -= c.model.FolderProgressBytesCompleted(folder)
// This may happen if we are in progress of pulling files that were
// deleted globally after the pull started.
@@ -117,13 +123,7 @@ func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, e
}
res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"], res["needTotalItems"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes, need.TotalItems()
fcfg, ok := c.cfg.Folder(folder)
if ok && fcfg.IgnoreDelete {
res["needDeletes"] = 0
}
if ok && fcfg.Type == config.FolderTypeReceiveOnly {
if haveFcfg && fcfg.Type == config.FolderTypeReceiveOnly {
// Add statistics for things that have changed locally in a receive
// only folder.
res["receiveOnlyChangedFiles"] = ro.Files

View File

@@ -2562,30 +2562,6 @@ func (m *model) checkFolderRunningLocked(folder string) error {
return errFolderNotRunning
}
// checkFolderDeviceStatusLocked first checks the folder and then whether the
// given device is connected and shares this folder.
// Need to hold (read) lock on both m.fmut and m.pmut when calling this.
func (m *model) checkDeviceFolderConnectedLocked(device protocol.DeviceID, folder string) error {
if err := m.checkFolderRunningLocked(folder); err != nil {
return err
}
if cfg, ok := m.cfg.Device(device); !ok {
return errDeviceUnknown
} else if cfg.Paused {
return errDevicePaused
}
if _, ok := m.conn[device]; !ok {
return errors.New("device is not connected")
}
if cfg, ok := m.cfg.Folder(folder); !ok || !cfg.SharedWith(device) {
return errors.New("folder is not shared with device")
}
return nil
}
// mapFolders returns a map of folder ID to folder configuration for the given
// slice of folder configurations.
func mapFolders(folders []config.FolderConfiguration) map[string]config.FolderConfiguration {

View File

@@ -93,17 +93,15 @@ func (w *AtomicWriter) Close() error {
return err
}
// Remove the destination file, on Windows only. If it fails, and not due
// to the file not existing, we won't be able to complete the rename
// either. Return this error because it may be more informative. On non-
// Windows we want the atomic rename behavior so we don't attempt remove.
if runtime.GOOS == "windows" {
if err := w.fs.Remove(w.path); err != nil && !fs.IsNotExist(err) {
return err
}
err := w.fs.Rename(w.next.Name(), w.path)
if runtime.GOOS == "windows" && fs.IsPermission(err) {
// On Windows, we might not be allowed to rename over the file
// because it's read-only. Get us some write permissions and try
// again.
_ = w.fs.Chmod(w.path, 0644)
err = w.fs.Rename(w.next.Name(), w.path)
}
if err := w.fs.Rename(w.next.Name(), w.path); err != nil {
if err != nil {
w.err = err
return err
}

View File

@@ -10,6 +10,7 @@ import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
@@ -52,18 +53,45 @@ func TestCreateAtomicCreate(t *testing.T) {
}
func TestCreateAtomicReplace(t *testing.T) {
os.RemoveAll("testdata")
defer os.RemoveAll("testdata")
testCreateAtomicReplace(t, 0666)
}
func TestCreateAtomicReplaceReadOnly(t *testing.T) {
testCreateAtomicReplace(t, 0444) // windows compatible read-only bits
}
if err := os.Mkdir("testdata", 0755); err != nil {
func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
t.Helper()
testdir, err := ioutil.TempDir("", "syncthing")
if err != nil {
t.Fatal(err)
}
testfile := filepath.Join(testdir, "testfile")
os.RemoveAll(testdir)
defer os.RemoveAll(testdir)
if err := os.Mkdir(testdir, 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("testdata/file", []byte("some old data"), 0644); err != nil {
if err := ioutil.WriteFile(testfile, []byte("some old data"), oldPerms); err != nil {
t.Fatal(err)
}
w, err := CreateAtomic("testdata/file")
// Go < 1.14 has a bug in WriteFile where it does not use the requested
// permissions on Windows. Chmod to make sure.
if err := os.Chmod(testfile, oldPerms); err != nil {
t.Fatal(err)
}
// Trust, but verify.
if info, err := os.Stat(testfile); err != nil {
t.Fatal(err)
} else if info.Mode() != oldPerms {
t.Fatalf("Wrong perms 0%o", info.Mode())
}
w, err := CreateAtomic(testfile)
if err != nil {
t.Fatal(err)
}
@@ -75,7 +103,7 @@ func TestCreateAtomicReplace(t *testing.T) {
t.Fatal(err)
}
bs, err := ioutil.ReadFile("testdata/file")
bs, err := ioutil.ReadFile(testfile)
if err != nil {
t.Fatal(err)
}

View File

@@ -165,7 +165,7 @@ func luhnify(s string) (string, error) {
for i := 0; i < 4; i++ {
p := s[i*13 : (i+1)*13]
copy(res[i*(13+1):], p)
l, err := luhnBase32.generate(p)
l, err := luhn32(p)
if err != nil {
return "", err
}
@@ -183,7 +183,7 @@ func unluhnify(s string) (string, error) {
for i := 0; i < 4; i++ {
p := s[i*(13+1) : (i+1)*(13+1)-1]
copy(res[i*13:], p)
l, err := luhnBase32.generate(p)
l, err := luhn32(p)
if err != nil {
return "", err
}

View File

@@ -2,32 +2,34 @@
package protocol
import (
"fmt"
"strings"
)
import "fmt"
// An alphabet is a string of N characters, representing the digits of a given
// base N.
type luhnAlphabet string
var luhnBase32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
var (
luhnBase32 luhnAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
)
func codepoint32(b byte) int {
switch {
case 'A' <= b && b <= 'Z':
return int(b - 'A')
case '2' <= b && b <= '7':
return int(b + 26 - '2')
default:
return -1
}
}
// generate returns a check digit for the string s, which should be composed
// of characters from the Alphabet a.
// luhn32 returns a check digit for the string s, which should be composed
// of characters from the alphabet luhnBase32.
// Doesn't follow the actual Luhn algorithm
// see https://forum.syncthing.net/t/v0-9-0-new-node-id-format/478/6 for more.
func (a luhnAlphabet) generate(s string) (rune, error) {
func luhn32(s string) (rune, error) {
factor := 1
sum := 0
n := len(a)
const n = 32
for i := range s {
codepoint := strings.IndexByte(string(a), s[i])
codepoint := codepoint32(s[i])
if codepoint == -1 {
return 0, fmt.Errorf("digit %q not valid in alphabet %q", s[i], a)
return 0, fmt.Errorf("digit %q not valid in alphabet %q", s[i], luhnBase32)
}
addend := factor * codepoint
if factor == 2 {
@@ -40,16 +42,5 @@ func (a luhnAlphabet) generate(s string) (rune, error) {
}
remainder := sum % n
checkCodepoint := (n - remainder) % n
return rune(a[checkCodepoint]), nil
}
// luhnValidate returns true if the last character of the string s is correct, for
// a string s composed of characters in the alphabet a.
func (a luhnAlphabet) luhnValidate(s string) bool {
t := s[:len(s)-1]
c, err := a.generate(t)
if err != nil {
return false
}
return rune(s[len(s)-1]) == c
return rune(luhnBase32[checkCodepoint]), nil
}

View File

@@ -3,46 +3,24 @@
package protocol
import (
"strings"
"testing"
)
func TestGenerate(t *testing.T) {
// Base 6 Luhn
a := luhnAlphabet("abcdef")
c, err := a.generate("abcdef")
func TestLuhn32(t *testing.T) {
c, err := luhn32("AB725E4GHIQPL3ZFGT")
if err != nil {
t.Fatal(err)
}
if c != 'e' {
t.Errorf("Incorrect check digit %c != e", c)
if c != 'G' {
t.Errorf("Incorrect check digit %c != G", c)
}
// Base 10 Luhn
a = luhnAlphabet("0123456789")
c, err = a.generate("7992739871")
if err != nil {
t.Fatal(err)
}
if c != '3' {
t.Errorf("Incorrect check digit %c != 3", c)
}
}
func TestInvalidString(t *testing.T) {
a := luhnAlphabet("ABC")
_, err := a.generate("7992739871")
t.Log(err)
_, err = luhn32("3734EJEKMRHWPZQTWYQ1")
if err == nil {
t.Error("Unexpected nil error")
}
}
func TestValidate(t *testing.T) {
a := luhnAlphabet("abcdef")
if !a.luhnValidate("abcdefe") {
t.Errorf("Incorrect validation response for abcdefe")
}
if a.luhnValidate("abcdefd") {
t.Errorf("Incorrect validation response for abcdefd")
if !strings.Contains(err.Error(), "'1'") {
t.Errorf("luhn32 should have errored on digit '1', got %v", err)
}
}

View File

@@ -165,7 +165,6 @@ type rawConnection struct {
closeBox chan asyncMessage
clusterConfigBox chan *ClusterConfig
dispatcherLoopStopped chan struct{}
preventSends chan struct{}
closed chan struct{}
closeOnce sync.Once
sendCloseOnce sync.Once
@@ -219,7 +218,6 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
closeBox: make(chan asyncMessage),
clusterConfigBox: make(chan *ClusterConfig),
dispatcherLoopStopped: make(chan struct{}),
preventSends: make(chan struct{}),
closed: make(chan struct{}),
compression: compress,
}
@@ -656,7 +654,6 @@ func (c *rawConnection) send(ctx context.Context, msg message, done chan struct{
select {
case c.outbox <- asyncMessage{msg, done}:
return true
case <-c.preventSends:
case <-c.closed:
case <-ctx.Done():
}

View File

@@ -9,10 +9,7 @@
package rand
import (
"crypto/md5"
cryptoRand "crypto/rand"
"encoding/binary"
"io"
mathRand "math/rand"
"reflect"
)
@@ -20,16 +17,16 @@ import (
// Reader is the standard crypto/rand.Reader, re-exported for convenience
var Reader = cryptoRand.Reader
// randomCharset contains the characters that can make up a randomString().
// randomCharset contains the characters that can make up a rand.String().
const randomCharset = "2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ"
var (
// defaultSecureSource is a concurrency safe math/rand.Source with a
// cryptographically sound base.
defaltSecureSource = newSecureSource()
defaultSecureSource = newSecureSource()
// defaultSecureRand is a math/rand.Rand based on the secure source.
defaultSecureRand = mathRand.New(defaltSecureSource)
defaultSecureRand = mathRand.New(defaultSecureSource)
)
// String returns a strongly random string of characters (taken from
@@ -43,19 +40,14 @@ func String(l int) string {
return string(bs)
}
// Int63 returns a strongly random int63
// Int63 returns a strongly random int63.
func Int63() int64 {
return defaltSecureSource.Int63()
return defaultSecureSource.Int63()
}
// Int64 returns a strongly random int64
// Int64 returns a strongly random int64.
func Int64() int64 {
var bs [8]byte
_, err := io.ReadFull(cryptoRand.Reader, bs[:])
if err != nil {
panic("randomness failure: " + err.Error())
}
return int64(binary.BigEndian.Uint64(bs[:]))
return int64(defaultSecureSource.Uint64())
}
// Intn returns, as an int, a non-negative strongly random number in [0,n).
@@ -64,18 +56,7 @@ func Intn(n int) int {
return defaultSecureRand.Intn(n)
}
// SeedFromBytes calculates a weak 64 bit hash from the given byte slice,
// suitable for use a predictable random seed.
func SeedFromBytes(bs []byte) int64 {
h := md5.New()
h.Write(bs)
s := h.Sum(nil)
// The MD5 hash of the byte slice is 16 bytes long. We interpret it as two
// uint64s and XOR them together.
return int64(binary.BigEndian.Uint64(s[0:]) ^ binary.BigEndian.Uint64(s[8:]))
}
// Shuffle the order of elements
// Shuffle the order of elements in slice.
func Shuffle(slice interface{}) {
rv := reflect.ValueOf(slice)
swap := reflect.Swapper(slice)

View File

@@ -8,23 +8,6 @@ package rand
import "testing"
func TestSeedFromBytes(t *testing.T) {
// should always return the same seed for the same bytes
tcs := []struct {
bs []byte
v int64
}{
{[]byte("hello world"), -3639725434188061933},
{[]byte("hello worlx"), -2539100776074091088},
}
for _, tc := range tcs {
if v := SeedFromBytes(tc.bs); v != tc.v {
t.Errorf("Unexpected seed value %d != %d", v, tc.v)
}
}
}
func TestRandomString(t *testing.T) {
for _, l := range []int{0, 1, 2, 3, 4, 8, 42} {
s := String(l)

View File

@@ -37,6 +37,10 @@ func (s *secureSource) Seed(int64) {
}
func (s *secureSource) Int63() int64 {
return int64(s.Uint64() & (1<<63 - 1))
}
func (s *secureSource) Uint64() uint64 {
var buf [8]byte
// Read eight bytes of entropy from the buffered, secure random number
@@ -50,8 +54,5 @@ func (s *secureSource) Int63() int64 {
}
// Grab those bytes as an uint64
v := binary.BigEndian.Uint64(buf[:])
// Mask of the high bit and return the resulting int63
return int64(v & (1<<63 - 1))
return binary.BigEndian.Uint64(buf[:])
}

View File

@@ -5,11 +5,11 @@ package client
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
"github.com/syncthing/syncthing/lib/dialer"
@@ -17,6 +17,15 @@ import (
"github.com/syncthing/syncthing/lib/relay/protocol"
)
type incorrectResponseCodeErr struct {
code int32
msg string
}
func (e incorrectResponseCodeErr) Error() string {
return fmt.Sprintf("incorrect response code %d: %s", e.code, e.msg)
}
func GetInvitationFromRelay(ctx context.Context, uri *url.URL, id syncthingprotocol.DeviceID, certs []tls.Certificate, timeout time.Duration) (protocol.SessionInvitation, error) {
if uri.Scheme != "relay" {
return protocol.SessionInvitation{}, fmt.Errorf("unsupported relay scheme: %v", uri.Scheme)
@@ -53,7 +62,7 @@ func GetInvitationFromRelay(ctx context.Context, uri *url.URL, id syncthingproto
switch msg := message.(type) {
case protocol.Response:
return protocol.SessionInvitation{}, fmt.Errorf("incorrect response code %d: %s", msg.Code, msg.Message)
return protocol.SessionInvitation{}, incorrectResponseCodeErr{msg.Code, msg.Message}
case protocol.SessionInvitation:
l.Debugln("Received invitation", msg, "via", conn.LocalAddr())
ip := net.IP(msg.Address)
@@ -104,13 +113,13 @@ func JoinSession(ctx context.Context, invitation protocol.SessionInvitation) (ne
}
}
func TestRelay(ctx context.Context, uri *url.URL, certs []tls.Certificate, sleep, timeout time.Duration, times int) bool {
func TestRelay(ctx context.Context, uri *url.URL, certs []tls.Certificate, sleep, timeout time.Duration, times int) error {
id := syncthingprotocol.NewDeviceID(certs[0].Certificate[0])
invs := make(chan protocol.SessionInvitation, 1)
c, err := NewClient(uri, certs, invs, timeout)
if err != nil {
close(invs)
return false
return fmt.Errorf("creating client: %w", err)
}
go c.Serve()
defer func() {
@@ -119,16 +128,17 @@ func TestRelay(ctx context.Context, uri *url.URL, certs []tls.Certificate, sleep
}()
for i := 0; i < times; i++ {
_, err := GetInvitationFromRelay(ctx, uri, id, certs, timeout)
_, err = GetInvitationFromRelay(ctx, uri, id, certs, timeout)
if err == nil {
return true
return nil
}
if !strings.Contains(err.Error(), "Incorrect response code") {
return false
if !errors.As(err, &incorrectResponseCodeErr{}) {
return fmt.Errorf("getting invitation: %w", err)
}
time.Sleep(sleep)
}
return false
return fmt.Errorf("getting invitation: %w", err) // last of the above errors
}
func configForCerts(certs []tls.Certificate) *tls.Config {

View File

@@ -201,7 +201,7 @@ func (c *staticClient) join() error {
switch msg := message.(type) {
case protocol.Response:
if msg.Code != 0 {
return fmt.Errorf("incorrect response code %d: %s", msg.Code, msg.Message)
return incorrectResponseCodeErr{msg.Code, msg.Message}
}
case protocol.RelayFull:

View File

@@ -106,11 +106,13 @@ func (f fakeInfo) Name() string { return f.name }
func (f fakeInfo) Mode() fs.FileMode { return 0755 }
func (f fakeInfo) Size() int64 { return f.size }
func (f fakeInfo) ModTime() time.Time { return time.Unix(1234567890, 0) }
func (f fakeInfo) IsDir() bool { return strings.Contains(filepath.Base(f.name), "dir") || f.name == "." }
func (f fakeInfo) IsRegular() bool { return !f.IsDir() }
func (f fakeInfo) IsSymlink() bool { return false }
func (f fakeInfo) Owner() int { return 0 }
func (f fakeInfo) Group() int { return 0 }
func (f fakeInfo) IsDir() bool {
return strings.Contains(filepath.Base(f.name), "dir") || f.name == "."
}
func (f fakeInfo) IsRegular() bool { return !f.IsDir() }
func (f fakeInfo) IsSymlink() bool { return false }
func (f fakeInfo) Owner() int { return 0 }
func (f fakeInfo) Group() int { return 0 }
type fakeFile struct {
name string

View File

@@ -767,16 +767,10 @@ func TestNotExistingError(t *testing.T) {
}
func TestSkipIgnoredDirs(t *testing.T) {
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
fss := fs.NewFilesystem(fs.FilesystemTypeBasic, tmp)
fss := fs.NewFilesystem(fs.FilesystemTypeFake, "")
name := "foo/ignored"
err = fss.MkdirAll(name, 0777)
err := fss.MkdirAll(name, 0777)
if err != nil {
t.Fatal(err)
}
@@ -811,6 +805,50 @@ func TestSkipIgnoredDirs(t *testing.T) {
}
}
// https://github.com/syncthing/syncthing/issues/6487
func TestIncludedSubdir(t *testing.T) {
fss := fs.NewFilesystem(fs.FilesystemTypeFake, "")
name := filepath.Clean("foo/bar/included")
err := fss.MkdirAll(name, 0777)
if err != nil {
t.Fatal(err)
}
pats := ignore.New(fss, ignore.WithCache(true))
stignore := `
!/foo/bar
*
`
if err := pats.Parse(bytes.NewBufferString(stignore), ".stignore"); err != nil {
t.Fatal(err)
}
fchan := Walk(context.TODO(), Config{
CurrentFiler: make(fakeCurrentFiler),
Filesystem: fss,
Matcher: pats,
})
found := false
for f := range fchan {
if f.Err != nil {
t.Fatalf("Error while scanning %v: %v", f.Err, f.Path)
}
if f.File.IsIgnored() {
t.Error("File is ignored:", f.File.Name)
}
if f.File.Name == name {
found = true
}
}
if !found {
t.Errorf("File not present in scan results")
}
}
// Verify returns nil or an error describing the mismatch between the block
// list and actual reader contents
func verify(r io.Reader, blocksize int, blocks []protocol.BlockInfo) error {

View File

@@ -13,3 +13,7 @@ import (
var (
l = logger.DefaultLogger.NewFacility("app", "Main run facility")
)
func shouldDebug() bool {
return l.ShouldDebug("app")
}

View File

@@ -12,7 +12,9 @@ import (
"fmt"
"io"
"net/http"
"os"
"runtime"
"sort"
"strings"
"sync"
"time"
@@ -70,6 +72,9 @@ type Options struct {
ProfilerURL string
ResetDeltaIdxs bool
Verbose bool
// null duration means use default value
DBRecheckInterval time.Duration
DBIndirectGCInterval time.Duration
}
type App struct {
@@ -90,7 +95,7 @@ type App struct {
func New(cfg config.Wrapper, dbBackend backend.Backend, evLogger events.Logger, cert tls.Certificate, opts Options) *App {
a := &App{
cfg: cfg,
ll: db.NewLowlevel(dbBackend),
ll: db.NewLowlevel(dbBackend, db.WithRecheckInterval(opts.DBRecheckInterval), db.WithIndirectGCInterval(opts.DBIndirectGCInterval)),
evLogger: evLogger,
opts: opts,
cert: cert,
@@ -123,6 +128,7 @@ func (a *App) startup() error {
},
PassThroughPanics: true,
})
a.mainService.Add(a.ll)
a.mainService.ServeBackground()
if a.opts.AuditWriter != nil {
@@ -233,9 +239,6 @@ func (a *App) startup() error {
// Drop delta indexes in case we've changed random stuff we
// shouldn't have. We will resend our index on next connect.
db.DropDeltaIndexIDs(a.ll)
// Remember the new version.
miscDB.PutString("prevVersion", build.Version)
}
// Check and repair metadata and sequences on every upgrade including RCs.
@@ -246,6 +249,11 @@ func (a *App) startup() error {
a.ll.CheckRepair()
}
if build.Version != prevVersion {
// Remember the new version.
miscDB.PutString("prevVersion", build.Version)
}
m := model.NewModel(a.cfg, a.myID, "syncthing", build.Version, a.ll, protectedFiles, a.evLogger)
if a.opts.DeadlockTimeoutS > 0 {
@@ -366,6 +374,10 @@ func (a *App) startup() error {
func (a *App) run() {
<-a.stop
if shouldDebug() {
l.Debugln("Services before stop:")
printServiceTree(os.Stdout, a.mainService, 0)
}
a.mainService.Stop()
done := make(chan struct{})
@@ -470,3 +482,37 @@ func (e *controller) Shutdown() {
func (e *controller) ExitUpgrading() {
e.Stop(ExitUpgrade)
}
type supervisor interface{ Services() []suture.Service }
func printServiceTree(w io.Writer, sup supervisor, level int) {
printService(w, sup, level)
svcs := sup.Services()
sort.Slice(svcs, func(a, b int) bool {
return fmt.Sprint(svcs[a]) < fmt.Sprint(svcs[b])
})
for _, svc := range svcs {
if sub, ok := svc.(supervisor); ok {
printServiceTree(w, sub, level+1)
} else {
printService(w, svc, level+1)
}
}
}
func printService(w io.Writer, svc interface{}, level int) {
type errorer interface{ Error() error }
t := "-"
if _, ok := svc.(supervisor); ok {
t = "+"
}
fmt.Fprintln(w, strings.Repeat(" ", level), t, svc)
if es, ok := svc.(errorer); ok {
if err := es.Error(); err != nil {
fmt.Fprintln(w, strings.Repeat(" ", level), " ->", err)
}
}
}

View File

@@ -6,26 +6,9 @@
package ur
import (
"errors"
"os/exec"
"strconv"
"strings"
)
import "golang.org/x/sys/unix"
func memorySize() (int64, error) {
cmd := exec.Command("sysctl", "hw.memsize")
out, err := cmd.Output()
if err != nil {
return 0, err
}
fs := strings.Fields(string(out))
if len(fs) != 2 {
return 0, errors.New("sysctl parse error")
}
bytes, err := strconv.ParseInt(fs[1], 10, 64)
if err != nil {
return 0, err
}
return bytes, nil
mem, err := unix.SysctlUint64("hw.memsize")
return int64(mem), err
}

View File

@@ -255,8 +255,12 @@ func (s *service) Stop() {
default:
s.cancel()
}
// Cache s.stopped in a variable while we hold the mutex
// to prevent a data race with Serve's resetting it.
stopped := s.stopped
s.mut.Unlock()
<-s.stopped
<-stopped
}
func (s *service) Error() error {

View File

@@ -60,10 +60,10 @@ func newStaggered(folderFs fs.Filesystem, params map[string]string) Versioner {
folderFs: folderFs,
versionsFs: versionsFs,
interval: [4]interval{
{30, 3600}, // first hour -> 30 sec between versions
{3600, 86400}, // next day -> 1 h between versions
{86400, 592000}, // next 30 days -> 1 day between versions
{604800, maxAge}, // next year -> 1 week between versions
{30, 60 * 60}, // first hour -> 30 sec between versions
{60 * 60, 24 * 60 * 60}, // next day -> 1 h between versions
{24 * 60 * 60, 30 * 24 * 60 * 60}, // next 30 days -> 1 day between versions
{7 * 24 * 60 * 60, maxAge}, // next year -> 1 week between versions
},
mutex: sync.NewMutex(),
}

View File

@@ -29,31 +29,70 @@ func TestStaggeredVersioningVersionCount(t *testing.T) {
versionsWithMtime := []string{
// 14:00:00 is "now"
"test~20160415-140000", // 0 seconds ago
"test~20160415-135959", // 1 second ago
"test~20160415-135958", // 2 seconds ago
"test~20160415-135900", // 1 minute ago
"test~20160415-135859", // 1 minute 1 second ago
"test~20160415-135830", // 1 minute 30 seconds ago
"test~20160415-135829", // 1 minute 31 seconds ago
"test~20160415-135700", // 3 minutes ago
"test~20160415-135630", // 3 minutes 30 seconds ago
"test~20160415-133000", // 30 minutes ago
"test~20160415-132900", // 31 minutes ago
"test~20160415-132500", // 35 minutes ago
"test~20160415-132000", // 40 minutes ago
"test~20160415-130000", // 60 minutes ago
"test~20160415-124000", // 80 minutes ago
"test~20160415-122000", // 100 minutes ago
"test~20160415-110000", // 120 minutes ago
"test~20160415-135931", // 29 seconds ago
"test~20160415-135930", // 30 seconds ago
"test~20160415-130059", // 59 minutes 01 seconds ago
"test~20160415-130030", // 59 minutes 30 seconds ago
"test~20160415-130000", // 1 hour ago
"test~20160415-120001", // 1 hour 59:59 ago
"test~20160414-155959", // 22 hours 1 second ago
"test~20160414-150001", // 22 hours 59 seconds ago
"test~20160414-150000", // 23 hours ago
"test~20160414-140000", // 1 day ago
"test~20160414-130001", // 1 days 59:59 second ago
"test~20160409-135959", // 6 days 1 second ago
"test~20160408-140001", // 6 days 23:59:59 second ago
"test~20160408-140000", // 7 days ago
"test~20160408-135959", // 7 days 1 second ago
"test~20160407-140001", // 7 days 23:59:59 ago
"test~20160407-140000", // 8 days ago
"test~20160317-140000", // 29 days ago
"test~20160317-135959", // 29 days 1 second ago
"test~20160316-140000", // 30 days ago
"test~20160308-135959", // 37 days 1 second ago
"test~20160301-140000", // 44 days ago
"test~20160223-140000", // 51 days ago
"test~20150423-140000", // 358 days ago (!!! 2016 was a leap year !!!)
"test~20150417-140000", // 364 days ago
"test~20150416-140000", // 365 days ago
// exceeds maxAge
"test~20150416-135959", // 365 days 1 second ago
"test~20150416-135958", // 365 days 2 seconds ago
"test~20150414-140000", // 367 days ago
}
delete := []string{
"test~20160415-140000", // 0 seconds ago
"test~20160415-135959", // 1 second ago
"test~20160415-135900", // 1 minute ago
"test~20160415-135830", // 1 minute 30 second ago
"test~20160415-130000", // 60 minutes ago
"test~20160415-124000", // 80 minutes ago
"test~20160415-135931", // 29 seconds ago
"test~20160415-130059", // 59 minutes 01 seconds ago
"test~20160415-130000", // 1 hour ago
"test~20160414-155959", // 22 hours 1 second ago
"test~20160414-150001", // 22 hours 59 seconds ago
"test~20160414-140000", // 1 day ago
"test~20160409-135959", // 6 days 1 second ago
"test~20160408-140001", // 6 days 23:59:59 second ago
"test~20160408-135959", // 7 days 1 second ago
"test~20160407-140001", // 7 days 23:59:59 ago
"test~20160317-135959", // 29 days 1 second ago
"test~20160308-135959", // 37 days 1 second ago
"test~20150417-140000", // 364 days ago
"test~20150416-135959", // 365 days 1 second ago
"test~20150416-135958", // 365 days 2 seconds ago
"test~20150414-140000", // 367 days ago
}
sort.Strings(delete)

View File

@@ -93,7 +93,7 @@ func BenchmarkBlock(b *testing.B) {
test.hash.Reset()
}
bbb.SetBytes(int64(len(buf)))
bbb.SetBytes(testSize)
bbb.ReportAllocs()
})

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STDISCOSRV" "1" "Mar 17, 2020" "v1" "Syncthing"
.TH "STDISCOSRV" "1" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STRELAYSRV" "1" "Mar 17, 2020" "v1" "Syncthing"
.TH "STRELAYSRV" "1" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "Mar 17, 2020" "v1" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Apr 06, 2020" "v1" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.

Some files were not shown because too many files have changed in this diff Show More