mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-03 03:19:04 -05:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebad9e2073 | ||
|
|
ab92f8520c | ||
|
|
0e67c036bb | ||
|
|
046bbdfbd4 | ||
|
|
c6c74e8291 | ||
|
|
59b1b0e1dc | ||
|
|
4b17c511f9 | ||
|
|
df318ed370 | ||
|
|
07ce3572a0 | ||
|
|
b64052bc26 | ||
|
|
7956e7d0ef | ||
|
|
d400e51422 | ||
|
|
674a99e9ae | ||
|
|
88cabb9e0a | ||
|
|
b7ba401c0b | ||
|
|
7505ea79a0 | ||
|
|
e1324a0e23 | ||
|
|
b7b9476e5a | ||
|
|
d1db7e3dd2 | ||
|
|
362da59396 | ||
|
|
66262392c3 | ||
|
|
48f9d323fa | ||
|
|
f69c0b550c | ||
|
|
32245435e2 | ||
|
|
2658369051 | ||
|
|
d50adb225b | ||
|
|
7da898f2d6 | ||
|
|
0d919bd79c | ||
|
|
f91e90a94f | ||
|
|
7ce20f197b | ||
|
|
2f26a95973 | ||
|
|
123941cf62 | ||
|
|
9c67d57c28 | ||
|
|
5b3466dc6e | ||
|
|
bca6854c03 | ||
|
|
c930b2e9e2 | ||
|
|
d7a257b391 | ||
|
|
7709ac33a7 | ||
|
|
1e2379df1b | ||
|
|
ea5c9176e1 | ||
|
|
cc1b003f21 | ||
|
|
38bd90e6f2 | ||
|
|
1c47fae206 | ||
|
|
79a758be3c | ||
|
|
c7cf3ef899 | ||
|
|
2c2e6cd0d5 | ||
|
|
b7dffc051e | ||
|
|
963e9a4071 | ||
|
|
b28899ac07 | ||
|
|
83f6da8dca | ||
|
|
1a29296d9d | ||
|
|
a7de4c68e3 | ||
|
|
7f23de4f03 | ||
|
|
ca89f12be6 | ||
|
|
ddfa82e990 | ||
|
|
61302c467c | ||
|
|
d6b4873eed | ||
|
|
1ea98a16b1 | ||
|
|
e2f3500df9 | ||
|
|
8b025af1e5 | ||
|
|
c4abe6f815 | ||
|
|
4c5e9cf921 | ||
|
|
74706bb02b | ||
|
|
5975772ed8 | ||
|
|
cf11fa4327 | ||
|
|
40580d8b9b | ||
|
|
e25e71cdde |
7
AUTHORS
7
AUTHORS
@@ -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>
|
||||
|
||||
33
build.go
33
build.go
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{} {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
17
go.mod
@@ -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
49
go.sum
@@ -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=
|
||||
|
||||
@@ -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": "Периодично сканиране, през определен интервал, и мониторинг за промени",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Τακτική σάρωση ανά καθορισμένο διάστημα και ενεργοποίηση επιτήρησης αλλαγών",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ä",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "定期スキャンと変更の監視はいずれも有効です",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}).",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "正以给定的间隔定期扫描并已启用更改监视",
|
||||
|
||||
@@ -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": "在一定的時間間隔,定期掃描及啟用觀察變動",
|
||||
|
||||
@@ -405,7 +405,7 @@
|
||||
<tr ng-if="folder.type == 'receiveonly' && canRevert(folder.id)">
|
||||
<th><span class="fas fa-fw fa-exclamation-circle"></span> <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>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<p translate>Copyright © 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 />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
{"/", "/", "/"},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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():
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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[:])
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,3 +13,7 @@ import (
|
||||
var (
|
||||
l = logger.DefaultLogger.NewFacility("app", "Main run facility")
|
||||
)
|
||||
|
||||
func shouldDebug() bool {
|
||||
return l.ShouldDebug("app")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ func BenchmarkBlock(b *testing.B) {
|
||||
test.hash.Reset()
|
||||
}
|
||||
|
||||
bbb.SetBytes(int64(len(buf)))
|
||||
bbb.SetBytes(testSize)
|
||||
bbb.ReportAllocs()
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
|
||||
.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user