Compare commits

...

28 Commits

Author SHA1 Message Date
Simon Frei
09aff7bb14 lib/model: Fixes on receive-only test setup and pulling (#5136) 2018-09-02 21:36:07 +02:00
Simon Frei
b068c8f346 lib/fs: Don't add path separators at end of path (fixes #5144) (#5146) 2018-09-02 21:31:18 +02:00
Simon Frei
c2ff49ed43 lib/fs: Evaluate root when watching not on fs creation (fixes #5043) (#5105) 2018-09-02 21:31:04 +02:00
Jakob Borg
b80da29b23 lib/db: Fix inconsistency in sequence index (fixes #5149) (#5158)
The problem here is that we would update the sequence index before
updating the FileInfos, which would result in a high sequence number
pointing to a low-sequence FileInfo. The index sender would pick up the
high sequence number, send the old file, and think everything was good.
On the receiving side the old file is a no-op and ignored. The file
remains out of sync until another update for it happens.

This fixes that by correcting the order of operations in the database
update: first we remove old sequence index entries, then we update the
FileInfos (which now don't have anything pointing to them) and then we
add the sequence indexes (which the index sender can see).

The other option is to add "proper" transactions where required at the
database layer. I actually have a branch for that, but it's literally
thousands of lines of diff and I'm putting that off for another day as
this solves the problem...
2018-09-02 21:02:28 +02:00
Simon Frei
7c0798b622 lib/model: Always release the lock (#5126) 2018-08-15 16:44:59 +02:00
Simon Frei
ee42c46bd3 lib/model: Catch racy nil deref in ClusterConfig (#5106) 2018-08-15 16:44:20 +02:00
Jakob Borg
7812c2c937 vendor: Point github.com/syncthing/notify at master again (metadata only) 2018-08-06 21:44:53 +02:00
Jakob Borg
500e02cdbb vendor: Patch github.com/syncthing/notify for Go 1.11 2018-08-06 20:15:43 +02:00
Simon Frei
82c9e23206 vendor: Remove unused vendor packages (fixes #3595) (#5096) 2018-08-04 16:29:13 +01:00
Simon Frei
705b7d18e8 build: Also copy gui to temporary GOPATH (#5095) 2018-08-02 16:13:17 +02:00
Simon Frei
f4bde023aa build: Build and set GOPATH before generating assets (#5093) 2018-08-01 21:22:47 +02:00
Jakob Borg
af48b069cc gui, man, authors: Update docs, translations, and contributors 2018-08-01 07:45:28 +02:00
Jakob Borg
5cb4a9acf6 lib/db: Don't account remote invalid files (fixes #5089) (#5090) 2018-07-31 13:00:03 +02:00
Oyebanji Jacob Mayowa
adc5bf6604 lib/upnp: Don’t log unknown device types (fixes #5038) (#5087) 2018-07-30 16:34:35 +02:00
Audrius Butkevicius
24d307531d gui: Use one instead of on to have callbacks fire one (#5085) 2018-07-29 21:15:24 +02:00
Audrius Butkevicius
93fdd1c012 cmd/strelaypoolsrv: Prevent scraped metrics moving backwards (#5068) 2018-07-27 07:59:55 +02:00
Audrius Butkevicius
5161f03f02 lib/config: Fix aliased append, copy config inputs and outputs (fixes #5063) (#5069) 2018-07-26 23:14:12 +02:00
Adam Piggott
682ffcb8ed github: Mention auxiliary projects and db corruption in issue template (#5081)
Add a section on using the correct issue tracker
Add a section on database corruption
2018-07-26 20:05:56 +02:00
Jakob Borg
524ffe3fa5 gui, man, authors: Update docs, translations, and contributors 2018-07-25 07:45:29 +02:00
Jakob Borg
d8366e4a88 authors: Enable auto updates (#5074)
Removes the manual handling of the AUTHORS file, giving every committer automatic credit for their contribution.

Manual tweaks to the file are still accepted and retained by the scripts.
2018-07-23 17:41:59 +02:00
Jakob Borg
b83c5b32bf authors: Add bebehei 2018-07-20 15:46:38 +02:00
Benedikt Heine
3102e36a45 dockerfile: Create a dedicated syncthing user (#5072)
A dedicated user is necessary to create relative references via
~/<folder> or $HOME/<folder>. Having the syncthing process just running
under a unprivileged UID/GID, will remove the home folder relation and
therefore will result in nonexistent shares after update.

Signed-off-by: Benedikt Heine <bebe@bebehei.de>
2018-07-20 15:45:40 +02:00
Jakob Borg
3d8344003e lib/logger: Add missing dots (fixes #5073) 2018-07-19 20:49:57 +02:00
Jakob Borg
a4415bce10 gui, man: Update docs & translations 2018-07-18 07:45:27 +02:00
Audrius Butkevicius
9f87fd1fcf lib/model: More auto accept tests (#5014) 2018-07-15 20:26:20 +03:00
Simon Frei
5592b8b190 lib/model: Record error for unavailable files (#5066) 2018-07-14 14:09:23 +01:00
Jakob Borg
f822b10550 all: Add receive only folder type (#5027)
Adds a receive only folder type that does not send changes, and where the user can optionally revert local changes. Also changes some of the icons to make the three folder types distinguishable.
2018-07-12 11:15:57 +03:00
Jakob Borg
1a6c7587c2 gui, man: Update docs & translations 2018-07-11 07:45:26 +02:00
243 changed files with 2386 additions and 30078 deletions

View File

@@ -11,6 +11,14 @@ requests directly to the developers. Worst case you might get a short
"that's a bug, please report it on GitHub" response on the forum, in which
case we thank you for your patience and following our advice. :)
### Please use the correct issue tracker
If your problem relates to a Syncthing wrapper or [sub-project](https://github.com/syncthing) such as [Syncthing for Android](https://github.com/syncthing/syncthing-android/issues), [SyncTrayzor](https://github.com/canton7/synctrayzor) or the [documentation](https://github.com/syncthing/docs/issues), please use their respective issue trackers.
### Does your log mention database corruption?
If your Syncthing log reports panics because of database corruption it is most likely a fault with your system's storage or memory. Affected log entries will contain lines starting with `panic: leveldb`. You will need to delete the index database to clear this, by running `syncthing -reset-database`.
### Please do post actual bug reports and feature requests.
If your issue is a bug report, replace this boilerplate with a description

View File

@@ -20,13 +20,8 @@ If this is a user visible change (including API and protocol changes), add a lin
to the corresponding pull request on https://github.com/syncthing/docs or describe
the documentation changes necessary.
### Authorship
## Authorship
Your name and email will be added automatically to the AUTHORS file
based on the commit metadata.
Every author of a code contribution (Go, Javascript, HTML, CSS etc, with the
possible exception of minor typo corrections and similar) is recorded in the
AUTHORS and NICKS files and the in-GUI credits. If this is your first
contribution, a maintainer will add you properly before accepting the
contribution. You need not do so yourself or worry about the fact that the
"authors" automated test fails. However, if your name (such as you want it
presented in the credits) is not visible on your Github profile or in your
commit messages, please assist by providing it here.

61
AUTHORS
View File

@@ -1,24 +1,33 @@
# This is the official list of Syncthing authors for copyright purposes.
# The format is:
#
# THIS FILE IS MOSTLY AUTO GENERATED. IF YOU'VE MADE A COMMIT TO THE
# REPOSITORY YOU WILL BE ADDED HERE AUTOMATICALLY WITHOUT THE NEED FOR
# ANY MANUAL ACTION.
#
# That said, you are welcome to correct your name or add a nickname / GitHub
# user name as appropriate. The format is:
#
# Name Name Name (nickname) <email1@example.com> <email2@example.com>
#
# After changing this list, run "go run script/authors.go" to sort and update
# the GUI HTML.
# The in-GUI authors list is periodically automatically updated from the
# contents of this file.
#
Aaron Bieber (qbit) <qbit@deftly.net>
Adam Piggott (ProactiveServices) <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com> <ProactiveServices@users.noreply.github.com>
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>
Alessandro G. (alessandro.g89) <alessandro.g89@gmail.com>
Alexander Graf (alex2108) <register-github@alex-graf.de>
Alexandre Viau (aviau) <alexandre@alexandreviau.net> <aviau@debian.org>
Anderson Mesquita (andersonvom) <andersonvom@gmail.com>
andresvia <andres.via@gmail.com>
Andrew Dunham (andrew-d) <andrew@du.nham.ca>
Andrew Rabert (nvllsvm) <ar@nullsum.net>
Andrey D (scienmind) <scintertech@cryptolab.net>
Andrew Rabert (nvllsvm) <ar@nullsum.net> <6550543+nvllsvm@users.noreply.github.com>
Andrey D (scienmind) <scintertech@cryptolab.net> <scienmind@users.noreply.github.com>
andyleap <andyleap@gmail.com>
Antoine Lamielle (0x010C) <antoine.lamielle@0x010c.fr> <gh@0x010c.fr>
Antony Male (canton7) <antony.male@gmail.com>
Aranjedeath <Aranjedeath@users.noreply.github.com>
Arthur Axel fREW Schmidt (frioux) <frew@afoolishmanifesto.com> <frioux@gmail.com>
Audrius Butkevicius (AudriusButkevicius) <audrius.butkevicius@gmail.com>
Bart De Vries (mogwa1) <devriesb@gmail.com>
@@ -26,17 +35,22 @@ Ben Curthoys (bencurthoys) <ben@bencurthoys.com>
Ben Schulz (uok) <ueomkail@gmail.com> <uok@users.noreply.github.com>
Ben Shepherd (benshep) <bjashepherd@gmail.com>
Ben Sidhom (bsidhom) <bsidhom@gmail.com>
Benedikt Heine (bebehei) <bebe@bebehei.de>
Benedikt Morbach <benedikt.morbach@googlemail.com>
Benny Ng (tpng) <benny.tpng@gmail.com>
Brandon Philips (philips) <brandon@ifup.org>
Brendan Long (brendanlong) <self@brendanlong.com>
Brian R. Becker (brbecker) <brbecker@gmail.com>
Caleb Callaway (cqcallaw) <enlightened.despot@gmail.com>
Carsten Hagemann (Moter8) <moter8@gmail.com>
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
Cedric Staniewski (xduugu) <cedric@gmx.ca>
Chris Howie (cdhowie) <me@chrishowie.com>
Chris Joel (cdata) <chris@scriptolo.gy>
Chris Tonkinson <chris@masterbran.ch>
chucic <chucic@seznam.cz>
Colin Kennedy (moshen) <moshen.colin@gmail.com>
Dale Visser <dale.visser@live.com>
Daniel Bergmann (brgmnn) <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
Daniel Harte (norgeous) <daniel@harte.me> <daniel@danielharte.co.uk> <norgeous@users.noreply.github.com>
Daniel Martí (mvdan) <mvdan@mvdan.cc>
@@ -44,9 +58,11 @@ Darshil Chanpura (dtchanpura) <dtchanpura@gmail.com> <dcprime314@gmail.com>
David Rimmer (dinosore) <dinosore@dbrsoftware.co.uk>
Denis A. (dva) <denisva@gmail.com>
Dennis Wilson (snnd) <dw@risu.io>
derekriemer <derek.riemer@colorado.edu>
Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
Dominik Heidler (asdil12) <dominik@heidler.eu>
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
Elliot Huffman <thelich2@gmail.com>
Emil Hessman (ceh) <emil@hessman.se>
Erik Meitner (WSGCSysadmin) <e.meitner@willystreet.coop>
Federico Castagnini (facastagnini) <federico.castagnini@gmail.com>
@@ -56,29 +72,38 @@ Francois-Xavier Gsell (zukoo) <fxgsell@gmail.com>
Frank Isemann (fti7) <frank@isemann.name>
Gilli Sigurdsson (gillisig) <gilli@vx.is>
Graham Miln (grahammiln) <graham.miln@dssw.co.uk> <graham.miln@miln.eu>
Han Boetes <han@boetes.org>
Harrison Jones (harrisonhjones) <harrisonhjones@users.noreply.github.com>
Heiko Zuerker (Smiley73) <heiko@zuerker.org>
Iain Barnett <iainspeed@gmail.com>
Ian Johnson (anonymouse64) <ian.johnson@canonical.com> <person.uwsome@gmail.com>
Jaakko Hannikainen (jgke) <jgke@jgke.fi>
Jacek Szafarkiewicz (hadogenes) <szafar@linux.pl>
Jake Peterson (acogdev) <jake@acogdev.com>
Jakob Borg (calmh) <jakob@nym.se> <jakob@kastelo.net>
James Patterson (jpjp) <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
janost <janost@tuta.io>
Jaroslav Malec (dzarda) <dzardacz@gmail.com>
jaseg <githubaccount@jaseg.net>
Jaya Chithra (jayachithra) <s.k.jayachithra@gmail.com>
Jens Diemer (jedie) <github.com@jensdiemer.de> <git@jensdiemer.de>
Jerry Jacobs (xor-gate) <jerry.jacobs@xor-gate.org> <xor-gate@users.noreply.github.com>
Jochen Voss (seehuhn) <voss@seehuhn.de>
Johan Andersson <j@i19.se>
Johan Vromans (sciurius) <jvromans@squirrel.nl>
John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
Jonathan Cross <jcross@gmail.com>
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
Karol Różycki (krozycki) <rozycki.karol@gmail.com>
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 White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
klemens <ka7@github.com>
Kurt Fitzner (Kudalufi) <kurt@va1der.ca> <kurt.fitzner@gmail.com>
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
Laurent Arnoud <laurent@spkdev.net>
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
Leo Arias (elopio) <yo@elopio.net>
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
@@ -88,50 +113,72 @@ Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
Marc Pujol (kilburn) <kilburn@la3.org>
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
marco-m <marco.molteni@laposte.net>
Mark Pulford (mpx) <mark@kyne.com.au>
Mateusz Naściszewski (mateon1) <matin1111@wp.pl>
Matic Potočnik <hairyfotr@gmail.com>
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
Matteo Ruina <matteo.ruina@gmail.com>
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
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 Tilli (pyfisch) <pyfisch@gmail.com>
Mike Boone <mike@boonedocks.net>
MikeLund <MikeLund@users.noreply.github.com>
Nate Morrison (nrm21) <natemorrison@gmail.com>
Nicholas Rishel (PrototypeNM1) <rishel.nick@gmail.com> <PrototypeNM1@users.noreply.github.com>
Nicolas Braud-Santoni <nicolas@braud-santoni.eu>
Niels Peter Roest (Niller303) <nielsproest@hotmail.com> <seje.niels@hotmail.com>
Nils Jakobi (thunderstorm99) <jakobi.nils@gmail.com>
NoLooseEnds <jon.koslung@gmail.com>
Oyebanji Jacob Mayowa <oyebanji05@gmail.com>
Pascal Jungblut (pascalj) <github@pascalj.com> <mail@pascal-jungblut.com>
Pawel Palenica (qepasa) <pawelpalenica11@gmail.com>
perewa <cavalcante.ten@gmail.com>
Peter Dave Hello <hsu@peterdavehello.org>
Peter Hoeg (peterhoeg) <peter@speartail.com>
Peter Marquardt (wwwutz) <wwwutz@gmail.com> <wwwutz@googlemail.com>
Phil Davis <phil.davis@inf.org>
Philippe Schommers (filoozoom) <philippe@schommers.be>
Phill Luby (pluby) <phill.luby@newredo.com>
Pier Paolo Ramon <ramonpierre@gmail.com>
Piotr Bejda (piobpl) <piotrb10@gmail.com>
Pramodh KP (pramodhkp) <pramodh.p@directi.com> <1507241+pramodhkp@users.noreply.github.com>
Richard Hartmann <RichiH@users.noreply.github.com>
Robert Carosi (nov1n) <robert@carosi.nl>
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
Ross Smith II (rasa) <ross@smithii.com>
rubenbe <github-com-00ff86@vandamme.email>
Ryan Sullivan (KayoticSully) <kayoticsully@gmail.com>
Sacheendra Talluri (sacheendra) <sacheendra.t@gmail.com>
Scott Klupfel (kluppy) <kluppy@going2blue.com>
Sergey Mishin (ralder) <ralder@yandex.ru>
Simon Frei (imsodin) <freisim93@gmail.com>
Sly_tom_cat <slytomcat@mail.ru>
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
Suhas Gundimeda (snugghash) <suhas.gundimeda@gmail.com> <snugghash@gmail.com>
Taylor Khan (nelsonkhan) <nelsonkhan@gmail.com>
Thomas Hipp <thomashipp@gmail.com>
Tim Abell (timabell) <tim@timwise.co.uk>
Tim Howes (timhowes) <timhowes@berkeley.edu>
Tobias Nygren (tnn2) <tnn@nygren.pp.se>
Tobias Tom (tobiastom) <t.tom@succont.de>
Tomas Cerveny (kozec) <kozec@kozec.com>
Tommy Thorn <tommy-github-email@thorn.ws>
Tully Robinson (tojrobinson) <tully@tojr.org>
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
Victor Buinsky (buinsky) <vix_booja@tut.by>
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
Vladimir Rusinov <vrusinov@google.com>
wangguoliang <liangcszzu@163.com>
William A. Kennington III (wkennington) <william@wkennington.com>
Wulf Weich (wweich) <wweich@users.noreply.github.com> <wweich@gmx.de> <wulf@weich-kr.de>
Xavier O. (damajor) <damajor@gmail.com>
xjtdy888 (xjtdy888) <xjtdy888@163.com>
Yannic A. (eipiminus1) <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
佛跳墙 <daoquan@qq.com>

View File

@@ -21,11 +21,30 @@ COPY --from=builder /go/src/github.com/syncthing/syncthing/syncthing /bin/syncth
RUN apk add --no-cache su-exec
ENV STNOUPGRADE=1
ENV PUSR=syncthing
ENV PUID=1000
ENV PGRP=syncthing
ENV PGID=1000
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 8384 || exit 1
ENTRYPOINT chown $PUID:$PGID /var/syncthing \
&& su-exec $PUID:$PGID /bin/syncthing -home /var/syncthing/config -gui-address 0.0.0.0:8384
ENTRYPOINT true \
&& ( getent group "${PGRP}" >/dev/null \
|| addgroup \
-g "${PGID}" \
"${PGRP}" \
) \
&& ( getent passwd "${PUSR}" >/dev/null \
|| adduser \
-h /var/syncthing \
-G "${PGRP}" \
-u "${PUID}" \
"${PUSR}" \
) \
&& chown "${PUSR}:${PGRP}" /var/syncthing \
&& su-exec "${PUSR}:${PGRP}" \
/bin/syncthing \
-home /var/syncthing/config \
-gui-address 0.0.0.0:8384 \
&& true

View File

@@ -211,14 +211,14 @@ func main() {
if err != nil {
log.Fatal(err)
}
os.Setenv("GOPATH", gopath)
log.Println("GOPATH is", gopath)
if !noBuildGopath {
lazyRebuildAssets()
if err := buildGOPATH(gopath); err != nil {
log.Fatal(err)
}
lazyRebuildAssets()
}
os.Setenv("GOPATH", gopath)
log.Println("GOPATH is", gopath)
} else {
inside := false
wd, _ := os.Getwd()
@@ -1260,7 +1260,7 @@ func temporaryBuildDir() (string, error) {
func buildGOPATH(gopath string) error {
pkg := filepath.Join(gopath, "src/github.com/syncthing/syncthing")
dirs := []string{"cmd", "lib", "meta", "script", "test", "vendor"}
dirs := []string{"cmd", "gui", "lib", "meta", "script", "test", "vendor"}
if debug {
t0 := time.Now()

View File

@@ -59,8 +59,8 @@ case "${1:-default}" in
go run script/authors.go
build transifex
pushd man ; ./refresh.sh ; popd
git add -A gui man
git commit -m 'gui, man: Update docs & translations'
git add -A gui man AUTHORS
git commit -m 'gui, man, authors: Update docs, translations, and contributors'
;;
noupgrade)

View File

@@ -496,7 +496,7 @@ func handleRelayTest(request request) {
mut.Lock()
if stats != nil {
updateMetrics(request.relay.uri.Host, stats, location)
updateMetrics(request.relay.uri.Host, *stats, location)
}
request.relay.Stats = stats
request.relay.StatsRetrieved = time.Now()

View File

@@ -44,6 +44,8 @@ var (
relayGlobalRate = makeGauge("relay_global_rate", "Global rate applied on the whole relay", "relay")
relayBuildInfo = makeGauge("relay_build_info", "Build information about a relay", "relay", "go_version", "go_os", "go_arch")
relayLocationInfo = makeGauge("relay_location_info", "Location information about a relay", "relay", "city", "country", "continent")
lastStats = make(map[string]stats)
)
func makeGauge(name string, help string, labels ...string) *prometheus.GaugeVec {
@@ -142,7 +144,7 @@ func refreshStats() {
if result.stats == nil {
deleteMetrics(result.relay.uri.Host)
} else {
updateMetrics(result.relay.uri.Host, result.stats, result.relay.Location)
updateMetrics(result.relay.uri.Host, *result.stats, result.relay.Location)
}
}
mut.Unlock()
@@ -182,13 +184,18 @@ func fetchStats(relay *relay) *stats {
return &stats
}
func updateMetrics(host string, stats *stats, location location) {
func updateMetrics(host string, stats stats, location location) {
if stats.GoVersion != "" || stats.GoOS != "" || stats.GoArch != "" {
relayBuildInfo.WithLabelValues(host, stats.GoVersion, stats.GoOS, stats.GoArch).Add(1)
}
if location.City != "" || location.Country != "" || location.Continent != "" {
relayLocationInfo.WithLabelValues(host, location.City, location.Country, location.Continent).Add(1)
}
if lastStat, ok := lastStats[host]; ok {
stats = mergeStats(stats, lastStat)
}
relayUptime.WithLabelValues(host).Set(float64(stats.UptimeSeconds))
relayPendingSessionKeys.WithLabelValues(host).Set(float64(stats.PendingSessionKeys))
relayActiveSessions.WithLabelValues(host).Set(float64(stats.ActiveSessions))
@@ -198,6 +205,7 @@ func updateMetrics(host string, stats *stats, location location) {
relayGoRoutines.WithLabelValues(host).Set(float64(stats.GoRoutines))
relaySessionRate.WithLabelValues(host).Set(float64(stats.Options.SessionRate))
relayGlobalRate.WithLabelValues(host).Set(float64(stats.Options.GlobalRate))
lastStats[host] = stats
}
func deleteMetrics(host string) {
@@ -210,4 +218,33 @@ func deleteMetrics(host string) {
relayGoRoutines.DeleteLabelValues(host)
relaySessionRate.DeleteLabelValues(host)
relayGlobalRate.DeleteLabelValues(host)
delete(lastStats, host)
}
// Due to some unexplainable behaviour, some of the numbers sometimes travel slightly backwards (by less than 1%)
// This happens between scrapes, which is 30s, so this can't be a race.
// This causes prometheus to assume a "rate reset", hence causes phenomenal spikes.
// One of the number that moves backwards is BytesProxied, which atomically increments a counter with numeric value
// returned by net.Conn.Read(). I don't think that can return a negative value, so I have no idea what's going on.
func mergeStats(new stats, old stats) stats {
new.UptimeSeconds = mergeValue(new.UptimeSeconds, old.UptimeSeconds)
new.PendingSessionKeys = mergeValue(new.PendingSessionKeys, old.PendingSessionKeys)
new.ActiveSessions = mergeValue(new.ActiveSessions, old.ActiveSessions)
new.Connections = mergeValue(new.Connections, old.Connections)
new.Proxies = mergeValue(new.Proxies, old.Proxies)
new.BytesProxied = mergeValue(new.BytesProxied, old.BytesProxied)
new.GoRoutines = mergeValue(new.GoRoutines, old.GoRoutines)
new.Options.SessionRate = mergeValue(new.Options.SessionRate, old.Options.SessionRate)
new.Options.GlobalRate = mergeValue(new.Options.GlobalRate, old.Options.GlobalRate)
return new
}
func mergeValue(new, old int) int {
if new >= old {
return new // normal increase
}
if float64(new) > 0.99*float64(old) {
return old // slight backward movement
}
return new // reset (relay restart)
}

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
package main
import (
"testing"
)
func TestMerge(t *testing.T) {
if mergeValue(1001, 1000) != 1001 {
t.Error("the computer says no")
}
if mergeValue(999, 1000) != 1000 {
t.Error("the computer says no")
}
if mergeValue(1, 1000) != 1 {
t.Error("the computer says no")
}
}

View File

@@ -85,6 +85,7 @@ type modelIntf interface {
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
Completion(device protocol.DeviceID, folder string) model.FolderCompletion
Override(folder string)
Revert(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated)
RemoteNeedFolderFiles(device protocol.DeviceID, folder string, page, perpage int) ([]db.FileInfoTruncated, error)
NeedSize(folder string) db.Counts
@@ -107,6 +108,7 @@ type modelIntf interface {
Connection(deviceID protocol.DeviceID) (connections.Connection, bool)
GlobalSize(folder string) db.Counts
LocalSize(folder string) db.Counts
ReceiveOnlyChangedSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool)
State(folder string) (string, time.Time, error)
@@ -293,6 +295,7 @@ func (s *apiService) Serve() {
postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
postRestMux.HandleFunc("/rest/db/revert", s.postDBRevert) // folder
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
postRestMux.HandleFunc("/rest/folder/versions", s.postFolderVersionsRestore) // folder <body>
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
@@ -712,6 +715,17 @@ func folderSummary(cfg configIntf, m modelIntf, folder string) (map[string]inter
need := m.NeedSize(folder)
res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes
if cfg.Folders()[folder].Type == config.FolderTypeReceiveOnly {
// Add statistics for things that have changed locally in a receive
// only folder.
ro := m.ReceiveOnlyChangedSize(folder)
res["receiveOnlyChangedFiles"] = ro.Files
res["receiveOnlyChangedDirectories"] = ro.Directories
res["receiveOnlyChangedSymlinks"] = ro.Symlinks
res["receiveOnlyChangedDeletes"] = ro.Deleted
res["receiveOnlyChangedBytes"] = ro.Bytes
}
res["inSyncFiles"], res["inSyncBytes"] = global.Files-need.Files, global.Bytes-need.Bytes
res["state"], res["stateChanged"], err = m.State(folder)
@@ -748,6 +762,12 @@ func (s *apiService) postDBOverride(w http.ResponseWriter, r *http.Request) {
go s.model.Override(folder)
}
func (s *apiService) postDBRevert(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
go s.model.Revert(folder)
}
func getPagingParams(qs url.Values) (int, int) {
page, err := strconv.Atoi(qs.Get("page"))
if err != nil || page < 1 {

View File

@@ -29,6 +29,8 @@ func (m *mockedModel) Completion(device protocol.DeviceID, folder string) model.
func (m *mockedModel) Override(folder string) {}
func (m *mockedModel) Revert(folder string) {}
func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
return nil, nil, nil
}
@@ -117,6 +119,10 @@ func (m *mockedModel) LocalSize(folder string) db.Counts {
return db.Counts{}
}
func (m *mockedModel) ReceiveOnlyChangedSize(folder string) db.Counts {
return db.Counts{}
}
func (m *mockedModel) CurrentSequence(folder string) (int64, bool) {
return 0, false
}

View File

@@ -109,6 +109,7 @@
"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.": "Когато syncthing замени или изтрие файл той се премества в .stversions и преименува с набавени дата и час.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Защитава файловете от промени направени на други устройства, но промените направени на това устройство ще бъдат синхронизирани с останалите устройства.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Известия на системата",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Филтриране по дата",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
"RAM Utilization": "Използван RAM",
"Random": "Произволен",
"Receive Only": "Receive Only",
"Recent Changes": "Последни промени",
"Reduced by ignore patterns": "Намалено посредством шаблон за игнориране",
"Release Notes": "Бележки по обновяването",
@@ -233,6 +235,7 @@
"Resume": "Пусни",
"Resume All": "Пускане на всичко",
"Reused": "Повторно използван",
"Revert Local Changes": "Revert Local Changes",
"Running": "Изпълнява се",
"Save": "Запази",
"Scan Time Remaining": "Оставащо време за сканиране",

View File

@@ -65,7 +65,7 @@
"Device ID": "ID del dispositiu",
"Device Identification": "Identificació del dispositiu",
"Device Name": "Nom del dispositiu",
"Device rate limits": "Device rate limits",
"Device rate limits": "Límits de la tasa del dispositiu",
"Device that last modified the item": "El dispositiu que va modificar el item per última vegada",
"Devices": "Dispositius",
"Disabled": "Desactivat",
@@ -109,8 +109,9 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Els arxius seran moguts a un directori .stversions a versions amb control de la data quan siguen reemplaçats o esborrats per Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Els fitxers són canviats a versions amb indicació de data en una carpeta \".stversions\" quant són reemplaçats o esborrats per 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.": "Els fitxers són protegits dels canvis fets en altres dispositius, però els canvis fets en aquest dispositiu seràn enviats a la resta del grup (cluster).",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Els fitxers es sincronitzen des-d'el cluster, però tots els canvis fets localment no s'enviaràn als altres dispositius.",
"Filesystem Notifications": "Notificacions del Sistema de Fitxers",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filesystem Watcher Errors": "Errors del Vigilant del Sistema de Fitxers",
"Filter by date": "Filtrar per data",
"Filter by name": "Filtrar per nom",
"Folder": "Carpeta",
@@ -119,7 +120,7 @@
"Folder Path": "Ruta de la carpeta",
"Folder Type": "Tipus de carpeta",
"Folders": "Carpetes",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
"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.": "Per a les següents carpetes va ocòrrer un error mentre es començava a vigilar els canvis. Es tornarà a intentar cada minut, així que potser els errors desapareguen pronte. Si persisteixen, tracta d'arreglar el motiu subjacent i demana ajuda si no pots.",
"Full Rescan Interval (s)": "Interval de l'Escaneig Complet (segons)",
"GUI": "IGU (Interfície Gràfica d'Usuari)",
"GUI Authentication Password": "Password d'autenticació de l'Interfície Gràfica d'Usuari (GUI)",
@@ -159,7 +160,7 @@
"Local State (Total)": "Estat Local (Total)",
"Log": "Registre",
"Log tailing paused. Click here to continue.": "Pausada l'adició de dades al registre. Polsa ací per continuar.",
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
"Log tailing paused. Scroll to bottom continue.": "Pausat el seguiment del registre. Es continua fins al final.",
"Logs": "Registres",
"Major Upgrade": "Actualització important",
"Mass actions": "Accions en masa",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guía ràpida de patrons suportats",
"RAM Utilization": "Utilització de la RAM",
"Random": "Aleatori",
"Receive Only": "Només recivir",
"Recent Changes": "Canvis Recents",
"Reduced by ignore patterns": "Reduït ignorant patrons",
"Release Notes": "Notes de la versió",
@@ -233,6 +235,7 @@
"Resume": "Continuar",
"Resume All": "Continuar Tot",
"Reused": "Reutilitzat",
"Revert Local Changes": "Revertir els canvis locals",
"Running": "Executant",
"Save": "Gravar",
"Scan Time Remaining": "Temps d'escaneig restant",
@@ -253,7 +256,7 @@
"Share With Devices": "Compartir amb els dispositius",
"Share this folder?": "Compartir aquesta carpeta?",
"Shared With": "Compartit amb",
"Sharing": "Sharing",
"Sharing": "Compartint",
"Show ID": "Mostrar ID",
"Show QR": "Mostrar QR",
"Show diff with previous version": "Mostrar les diferències amb la versió prèvia",

View File

@@ -1,5 +1,5 @@
{
"A device with that ID is already added.": "Přístroj s tímto ID je již přidán.",
"A device with that ID is already added.": "Zařízení s tímto ID je již přidáno.",
"A negative number of days doesn't make sense.": "Záporný počet dní nedává smysl.",
"A new major version may not be compatible with previous versions.": "Nová důležitá verze nemusí být kompatibilní s předchozími verzemi.",
"API Key": "API klíč",
@@ -7,7 +7,7 @@
"Action": "Akce",
"Actions": "Akce",
"Add": "Přidat",
"Add Device": "Přidat přístroj",
"Add Device": "Přidat zařízení",
"Add Folder": "Přidat adresář",
"Add Remote Device": "Přidat vzdálené zařízení",
"Add devices from the introducer to our device list, for mutually shared folders.": "Přidat zařízení ze zavaděče do našeho seznamu zařízení, pro vzájemně sdílené adresáře.",
@@ -27,7 +27,7 @@
"An external command handles the versioning. It has to remove the file from the synced folder.": "Verzování obstarává externí příkaz. Musí odstranit soubor ze sdíleného adresáře.",
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formát anonymního hlášení o používání byl změněn. Chcete přejít na nový formát?",
"Any devices configured on an introducer device will be added to this device as well.": "Jakékoliv přístroje nakonfigurované na zavaděči budou přidány také na tento přístroj.",
"Any devices configured on an introducer device will be added to this device as well.": "Jakákoliv zařízení nakonfigurovaná na zavaděči budou přidána také na toto zařízení.",
"Are you sure you want to remove device {%name%}?": "Skutečně chcete odebrat zařízení {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Skutečně chcete odebrat adresář {{label}}?",
"Are you sure you want to restore {%count%} files?": "Opravdu chcete obnovit {{count}} souborů?",
@@ -62,12 +62,12 @@
"Deleted": "Smazáno",
"Device": "Zařízení",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Zařízení \"{{name}}\" ({{device}} na {{address}}) se chce připojit. Přidat nové zařízení?",
"Device ID": "ID přístroje",
"Device Identification": "Identifikace přístroje",
"Device Name": "Jméno přístroje",
"Device rate limits": "Device rate limits",
"Device that last modified the item": "Poslední přístroj, který modifikoval položku",
"Devices": "Přístroje",
"Device ID": "ID zařízení",
"Device Identification": "Identifikace zařízení",
"Device Name": "Jméno zařízení",
"Device rate limits": "Rychlostní limity zařízení",
"Device that last modified the item": "Poslední zařízení, které změnilo položku",
"Devices": "Zařízení",
"Disabled": "Vypnuto",
"Disabled periodic scanning and disabled watching for changes": "Periodické skenování i sledování změn vypnuto",
"Disabled periodic scanning and enabled watching for changes": "Periodické skenování vypnuto; sledování změn zapnuto",
@@ -108,7 +108,8 @@
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do složky .stversions.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do verzí označených daty v adresáři .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do verzí označených daty ve složce .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Soubory jsou chráněny před změnami na ostatních přístrojích, ale změny provedené z tohoto přístroje budou rozeslány na zbytek clusteru.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Soubory jsou chráněny před změnami na ostatních zařízeních, ale změny provedené z tohoto zařízení budou rozeslány na zbytek clusteru.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Soubory jsou synchronizovány z clusteru, ale lokální změny nebudou rozesílány na ostatní zařízení.",
"Filesystem Notifications": "Oznámení souborového systému",
"Filesystem Watcher Errors": "Chyby sledování soubor. systému",
"Filter by date": "Vybrat podle data",
@@ -159,7 +160,7 @@
"Local State (Total)": "Místní status (Celkem)",
"Log": "Log",
"Log tailing paused. Click here to continue.": "Log pozastaven. Klikněte zde pro pokračování.",
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
"Log tailing paused. Scroll to bottom continue.": "Log pozastaven. Sjeďte dolů pro pokračování.",
"Logs": "Logy",
"Major Upgrade": "Důležitá aktualizace",
"Mass actions": "Hromadné akce",
@@ -167,12 +168,12 @@
"Maximum Age": "Maximální časový limit",
"Metadata Only": "Pouze metadata",
"Minimum Free Disk Space": "Minimální velikost volného místa na disku",
"Mod. Device": "Přístroj, který provedl modifikaci",
"Mod. Device": "Zařízení, které provedlo změnu",
"Mod. Time": "Čas modifikace",
"Move to top of queue": "Přesunout na začátek fronty",
"Multi level wildcard (matches multiple directory levels)": "Víceúrovňový zástupný znak (shoda skrz více úrovní složek)",
"Never": "Nikdy",
"New Device": "Nový přístroj",
"New Device": "Nové zařízení",
"New Folder": "Nový adresář",
"Newest First": "Od nejnovějšího",
"No": "Ne",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Rychlá nápověda k podporovaným vzorům",
"RAM Utilization": "Využití RAM",
"Random": "Náhodně",
"Receive Only": "Pouze příjem",
"Recent Changes": "Nedávné změny",
"Reduced by ignore patterns": "Redukováno o ignorované vzory",
"Release Notes": "Poznámky k vydání",
@@ -220,7 +222,7 @@
"Remove": "Odstranit",
"Remove Device": "Odebrat zařízení",
"Remove Folder": "Odebrat adresář",
"Required identifier for the folder. Must be the same on all cluster devices.": "Požadovaný identifikátor adresáře. Musí být stejný na všech zařízeních.",
"Required identifier for the folder. Must be the same on all cluster devices.": "Požadovaný identifikátor adresáře. Musí být stejný na všech zařízeních clusteru.",
"Rescan": "Opakovat skenování",
"Rescan All": "Opakovat skenování všech",
"Rescan Interval": "Interval opakování skenování",
@@ -233,6 +235,7 @@
"Resume": "Pokračovat",
"Resume All": "Pokračovat (vše)",
"Reused": "Opakovaně použité",
"Revert Local Changes": "Vrátit lokální změny",
"Running": "Probíhá",
"Save": "Uložit",
"Scan Time Remaining": "Zbývající čas skenování",
@@ -242,23 +245,23 @@
"Select a version": "Vyberte verzi",
"Select latest version": "Vybrat nejnovější verzi",
"Select oldest version": "Vybrat nejstarší verzi",
"Select the devices to share this folder with.": "Vybrat přístroje, se kterými sdílet tento adresář.",
"Select the folders to share with this device.": "Vybrat adresáře sdílené s tímto přístrojem.",
"Select the devices to share this folder with.": "Vybrat zařízení, se kterými sdílet tento adresář.",
"Select the folders to share with this device.": "Vybrat adresáře sdílené s tímto zařízením.",
"Send & Receive": "Odeslat a přijmout",
"Send Only": "Pouze odeslat",
"Settings": "Nastavení",
"Share": "Sdílet",
"Share Folder": "Sdílet adresář",
"Share Folders With Device": "Sdílet adresáře s tímto přístrojem",
"Share With Devices": "Sdílet s přístroji",
"Share Folders With Device": "Sdílet adresáře s tímto zařízením",
"Share With Devices": "Sdílet se zařízeními",
"Share this folder?": "Sdílet tento adresář?",
"Shared With": "Sdíleno s",
"Sharing": "Sharing",
"Sharing": "Sdílení",
"Show ID": "Zobrazit ID",
"Show QR": "Zobrazit QR",
"Show diff with previous version": "Ukázat rozdíl oproti předchozí verzi",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Bude odesíláno ostatním přístrojům jako výchozí jméno přístroje.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Pokud nebude vyplněno, bude nastaveno na jméno, které přístroj odesílá.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo ID zařízení na náhledu stavu clusteru. Bude odesíláno ostatním zařízením jako výchozí jméno zařízení.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo ID zařízení na náhledu stavu clusteru. Pokud nebude vyplněno, bude nastaveno na jméno, které zařízení odesílá.",
"Shutdown": "Vypnout",
"Shutdown Complete": "Vypnutí dokončeno",
"Simple File Versioning": "Jednoduché verzování souborů",
@@ -286,10 +289,10 @@
"The Syncthing admin interface is configured to allow remote access without a password.": "V nastavení aplikace Syncthing je povoleno vzdálené připojení k administrátorskému rozhraní bez zadání hesla.",
"The aggregated statistics are publicly available at the URL below.": "Souhrnné statistiky jsou veřejně dostupné na níže uvedené URL.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurace byla uložena, ale není aktivována. Pro aktivaci nové konfigurace je třeba restartovat Syncthing.",
"The device ID cannot be blank.": "ID přístroje nemůže být prázdné.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID přístroje, které je třeba vložit, lze nalézt v dialogu \"Akce > Zobrazit ID\" na druhém přístroji. Mezery a pomlčky nejsou nutné (budou ignorovány).",
"The device ID cannot be blank.": "ID zařízení nemůže být prázdné.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID zařízení, které je třeba vložit, lze nalézt v dialogu \"Akce > Zobrazit ID\" na druhém zařízení. Mezery a pomlčky nejsou nutné (budou ignorovány).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Šifrovaná data o využití jsou zasílána denně. Jsou používána pro zjištění nejobvyklejších platforem, velikosti adresářů a verzí aplikace. Pokud se hlášená data změní, budete opět upozorněni tímto dialogem.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Zadané ID přístroje není platné. Mělo by mít 52 nebo 56 znaků a mělo by obsahovat písmena a čísla. Mezery a pomlčky jsou nepovinné.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Zadané ID zařízení není platné. Mělo by mít 52 nebo 56 znaků a mělo by obsahovat písmena a čísla. Mezery a pomlčky jsou nepovinné.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "První parametr příkazové řádky je cesta k adresáři, druhý je relativní cesta v témže adresáři.",
"The folder ID cannot be blank.": "ID adresáře nemůže být prázdné.",
"The folder ID must be unique.": "ID adresáře musí být unikátní.",
@@ -341,8 +344,8 @@
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Pozor: Pokud používáte externí sledování změn jako {{syncthingInotify}}, měly byste se ujistit, že je toto sledování vypnuto.",
"Watch for Changes": "Sledovat změny",
"Watching for Changes": "Sledování změn",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového přístroje mějte na paměti, že je ho třeba také zadat na druhé straně.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového zařízení mějte na paměti, že je ho třeba také zadat na druhé straně.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč zařízeními. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi zařízeními.",
"Yes": "Ano",
"You can also select one of these nearby devices:": "Také můžete vybrat jedno z těchto okolních zařízení:",
"You can change your choice at any time in the Settings dialog.": "Vaši volbu můžete kdykoliv změnit v dialogu nastavení.",

View File

@@ -1,5 +1,5 @@
{
"A device with that ID is already added.": "En enhed, med dette id, er allerede tilføjet.",
"A device with that ID is already added.": "En enhed med dette id er allerede tilføjet.",
"A negative number of days doesn't make sense.": "Et negativt antal dage giver ikke mening.",
"A new major version may not be compatible with previous versions.": "En ny versionsudgivelse er måske ikke kompatibel med tidligere versioner.",
"API Key": "API-nøgle",
@@ -10,9 +10,9 @@
"Add Device": "Tilføj enhed",
"Add Folder": "Tilføj mappe",
"Add Remote Device": "Tilføj fjernenhed",
"Add devices from the introducer to our device list, for mutually shared folders.": "Add devices from the introducer to our device list, for mutually shared folders.",
"Add devices from the introducer to our device list, for mutually shared folders.": "Tilføj enheder fra den introducerende enhed til vores enhedsliste for gensidigt delte mapper.",
"Add new folder?": "Tilføj ny mappe",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Derudover vil intervallet for den komplette genskan blive forøget (60 gange, dvs. ny standard er 1 time). Du kan også konfigurere det manuelt for hver mappe senere efter at have valgt Nej.",
"Address": "Adresse",
"Addresses": "Adresser",
"Advanced": "Avanceret",
@@ -23,121 +23,122 @@
"Allowed Networks": "Tilladte netværk",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the shared folder.": "En ekstern kommando styrer versioneringen. Den skal fjerne filen fra den delte mappe.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "En ekstern kommando styrer versioneringen. Den skal fjerne filen fra den delte mappe. Hvis stien til programmet indeholder mellemrum, bør den sættes i anførselstegn.",
"An external command handles the versioning. It has to remove the file from the synced folder.": " ",
"Anonymous Usage Reporting": "Anonym brugerstatistik",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Any devices configured on an introducer device will be added to this device as well.": "Alle enheder som er konfigueret som en introducerende enhed, vil også blive tilføjet til denne enhed.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Den automatiske opdatering tilbyder nu valget mellem stabile - og udgivelses kandidater.",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formatet for anonym brugerstatistik er ændret. Vil du flytte til det nye format?",
"Any devices configured on an introducer device will be added to this device as well.": "Alle enheder som er konfigureret som en introducerende enhed, vil også blive tilføjet til denne enhed.",
"Are you sure you want to remove device {%name%}?": "Er du sikker på, at du vil fjerne enheden {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Er du sikker på, at du vil fjerne mappen {{label}}?",
"Are you sure you want to restore {%count%} files?": "Er du sikker på, at du vil genskabe {{count}} filer?",
"Auto Accept": "Autoacceptér",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Den automatiske opdatering tilbyder nu valget mellem stabile udgivelser og udgivelseskandidater.",
"Automatic upgrades": "Automatisk opdatering",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Available debug logging facilities:": "Available debug logging facilities:",
"Automatically create or share folders that this device advertises at the default path.": "Opret eller del automatisk mapper på standardstien, som denne enhed tilbyder.",
"Available debug logging facilities:": "Tilgængelige faciliteter for fejlretningslogning:",
"Be careful!": "Vær forsigtig!",
"Bugs": "Fejl",
"CPU Utilization": "CPU-forbrug",
"Changelog": "Udgivelsesnoter",
"Clean out after": "Rens efter",
"Click to see discovery failures": "Klik for at se opdagelses fejl ",
"Click to see discovery failures": "Klik for at se opdagelsesfejl",
"Close": "Luk",
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentering som bruges i starten af en linje",
"Comment, when used at the start of a line": "Kommentar, når den bruges i starten af en linje",
"Compression": "Anvend komprimering",
"Configured": "Konfigureret",
"Connection Error": "Tilslutnings fejl",
"Connection Type": "Tilslutningstype",
"Connections": "Connections",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.",
"Connections": "Forbindelser",
"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.": "Løbende overvågning af ændringer er nu tilgængeligt i Syncthing. Dette vil opfange ændringer på disken og igangsætte en skanning, men kun på ændrede stier. Fordelene er, er ændringer forplanter sig hurtigere, og at færre komplette skanninger er nødvendige.",
"Copied from elsewhere": "Kopieret fra et andet sted",
"Copied from original": "Kopieret fra originalen",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 de følgende bidragsydere:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 de følgende bidragsydere:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Creating ignore patterns, overwriting an existing file at {{path}}.",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Opretter ignoreringsmønstre; overskriver en eksisterende fil på {{path}}.",
"Danger!": "Fare!",
"Debugging Facilities": "Debugging Facilities",
"Default Folder Path": "Default Folder Path",
"Debugging Facilities": "Faciliteter til fejlretning",
"Default Folder Path": "Standardmappesti",
"Deleted": "Slettet",
"Device": "Enhed",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enheden\"{{name}}\" ({{device}} på {{address}}) vil gerne forbinde. Tilføj denne enhed ? ",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enheden{{name}} ({{device}} på {{address}}) vil gerne forbinde. Tilføj denne enhed?",
"Device ID": "Enheds-ID",
"Device Identification": "Enhedsidentifikation",
"Device Name": "Enhedsnavn",
"Device rate limits": "Device rate limits",
"Device that last modified the item": "Device that last modified the item",
"Device rate limits": "Enhedens hastighedsbegrænsning",
"Device that last modified the item": "Enhed, som sidst ændrede filen",
"Devices": "Enheder",
"Disabled": "Disabled",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disabled": "Deaktiveret",
"Disabled periodic scanning and disabled watching for changes": "Deaktiverede periodisk skanning og deaktiverede overvågning af ændringer",
"Disabled periodic scanning and enabled watching for changes": "Deaktiverede periodisk skanning og aktiverede overvågning af ændringer",
"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:",
"Disconnected": "Ikke tilsluttet",
"Discovered": "Opdaget",
"Discovery": "Opslag",
"Discovery Failures": "Opdagelses Fejl ",
"Do not restore": "Do not restore",
"Do not restore all": "Do not restore all",
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
"Do not restore": "Genskab ikke",
"Do not restore all": "Genskab ikke alle",
"Do you want to enable watching for changes for all your folders?": "Vil du aktivere løbende overvågning af ændringer for alle dine mapper?",
"Documentation": "Dokumentation",
"Download Rate": "Downloadhastighed",
"Downloaded": "Downloadet",
"Downloading": "Downloader",
"Edit": "Rediger",
"Edit Device": "Rediger enhed",
"Edit Folder": "Rediger mappe",
"Edit": "Redigér",
"Edit Device": "Redigér enhed",
"Edit Folder": "Redigér mappe",
"Editing": "Redigerer",
"Editing {%path%}.": "Redigerer {{path}}.",
"Enable NAT traversal": "Aktiver NAT",
"Enable Relaying": "Aktiver Relaying",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Angiv kommaseparerede adresser (\"tcp://ip:port\", \"tcp://host:port\") eller \"dynamic\" for at benytte automatisk opdagelse af adressen.",
"Enter ignore patterns, one per line.": "Vælg ignorer maske, én per linje.",
"Enable NAT traversal": "Aktivér NAT-traversering",
"Enable Relaying": "Aktivér videresending",
"Enabled": "Aktiveret",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Indtast et ikke-negativt tal (fx “2,35”) og vælg en enhed. Procentsatser er ud fra total diskstørrelse.",
"Enter a non-privileged port number (1024 - 65535).": "Indtast et ikke-priviligeret portnummer (102465535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Angiv kommaseparerede adresser (tcp://ip:port”, “tcp://host:port) eller dynamic for at benytte automatisk opdagelse af adressen.",
"Enter ignore patterns, one per line.": "Indtast ignoreringsmønstre, ét per linje.",
"Error": "Fejl",
"External File Versioning": "Ekstern fil-versionskontrol",
"External File Versioning": "Ekstern filversionering",
"Failed Items": "Mislykkede filer",
"Failed to load ignore patterns": "Failed to load ignore patterns",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Fejl i forbindelse med opkobling til IPv6 servere skal forventes hvis der ikke er IPv6 forbindelse. ",
"File Pull Order": "Filhentnings rækkefølge",
"Failed to load ignore patterns": "Indlæsning af ignoreringsmønstre mislykkedes",
"Failed to setup, retrying": "Opsætning mislykkedes; prøver igen",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Fejl i forbindelse med opkobling til IPv6-servere skal forventes, hvis der ikke er IPv6-forbindelse.",
"File Pull Order": "Hentningsrækkefølge for filer",
"File Versioning": "Filversionering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filtilladelses bits ignoreres når der søges efter ændringer. Bruges på FAT filsystemer. ",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer bliver flyttet til .stversions mappen, når de bliver erstattet eller slettet af Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til .stversions mappen når de erstattes eller slettes af Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer er flyttet til datostemplet versioner i en .stversions mappe når de bliver erstattet eller slettet af Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til data-stemplede versioner i en .stversions mappe når de erstattes eller slettes af 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 er beskyttet fra ændringer foretaget på andre enheder, men ændringerne på denne enhed vil blive sendt til alle andre tilknyttede enheder.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
"Filter by name": "Filter by name",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filtilladelsesbits ignoreres, når der søges efter ændringer. Brug på FAT-filsystemer.",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer bliver flyttet til .stversions-mappen, når de bliver erstattet eller slettet af Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til .stversions-mappen, når de erstattes eller slettes af Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttes til datostemplede versioner i en .stversions-mappe, når de erstattes eller slettes af Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til datostemplede versioner i en .stversions-mappe, når de erstattes eller slettes af 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 beskyttes mod ændringer, foretaget på andre enheder, men ændringerne på denne enhed vil blive sendt til alle andre tilknyttede enheder.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Filer synkroniseres fra tilknyttede enheder, men ændringer på denne enhed sendes ikke til andre enheder.",
"Filesystem Notifications": "Filsystemsnotifikationer",
"Filesystem Watcher Errors": "Fejl ved filsystemovervågning",
"Filter by date": "Filtrér efter dato",
"Filter by name": "Filtrér efter navn",
"Folder": "Mappe",
"Folder ID": "Mappe-ID",
"Folder Label": "Mappelabel",
"Folder Label": "Mappeetiket",
"Folder Path": "Mappesti",
"Folder Type": "Mappetype",
"Folders": "Mapper",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
"Full Rescan Interval (s)": "Full Rescan Interval (s)",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For de følgende mapper opstod en fejl ved start på overvågning af ændringer. Der prøves igen hvert minut, så fejlene går eventuelt væk snart. Hvis de forbliver, kan du prøve at rette den tilgrundliggende fejl eller spørge efter hjælp, hvis du ikke kan.",
"Full Rescan Interval (s)": "Interval for komplet genskan (sek.)",
"GUI": "GUI",
"GUI Authentication Password": "GUI-kodeord",
"GUI Authentication Password": "GUI-adgangskode",
"GUI Authentication User": "GUI-brugernavn",
"GUI Listen Address": "GUI Listen Address",
"GUI Listen Addresses": "GUI-lytteadresse",
"GUI Theme": "GUI tema",
"General": "General",
"GUI Listen Address": "GUI-lytteadresse",
"GUI Listen Addresses": "GUI-lytteadresser",
"GUI Theme": "GUI-tema",
"General": "Generalt",
"Generate": "Opret",
"Global Changes": "Globale ændringer",
"Global Discovery": "Globalt opslag",
"Global Discovery Servers": "Globale opslags servere",
"Global Discovery Servers": "Globale opslagsservere",
"Global State": "Global tilstand",
"Help": "Hjælp",
"Home page": "Hjem",
"Ignore": "Ignorer",
"Ignore Patterns": "Ignoreringsmaske",
"Ignore Permissions": "Ignorér filrettigheder",
"Ignore": "Ignorér",
"Ignore Patterns": "Ignoreringsmønstre",
"Ignore Permissions": "Ignorér rettigheder",
"Incoming Rate Limit (KiB/s)": "Indgående hastighedsbegrænsning (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Ukorrekt opsætning kan skade dine data og gøre Syncthing ude af stand til at fungere.",
"Introduced By": "Introduceret af",
@@ -145,179 +146,181 @@
"Inversion of the given condition (i.e. do not exclude)": "Det omvendte (dvs. undlad ikke)",
"Keep Versions": "Behold versioner",
"Largest First": "Største først",
"Last File Received": "Sidste modtaget fil",
"Last Scan": "Sidste skanning.",
"Last File Received": "Senest modtagne fil",
"Last Scan": "Seneste skanning",
"Last seen": "Sidst set",
"Later": "Senere",
"Latest Change": "Sidste ændring",
"Latest Change": "Seneste ændring",
"Learn more": "Lær mere",
"Listeners": "Lyttere",
"Loading data...": "Loading data...",
"Loading...": "Loading...",
"Loading data...": "Indlæser data",
"Loading...": "Indlæser…",
"Local Discovery": "Lokal opslag",
"Local State": "Lokal tilstand",
"Local State (Total)": "Lokal tilstand (total)",
"Log": "Log",
"Log tailing paused. Click here to continue.": "Log tailing paused. Click here to continue.",
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
"Logs": "Logs",
"Major Upgrade": "Ny version",
"Mass actions": "Mass actions",
"Master": "Mester ",
"Maximum Age": "Maks alder",
"Log": "Logbog",
"Log tailing paused. Click here to continue.": "Logfølgning pause. Klik her for at fortsætte.",
"Log tailing paused. Scroll to bottom continue.": "Logfølgning pause. Rul til bunden for at fortsætte.",
"Logs": "Logbog",
"Major Upgrade": "Opgradering til ny hovedversion",
"Mass actions": "Massehandlinger",
"Master": "Styrende",
"Maximum Age": "Maksimal alder",
"Metadata Only": "Kun metadata",
"Minimum Free Disk Space": "Mindst ledig diskplads",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Mod. Device": "Enhed for ændring",
"Mod. Time": "Tid for ændring",
"Move to top of queue": "Flyt til toppen af køen",
"Multi level wildcard (matches multiple directory levels)": "Flerniveau wildcard (matcher flere biblioteksniveauer)",
"Multi level wildcard (matches multiple directory levels)": "Flerniveau-wildcard (matcher flere mappeniveauer)",
"Never": "Aldrig",
"New Device": "Ny enhed",
"New Folder": "Ny mappe",
"Newest First": "Nyeste først",
"No": "Nej",
"No File Versioning": "Ingen filversion",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No File Versioning": "Ingen filversionering",
"No files will be deleted as a result of this operation.": "Ingen filer vil blive slettet som resultat af denne handling.",
"No upgrades": "Ingen opgraderinger",
"Normal": "Normal",
"Notice": "OBS",
"Notice": "Bemærk",
"OK": "OK",
"Off": "Slå fra",
"Off": "Deaktiveret",
"Oldest First": "Ældste først",
"Optional descriptive label for the folder. Can be different on each device.": "En frivillig beskrivelse af mappen. Kan være forskellig på hver enkelt enhed.",
"Optional descriptive label for the folder. Can be different on each device.": "En valgfri beskrivende etiket for mappen. Kan være forskellig på hver enkelt enhed.",
"Options": "Indstillinger",
"Out of Sync": "Ikke synkroniseret",
"Out of Sync Items": "Endnu ikke synkroniserede filer",
"Out of Sync Items": "Ikke synkroniserede filer",
"Outgoing Rate Limit (KiB/s)": "Udgående hastighedsbegrænsning (KiB/s)",
"Override Changes": "Overskriv ændringer",
"Path": "Sti",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sti til den lokale mappe. Vil blive oprettet hvis den ikke findes. Tilde tegnet (~) kan bruges som en forkortelse for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Sti hvor versioner skal gemmes ( lad dette felt være tom for at bruge .stversions mappen i den delte mappe)",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sti hvor versioner skal gemmes (efterlad tom for default .stversions mappe)",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Sti til den lokale mappe. Vil blive oprettet hvis den ikke findes. Tildetegnet (~) kan bruges som en forkortelse for",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Sti hvor nye automatisk accepterede mapper oprettes, så vel som standardstien der foreslås ved tilføjelse af nye mapper gennem brugerfladen. Tildetegnet (~) udvides til {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Sti hvor versioner skal gemmes (lad være tomt for at bruge .stversions-mappen i den delte mappe).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sti hvor versioner skal gemmes (lad være tomt for at bruge .stversions-mappen i den delte mappe).",
"Pause": "Pause",
"Pause All": "Sæt alt på pause",
"Paused": "Pauset",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Permissions": "Permissions",
"Please consult the release notes before performing a major upgrade.": "Tjek venligst udgivelsesnoterne før opgradering til en ny version.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "t vensligt en GUI bruger og kodeord i opsætningsdialogen.",
"Paused": "På pause",
"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",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning med et givent interval og lykkedes ikke med at opsætte overvågning af ændringer; prøver igen hvert minut:",
"Permissions": "Tilladelser",
"Please consult the release notes before performing a major upgrade.": "Tjek venligst udgivelsesnoterne før opgradering til en ny hovedversion.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Opret venligst en GUI-bruger og -adgangskode i opsætningen.",
"Please wait": "Vent venligst",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
"Prefix indicating that the file can be deleted if preventing directory removal": "Forstavelse, der indikerer, at filen kan slettes, hvis fjernelse at mappe undgåes",
"Prefix indicating that the pattern should be matched without case sensitivity": "Forstavelse, der indikerer det mønster, der skal sammenlignes uden versalfølsomhed",
"Preview": "Forhåndsvisning",
"Preview Usage Report": "Forhåndsvisning af forbrugsrapport",
"Quick guide to supported patterns": "Hurtig guide til supporteret mønstre",
"Quick guide to supported patterns": "Kvikguide til understøttede mønstre",
"RAM Utilization": "RAM-forbrug",
"Random": "Tilfældig",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Reduceret af ignorerings mønsteret. ",
"Receive Only": "Modtag kun",
"Recent Changes": "Nylige ændringer",
"Reduced by ignore patterns": "Reduceret af ignoreringsmønstre",
"Release Notes": "Udgivelsesnoter",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Udgivelseskandidater indeholder alle de nyeste funktioner og rettelser. De er ens med de traditionelle 2 ugers Syncthing udgivelser.",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Udgivelseskandidater indeholder alle de nyeste funktioner og rettelser. De er det samme som de traditionelle tougers-udgivelser af Syncthing.",
"Remote Devices": "Fjernenheder ",
"Remove": "Fjern",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Required identifier for the folder. Must be the same on all cluster devices.": "Nødvendig identifikation af mappen. Dette skal være det samme på alle enheder.",
"Remove Device": "Fjern enhed",
"Remove Folder": "Fjern mappe",
"Required identifier for the folder. Must be the same on all cluster devices.": "Nødvendig identifikator for mappen. Dette skal være det samme på alle enheder.",
"Rescan": "Skan igen",
"Rescan All": "Skan alt igen",
"Rescan Interval": "Genskannings interval",
"Rescans": "Rescans",
"Rescan Interval": "Genskanningsinterval",
"Rescans": "Genskanninger",
"Restart": "Genstart",
"Restart Needed": "Programmet kræver genstart",
"Restart Needed": "Genstart påkrævet",
"Restarting": "Genstarter",
"Restore": "Restore",
"Restore Versions": "Restore Versions",
"Restore": "Genskab",
"Restore Versions": "Genskab versioner",
"Resume": "Genoptag",
"Resume All": "Genoptag alt",
"Reused": "Genbrugt",
"Running": "Running",
"Revert Local Changes": "Opgiv lokale ændringer",
"Running": "Kører",
"Save": "Gem",
"Scan Time Remaining": "Tid tilbage af skanningen",
"Scanning": "Opdaterer",
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select a version": "Select a version",
"Select latest version": "Select latest version",
"Select oldest version": "Select oldest version",
"Select the devices to share this folder with.": "Vælg hvilke enheder du vil dele denne mappe med",
"Scanning": "Skanner",
"See external versioner help for supported templated command line parameters.": "Se hjælp til ekstern versionering for understøttede kommandolinjeparametre.",
"See external versioning help for supported templated command line parameters.": "Se hjælp til ekstern versionering for understøttede kommandolinjeparametre.",
"Select a version": "Vælg en version",
"Select latest version": "Vælg seneste version",
"Select oldest version": "Vælg ældste version",
"Select the devices to share this folder with.": "Vælg hvilke enheder du vil dele denne mappe med.",
"Select the folders to share with this device.": "Vælg hvilke mapper du vil dele med denne enhed.",
"Send & Receive": "Send & Modtag",
"Send Only": "Send Kun",
"Send & Receive": "Send og modtag",
"Send Only": "Send kun",
"Settings": "Indstillinger",
"Share": "Del",
"Share Folder": "Delt mappe",
"Share Folder": "Del mappe",
"Share Folders With Device": "Del mappe med enhed",
"Share With Devices": "Del med enhed",
"Share this folder?": "Del denne mappe",
"Share With Devices": "Del med enheder",
"Share this folder?": "Del denne mappe?",
"Shared With": "Delt med",
"Sharing": "Sharing",
"Sharing": "Deler",
"Show ID": "Vis ID",
"Show QR": "Vis QR",
"Show diff with previous version": "Show diff with previous version",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vist istedet for Enheds ID i klynge status. Vil blive vist på andre enheder som valgfrit standard navn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vist istedet for Enheds ID i klynge status. Vil blive opdateret til det navn som enheden viser, hvis det ikke er udfyldt.",
"Show diff with previous version": "Vis forskelle fra tidligere version",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Vises i stedet for enheds-ID i klyngestatus. Vil blive sendt til andre enheder som valgfrit standardnavn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Vises i stedet for enheds-ID i klyngestatus. Vil blive opdateret til det navn, som enheden sender, hvis det ikke er udfyldt.",
"Shutdown": "Luk ned",
"Shutdown Complete": "Nedlukning fuldført",
"Simple File Versioning": "Simpel fil versioner",
"Single level wildcard (matches within a directory only)": "Enkeltnivau wildcard (matcher kun inden for en mapp)",
"Size": "Size",
"Simple File Versioning": "Simpel filversionering",
"Single level wildcard (matches within a directory only)": "Enkeltniveau-wildcard (matcher kun inden for en mappe)",
"Size": "Størrelse",
"Smallest First": "Mindste først",
"Some items could not be restored:": "Some items could not be restored:",
"Some items could not be restored:": "Enkelte filer kunne ikke genskabes:",
"Source Code": "Kildekode",
"Stable releases and release candidates": "Stabile udgivelser og udgivelseskandidater ",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabile udgivelser er forsinket med omkring 2 uger. I denne periode gennemgår de tests som udgivelseskandidater. ",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabile udgivelser er forsinket med omkring to uger. I denne periode gennemgår de afprøvninger som udgivelseskandidater.",
"Stable releases only": "Kun stabile udgivelser",
"Staggered File Versioning": "Forskudte filversioner",
"Staggered File Versioning": "Forskudt filversionering",
"Start Browser": "Start browser",
"Statistics": "Statistikker",
"Stopped": "Stoppet",
"Support": "Support",
"Sync Protocol Listen Addresses": "Lytteadresser for indgående forbindelser",
"Support": "St",
"Sync Protocol Listen Addresses": "Lytteadresser for synkroniseringsprotokol",
"Syncing": "Synkroniserer",
"Syncthing has been shut down.": "Syncthing er blevet lukket ned.",
"Syncthing has been shut down.": "Syncthing er lukket ned.",
"Syncthing includes the following software or portions thereof:": "Syncthing indeholder følgende software eller dele heraf:",
"Syncthing is restarting.": "Syncthing genstarter",
"Syncthing is upgrading.": "Syncthing opgraderer",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ud til at være stoppet eller oplever problemer med din internetforbindels. Prøver igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Det ser ud til at Syncthiing har problemer med at udføre opgaven. Prøv at genopfriske siden eller genstarte Synching hvis problemet vedbliver.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administationsdelen er konfigureret til at blive fjernstyret uden kodeord.",
"The aggregated statistics are publicly available at the URL below.": "Den indsamlede statistik er offentlig tilgængelig på den nedenstående URL.",
"Syncthing is restarting.": "Syncthing genstarter.",
"Syncthing is upgrading.": "Syncthing opgraderer.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ser ud til at være stoppet eller oplever problemer med din internetforbindelse. Prøver igen",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Det ser ud til, at Syncthing har problemer med at udføre opgaven. Prøv at genindlæse siden eller genstarte Synching, hvis problemet vedbliver.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing-administationsfladen er sat op til at kunne fjernstyres uden adgangskode.",
"The aggregated statistics are publicly available at the URL below.": "Den indsamlede statistik er offentligt tilgængelig på den nedenstående URL.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen er gemt, men ikke aktiveret. Syncthing skal genstarte for at aktivere den nye konfiguration.",
"The device ID cannot be blank.": "Enhedens ID må ikke være tom.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Det enheds ID som skal indtastes her kan findes under menuen \"Handlinger > Vis ID\" på den anden enhed. Mellemrum og streger er frivillige. (De bliver ignoreret) ",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterede forbrugsrapport sendes dagligt. Den benyttes til at spore anvendte platforme, mappestørrelser og versioner. Hvis det typen af opsamlet data ændres på et senere tidspunkt, vil du blive spurgt om tilladelse igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det indtastede node ID ser ikke gyldigt ud. Det skal være en 52 eller 56 tegn streng, bestående af tal og bogstaver, eventuelt med mellemrum og bindestreger.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den første kommandolinjeparameter er mappestien og den anden parameter er den relative sti til mappen.",
"The folder ID cannot be blank.": "Mappe-ID må ikke være tom",
"The folder ID must be unique.": "Mappe-ID skal være unik",
"The folder path cannot be blank.": "Mappestien må ikke være tom",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De følgende intervaller er brugt: i den første time, er en version gemt hvert 30. sekund, for første da er en version gemt hver time, for de første 30 dage er en version gemt hver dag og indtil den maksimale periode er en version hver uge gemt.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Det enheds-ID, som skal indtastes her, kan findes under menuen Handlinger > Vis ID på den anden enhed. Mellemrum og bindestreger er valgfri (ignoreres).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterede forbrugsrapport sendes dagligt. Den benyttes til at spore anvendte platforme, mappestørrelser og programversioner. Hvis den opsamlede data ændres på et senere tidspunkt, vil du blive spurgt om tilladelse igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det indtastede enheds-ID ser ikke gyldigt ud. Det skal være en streng på 52 eller 56 tegn, der består af tal og bogstaver, eventuelt med mellemrum og bindestreger.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den første kommandolinjeparameter er mappestien og den anden parameter er den relative sti inden i mappen.",
"The folder ID cannot be blank.": "Mappe-ID må ikke være tom.",
"The folder ID must be unique.": "Mappe-ID skal være unik.",
"The folder path cannot be blank.": "Mappestien må ikke være tom.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De følgende intervaller er brugt: Inden for den første time bliver en version gemt hvert 30. sekund, inden for den første dag bliver en version gemt hver time, inden for de første 30 dage bliver en version gemt hver dag, og indtil den maksimale alder bliver en version gemt hver uge.",
"The following items could not be synchronized.": "Følgende filer kunne ikke synkroniseres.",
"The maximum age must be a number and cannot be blank.": "Maks alder skal være et nummer og må ikke være tom",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maks tid gamle udgaver skal gemmes (i dage, sæt lig med 0 for at beholde gamle versioner for altid)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Procentsatsen på den mindste ledige diskplads må ikke være negative og skal være mellem 0 og 100 (inklusiv).",
"The number of days must be a number and cannot be blank.": "Antallet af dage skal være et nummer og feltet må ikke være tomt",
"The number of days to keep files in the trash can. Zero means forever.": "Antal dage filer gemmes i skraldespanden. Nul betyder for evigt.",
"The number of old versions to keep, per file.": "Antallet af gamle versioner som gemmes, per fil.",
"The number of versions must be a number and cannot be blank.": "Antallet af versioner skal være et tal, og kan ikke være blankt.",
"The path cannot be blank.": "Stien må ikke være tom",
"The rate limit must be a non-negative number (0: no limit)": "Ratebegrænsningen må ikke være negative tal (0: ingen begrænsning)",
"The rescan interval must be a non-negative number of seconds.": "Genskanningsintervallet skal være et ikke-negativt antal sekunder",
"They are retried automatically and will be synced when the error is resolved.": "De prøves igen automatisk og vil blive synkroniseret når fejlen er løst.",
"This Device": "Denne Enhed",
"The maximum age must be a number and cannot be blank.": "Maksimal alder skal være et tal og feltet må ikke være tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den maksimale tid, en version skal gemmes (i dage; sæt lig med 0 for at beholde gamle versioner for altid).",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Procentsatsen for mindst ledig diskplads skal være et ikke-negativt tal mellem 0 og 100 (inklusive).",
"The number of days must be a number and cannot be blank.": "Antallet af dage skal være et tal og feltet må ikke være tomt.",
"The number of days to keep files in the trash can. Zero means forever.": "Antal dage, filer gemmes i papirkurven. Nul betyder for evigt.",
"The number of old versions to keep, per file.": "Antallet af gamle versioner som gemmes per fil.",
"The number of versions must be a number and cannot be blank.": "Antallet af versioner skal være et tal og feltet må ikke være tomt.",
"The path cannot be blank.": "Stien må ikke være tom.",
"The rate limit must be a non-negative number (0: no limit)": "Hastighedsbegrænsningen skal være et ikke-negativt tal (0: ingen begrænsning)",
"The rescan interval must be a non-negative number of seconds.": "Genskanningsintervallet skal være et ikke-negativt antal sekunder.",
"They are retried automatically and will be synced when the error is resolved.": "De prøves igen automatisk og vil blive synkroniseret, når fejlen er løst.",
"This Device": "Denne enhed",
"This can easily give hackers access to read and change any files on your computer.": "Dette gør det nemt for hackere at få adgang til at læse og ændre filer på din computer.",
"This is a major version upgrade.": "Dette er en ny version",
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
"This is a major version upgrade.": "Dette er en ny hovedversion.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Denne indstilling styrer den krævede ledige plads på hjemmedrevet (dvs. drevet med indeksdatabasen).",
"Time": "Tid",
"Time the item was last modified": "Time the item was last modified",
"Trash Can File Versioning": "Skraldespand fil versioner",
"Time the item was last modified": "Tidspunkt for seneste ændring af filen",
"Trash Can File Versioning": "Versionering med papirkurv",
"Type": "Type",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unavailable": "Ikke tilgængelig",
"Unavailable/Disabled by administrator or maintainer": "Ikke tilgængelig / deaktiveret af administrator eller vedligeholder",
"Undecided (will prompt)": "Ubestemt (du bliver spurgt)",
"Unknown": "Ukendt",
"Unshared": "Ikke delt",
"Unused": "Ubrugt",
@@ -325,34 +328,34 @@
"Updated": "Opdateret",
"Upgrade": "Upgradér",
"Upgrade To {%version%}": "Opgradér til {{version}}",
"Upgrading": "Opgradere",
"Upgrading": "Opgraderer",
"Upload Rate": "Uploadhastighed",
"Uptime": "Oppetid",
"Usage reporting is always enabled for candidate releases.": "Forbrugsraportering er altid slået til for udgivelseskandidater.",
"Use HTTPS for GUI": "Anvend HTTPS til GUI adgang",
"Usage reporting is always enabled for candidate releases.": "Forbrugsraportering er altid aktiveret for udgivelseskandidater.",
"Use HTTPS for GUI": "Anvend HTTPS til GUI-adgang",
"Version": "Version",
"Versions": "Versions",
"Versions Path": "Versions-sti",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner slettes automatisk, hvis de er ældre end den satte maksimum alder eller overstiger det tilladte antal filer i et interval.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Advarsel, denne sti er en rodmappe til den eksisterende mappe \"{{otherFolder}}\".",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel, denne sti er en rodmappe til den eksisterende mappe \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel, denne sti er en undermappe af en eksisterende mappe \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel, denne sti er en undermappe af en eksisterende mappe \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "Watch for Changes",
"Watching for Changes": "Watching for Changes",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når der tilføjes en ny enhed, vær da opmærksom på, at denne enhed også skal tilføjes den anden side.",
"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 der tilføjes en ny enhed, vær da opmærksom på at samme ID bruges til at forbinde mapperne på de forskellige enheder. Der er forskel på store og små bogstaver, og ID skal være fuldstændig identisk på alle enheder.",
"Versions": "Versioner",
"Versions Path": "Versionssti",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner slettes automatisk, hvis de er ældre end den givne maksimum alder eller overstiger det tilladte antal filer i et interval.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Advarsel: Denne sti er en forældermappe til den eksisterende mappe {{otherFolder}}.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel: Denne sti er en forældermappe til den eksisterende mappe {{otherFolderLabel}} ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel: Denne sti er en undermappe til den eksisterende mappe {{otherFolder}}.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Advarsel: Denne sti er en undermappe til den eksisterende mappe {{otherFolderLabel}} ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advarsel: Hvis du bruger en ekstern overvågning så som {{syncthingInotify}}, bør du være sikker på, at den er deaktiveret.",
"Watch for Changes": "Overvåg ændringer",
"Watching for Changes": "Overvågning af ændringer",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Når der tilføjes en ny enhed, vær da opmærksom på, at denne enhed også skal tilføjes i den anden ende.",
"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 der tilføjes en ny enhed, vær da opmærksom på at samme mappe-ID bruges til at forbinde mapper på de forskellige enheder. Der er forskel på store og små bogstaver, og ID skal være fuldstændig identisk på alle enheder.",
"Yes": "Ja",
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
"You can also select one of these nearby devices:": "Du kan også vælge en af disse enheder i nærheden:",
"You can change your choice at any time in the Settings dialog.": "Du kan altid ændre dit valg under indstillinger.",
"You can read more about the two release channels at the link below.": "Du kan læse mere om de to udgivelseskanaler på links herunder.",
"You can read more about the two release channels at the link below.": "Du kan læse mere om de to udgivelseskanaler på linket herunder.",
"You must keep at least one version.": "Du skal beholde mindst én version.",
"days": "dage",
"directories": "mapper",
"files": "filer",
"full documentation": "Fuld dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen \"{{folder}}\". ",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vil gerne dele mappen \"{{folderlabel}}\" ({{folder}})."
"full documentation": "fuld dokumentation",
"items": "filer",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen {{folder}}”.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker at dele mappen {{folderlabel}} ({{folder}})."
}

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dateien werden mit Datumsstempel versioniert und in den .stversions Ordner verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dateien werden mit Datumsstempel versioniert und in den .stversions Ordner verschoben, wenn sie von Syncthing ersetzt oder gelöscht werden.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dateien sind auf diesem Gerät schreibgeschützt. Auf diesem Gerät durchgeführte Veränderungen werden aber auf den Rest des Verbunds übertragen.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Dateisystembenachrichtigungen",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Nach Datum sortieren",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"RAM Utilization": "RAM-Auslastung",
"Random": "Zufall",
"Receive Only": "Receive Only",
"Recent Changes": "Letzte Änderungen",
"Reduced by ignore patterns": "Durch Ignoriermuster reduziert",
"Release Notes": "Veröffentlichungshinweise",
@@ -233,6 +235,7 @@
"Resume": "Fortsetzen",
"Resume All": "Alles fortsetzen",
"Reused": "Erneut benutzt",
"Revert Local Changes": "Revert Local Changes",
"Running": "Läuft gerade",
"Save": "Speichern",
"Scan Time Remaining": "Zeit für Scan verbleibend",

View File

@@ -109,6 +109,7 @@
"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.": "Τα αρχεία που σβήνονται ή αντικαθιστούνται από το Syncthing μετακινούνται σε έναν φάκελο .stversions με χρονοσφραγίδα.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Τα αρχεία προστατεύονται από αλλαγές που γίνονται σε άλλες συσκευές, αλλά όποιες αλλαγές γίνουν σε αυτή τη συσκευή θα αποσταλούν σε όλη τη συστάδα συσκευών.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Ειδοποιήσεις συστήματος αρχείων",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Φιλτράρισμα κατά ημερομηνία",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Σύντομη βοήθεια σχετικά με τα πρότυπα αναζήτησης που υποστηρίζονται",
"RAM Utilization": "Επιβάρυνση RAM",
"Random": "Τυχαία",
"Receive Only": "Receive Only",
"Recent Changes": "Πρόσφατες αλλαγές",
"Reduced by ignore patterns": "Περιορισμένα λόγω προτύπων αγνόησης",
"Release Notes": "Σημείωμα έκδοσης",
@@ -233,6 +235,7 @@
"Resume": "Συνέχεια",
"Resume All": "Συνέχιση όλων",
"Reused": "Χρησιμοποιήθηκε ξανά",
"Revert Local Changes": "Revert Local Changes",
"Running": "Εκτελείται",
"Save": "Αποθήκευση",
"Scan Time Remaining": "Εναπομείναντας χρόνος για τον έλεγχο ",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilisation",
"Random": "Random",
"Receive Only": "Receive Only",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release Notes",
@@ -233,6 +235,7 @@
"Resume": "Resume",
"Resume All": "Resume All",
"Reused": "Reused",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Save",
"Scan Time Remaining": "Scan Time Remaining",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Random": "Random",
"Receive Only": "Receive Only",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release Notes",
@@ -233,6 +235,7 @@
"Resume": "Resume",
"Resume All": "Resume All",
"Reused": "Reused",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Save",
"Scan Time Remaining": "Scan Time Remaining",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al date stampitaj versioj en .stversiaj dosierujo kiam ili estas anstataŭigitaj aŭ forigitaj en Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al datostampitaj versioj en .stversions dosierujoj kiam ili estas anstataŭigitaj aŭ forigitaj en 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.": "Dosieroj estas protektataj kontraŭ ŝanĝoj faritaj en aliaj aparatoj, sed ŝanĝoj faritaj en ĉi tiu aparato estos senditaj al cetera parto de la grupo.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Rapida gvidisto al subtenata ŝablonoj",
"RAM Utilization": "RAM Utiligado",
"Random": "Hazarda",
"Receive Only": "Receive Only",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Reduktita per ignorado de la ŝablonoj",
"Release Notes": "Eldonitaj Notoj",
@@ -233,6 +235,7 @@
"Resume": "Daŭrigu",
"Resume All": "Daŭrigu Ĉion",
"Reused": "Reuzita",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Konservu",
"Scan Time Remaining": "Skanada Restanta Tempo",

View File

@@ -12,7 +12,7 @@
"Add Remote Device": "Añadir un dispositivo",
"Add devices from the introducer to our device list, for mutually shared folders.": "Añadir dispositivos desde el introductor a nuestra lista de dispositivos, para las carpetas compartidas mutuamente.",
"Add new folder?": "¿Agregar una carpeta nueva?",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "De manera adicional, el intervalo de escaneo será incrementado (por ejemplo, times 60, establece un nuevo intervalo por defecto de una hora). También puedes configurarlo manualmente para cada carpeta tras elegir el número.",
"Address": "Dirección",
"Addresses": "Direcciones",
"Advanced": "Avanzado",
@@ -23,19 +23,19 @@
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabético",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Un comando externo gestiona las versiones. Tiene que eliminar el fichero de la carpeta compartida.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Un comando externo maneja el versionado. Tiene que eliminar el fichero de la carpeta compartida. Si la ruta a la aplicación contiene espacios, hay que escribirla entre comillas.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando externo controla la versión. El fichero debe ser eliminado de la carpeta sincronizada.",
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
"Anonymous usage report format has changed. Would you like to move to the new format?": "El formato del informe anónimo de uso ha cambiado. ¿Quieres cambiar al nuevo formato?",
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo de introducción será añadido también.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?",
"Auto Accept": "Auto Accept",
"Are you sure you want to remove device {%name%}?": "¿Estás seguro de que quieres quitar el dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "¿Estás seguro de que quieres quitar la carpeta {{label}}?",
"Are you sure you want to restore {%count%} files?": "¿Estás seguro de que quieres restaurar {{count}} ficheros?",
"Auto Accept": "Auto aceptar",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Ahora la actualización automática permite elegir entre versiones estables o versiones candidatas.",
"Automatic upgrades": "Actualizaciones automáticas",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Available debug logging facilities:": "Available debug logging facilities:",
"Automatically create or share folders that this device advertises at the default path.": "Crear o compartir automáticamente las carpetas que este dispositivo anuncia en la ruta por defecto.",
"Available debug logging facilities:": "Ayudas disponibles para la depuración del registro:",
"Be careful!": "¡Ten cuidado!",
"Bugs": "Errores",
"CPU Utilization": "Uso de CPU",
@@ -49,36 +49,36 @@
"Configured": "Configurado",
"Connection Error": "Error de conexión",
"Connection Type": "Tipo de conexión",
"Connections": "Connections",
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.",
"Connections": "Conexiones",
"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.": "La vigilancia continua de los cambios está ahora disponible dentro de Syncthing. Se detectarán los cambios en el disco y se programará un escaneo solo en las rutas modificadas. Los beneficios son que los cambios se propagan más rápidamente y que se necesitan menos escaneos.",
"Copied from elsewhere": "Copiado de otro sitio",
"Copied from original": "Copiado del original",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes Colaboradores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 Los siguientes colaboradores:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Crear patrones a ignorar, sobreescribiendo un fichero existente en {{path}}.",
"Danger!": "¡Peligro!",
"Debugging Facilities": "Debugging Facilities",
"Default Folder Path": "Default Folder Path",
"Debugging Facilities": "Ayudas a la depuración",
"Default Folder Path": "Ruta de la carpeta por defecto",
"Deleted": "Eliminado",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "El dispositivo \"{{name}}\" ({{device}} en la dirección {{address}}) quiere conectarse. Añadir nuevo dispositivo?",
"Device ID": "ID del Dispositivo",
"Device Identification": "Identificación del Dispositivo",
"Device Name": "Nombre del Dispositivo",
"Device rate limits": "Device rate limits",
"Device that last modified the item": "Device that last modified the item",
"Device rate limits": "Límites de la tasa del dispositivo",
"Device that last modified the item": "Último dispositivo que cambió el objeto",
"Devices": "Dispositivos",
"Disabled": "Disabled",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disabled": "Desactivado",
"Disabled periodic scanning and disabled watching for changes": "Desactivados el escaneo periódico y la vigilancia de cambios",
"Disabled periodic scanning and enabled watching for changes": "Desactivado el escaneo periódico y activada la vigilancia de cambios",
"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:",
"Disconnected": "Desconectado",
"Discovered": "Descubierto",
"Discovery": "Descubrimiento",
"Discovery Failures": "Fallos de Descubrimiento",
"Do not restore": "Do not restore",
"Do not restore all": "Do not restore all",
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
"Do not restore": "No restaurar",
"Do not restore all": "No restaurar todo",
"Do you want to enable watching for changes for all your folders?": "Quieres activar la vigilancia de cambios para todas tus carpetas?",
"Documentation": "Documentación",
"Download Rate": "Velocidad de descarga",
"Downloaded": "Descargado",
@@ -90,7 +90,7 @@
"Editing {%path%}.": "Editando {{path}}.",
"Enable NAT traversal": "Permitir NAT transversal",
"Enable Relaying": "Habilitar Retransmisión",
"Enabled": "Enabled",
"Enabled": "Activado",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introduce un número no negativo (por ejemplo, \"2.35\") y selecciona una unidad. Los porcentajes son como parte del tamaño total del disco.",
"Enter a non-privileged port number (1024 - 65535).": "Introduce un puerto sin privilegios (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduzca las direcciones, separadas por comas (\"tcp://ip:port\", \"tcp://host:port\"), o \"dynamic\" para llevar a cabo el descubrimiento automático de la dirección.",
@@ -98,8 +98,8 @@
"Error": "Error",
"External File Versioning": "Versionado externo de fichero",
"Failed Items": "Elementos fallidos",
"Failed to load ignore patterns": "Failed to load ignore patterns",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to load ignore patterns": "Fallo al cargar los patrones a ignorar",
"Failed to setup, retrying": "Fallo al configurar, reintentando",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Se espera un fallo al conectar a los servidores IPv6 si no hay conectividad IPv6.",
"File Pull Order": "Orden de obtención de los ficheros",
"File Versioning": "Versionado de ficheros",
@@ -109,18 +109,19 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Los ficheros son movidos a una carpeta .stversions a versiones con control de fecha cuando son reemplazados o borrados por Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Los ficheros son cambiados a versiones con indicación de fecha en una carpeta \".stversions\" cuando son reemplazados o borrados por 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.": "Los ficheros son protegidos por los cambios hechos en otros dispositivos, pero los cambios hechos en este dispositivo serán enviados al resto del grupo (cluster).",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
"Filter by name": "Filter by name",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Los ficheros se sincronizan desde el cluster, pero cualquier cambio hecho localmente no se enviará a los otros dispositivos.",
"Filesystem Notifications": "Notificaciones del sistema de ficheros",
"Filesystem Watcher Errors": "Errores del vigilante del sistema de ficheros",
"Filter by date": "Filtrar por fecha",
"Filter by name": "Filtrar por nombre",
"Folder": "Carpeta",
"Folder ID": "ID de carpeta",
"Folder Label": "Etiqueta de la Carpeta",
"Folder Path": "Ruta de la carpeta",
"Folder Type": "Tipo de carpeta",
"Folders": "Carpetas",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
"Full Rescan Interval (s)": "Full Rescan Interval (s)",
"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.": "Para las siguientes carpetas ocurrió un error cuando se empezó a vigilar los cambios. Se reintentará cada minuto, así que puede ser que los errores desaparezcan pronto. Si persisten, intenta solucionar el problema subyacente y pide ayuda en el caso de que no puedas.",
"Full Rescan Interval (s)": "Intervalo para el reescaneo completo (s)",
"GUI": "GUI",
"GUI Authentication Password": "Password de la Interfaz Gráfica de Usuario (GUI)",
"GUI Authentication User": "Autentificación de usuario de la Interfaz Gráfica de Usuario (GUI)",
@@ -152,23 +153,23 @@
"Latest Change": "Último Cambio",
"Learn more": "Saber más",
"Listeners": "Oyentes",
"Loading data...": "Loading data...",
"Loading...": "Loading...",
"Loading data...": "Cargando datos...",
"Loading...": "Cargando...",
"Local Discovery": "Descubrimiento local",
"Local State": "Estado local",
"Local State (Total)": "Estado Local (Total)",
"Log": "Log",
"Log tailing paused. Click here to continue.": "Log tailing paused. Click here to continue.",
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
"Logs": "Logs",
"Log": "Registro",
"Log tailing paused. Click here to continue.": "Pausada la continuación del registro. Pulsar aquí para continuar.",
"Log tailing paused. Scroll to bottom continue.": "Pausada la continuación del registro. Continúa hasta el final.",
"Logs": "Registros",
"Major Upgrade": "Actualización importante",
"Mass actions": "Mass actions",
"Mass actions": "Acciones en masa",
"Master": "Maestro",
"Maximum Age": "Edad máxima",
"Metadata Only": "Sólo metadatos",
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
"Mod. Device": "Mod. Device",
"Mod. Time": "Mod. Time",
"Mod. Device": "Dispositivo modificador",
"Mod. Time": "Tiempo de la modificación",
"Move to top of queue": "Mover al principio de la cola",
"Multi level wildcard (matches multiple directory levels)": "Comodín multinivel (coincide con múltiples niveles de directorio)",
"Never": "Nunca",
@@ -177,7 +178,7 @@
"Newest First": "El más nuevo primero",
"No": "No",
"No File Versioning": "Sin versionado de fichero",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No files will be deleted as a result of this operation.": "No se borraron ficheros como resultado de esta operación",
"No upgrades": "Sin actualizaciones",
"Normal": "Normal",
"Notice": "Aviso",
@@ -192,56 +193,58 @@
"Override Changes": "Anular cambios",
"Path": "Parche",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta a la carpeta en la máquina local. Se creará si no existe. El carácter de la tilde (~) puede usarse como atajo.",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Se creará la ruta donde se crearán las nuevas carpetas aceptadas automáticaments, así como la ruta sugerida por defecto cuando se añaden nuevas carpetas a través del Interfaz de Usuario. El carácter de la virgulilla (~) se extiende a {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "La ruta donde las versiones deben ser almacenadas (dejar vacío para el directorio .stversions por defecto en la carpeta compartida).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ruta donde se almacenarán las versiones (dejar vacío para usar la carpeta por defecto \".stversions\").",
"Pause": "Pausar",
"Pause All": "Pausar todo",
"Paused": "Pausado",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Permissions": "Permissions",
"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",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Escaneo periódico en un intervalo determinado y falló la configuración de la vigilancia de cambios, se reintentará cada 1 minuto:",
"Permissions": "Permisos",
"Please consult the release notes before performing a major upgrade.": "Por favor, consultar las notas de la versión antes de realizar una actualización importante.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, introduzca un Usuario y Contraseña para la Autenticación de la Interfaz de Usuario en el panel de Ajustes.",
"Please wait": "Por favor, espere",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefix indicating that the file can be deleted if preventing directory removal",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefix indicating that the pattern should be matched without case sensitivity",
"Prefix indicating that the file can be deleted if preventing directory removal": "El prefijo indica que el fichero puede ser borrado si se previene la eliminación de directorios",
"Prefix indicating that the pattern should be matched without case sensitivity": "El prefijo indica que el patrón se comparará sin tener en cuenta las mayúsculas",
"Preview": "Vista previa",
"Preview Usage Report": "Informe de uso de vista previa",
"Quick guide to supported patterns": "Guía rápida de patrones soportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatorio",
"Recent Changes": "Recent Changes",
"Receive Only": "Solo recibir",
"Recent Changes": "Cambios recientes",
"Reduced by ignore patterns": "Reducido por patrones de ignorar",
"Release Notes": "Notas de la versión",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Las versiones candidatas contienen las últimas funcionalidades y correcciones. Son similares a las tradicionales versiones bisemanales de Syncthing.",
"Remote Devices": "Otros dispositivos",
"Remove": "Eliminar",
"Remove Device": "Remove Device",
"Remove Folder": "Remove Folder",
"Remove Device": "Quitar dispositivo",
"Remove Folder": "Quitar carpeta",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador requerido para la carpeta. Debe ser el mismo en todos los dispositivos del clúster.",
"Rescan": "Volver a analizar",
"Rescan All": "Volver a analizar Todo",
"Rescan Interval": "Intervalo de análisis",
"Rescans": "Rescans",
"Rescans": "Reescaneos",
"Restart": "Reiniciar",
"Restart Needed": "Reinicio necesario",
"Restarting": "Reiniciando",
"Restore": "Restore",
"Restore Versions": "Restore Versions",
"Restore": "Restaurar",
"Restore Versions": "Restaurar versiones",
"Resume": "Continuar",
"Resume All": "Continuar todo",
"Reused": "Reutilizado",
"Running": "Running",
"Revert Local Changes": "Revertir los cambios locales",
"Running": "Ejecutando",
"Save": "Guardar",
"Scan Time Remaining": "Tiempo Restante de Escaneo",
"Scanning": "Analizando",
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select a version": "Select a version",
"Select latest version": "Select latest version",
"Select oldest version": "Select oldest version",
"See external versioner help for supported templated command line parameters.": "Consultar la ayuda externa del versionador para ver las plantillas de los parámetros de línea de comandos",
"See external versioning help for supported templated command line parameters.": "Consultar la ayuda externa del versionado para ver las plantillas de los parámetros de línea de comandos",
"Select a version": "Selecciona una versión",
"Select latest version": "Selecciona la última versión",
"Select oldest version": "Selecciona la versión más antigua",
"Select the devices to share this folder with.": "Selecciona los dispositivos con los que compartir esta carpeta.",
"Select the folders to share with this device.": "Selecciona las carpetas para compartir con este dispositivo.",
"Send & Receive": "Enviar y Recibir",
@@ -253,19 +256,19 @@
"Share With Devices": "Compartir con dispositivos",
"Share this folder?": "¿Deseas compartir esta carpeta?",
"Shared With": "Compartir con",
"Sharing": "Sharing",
"Sharing": "Compartiendo",
"Show ID": "Mostrar ID",
"Show QR": "Mostrar QR",
"Show diff with previous version": "Show diff with previous version",
"Show diff with previous version": "Muestra las diferencias con la versión previa",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Se muestra en lugar del ID del dispositivo en el estado del grupo (cluster). Se notificará a los otros dispositivos como nombre opcional por defecto.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Se muestra en lugar del ID del dispositivo en el estado del grupo (cluster). Se actualizará al nombre que el dispositivo anuncia si se deja vacío.",
"Shutdown": "Apagar",
"Shutdown Complete": "Apagar completamente",
"Simple File Versioning": "Versionado simple de fichero",
"Single level wildcard (matches within a directory only)": "Comodín de nivel único (coincide solamente dentro de un directorio)",
"Size": "Size",
"Size": "Tamaño",
"Smallest First": "El más pequeño primero",
"Some items could not be restored:": "Some items could not be restored:",
"Some items could not be restored:": "Algunos ítems no se pudieron restaurar:",
"Source Code": "Código fuente",
"Stable releases and release candidates": "Versiones estables y versiones candidatas",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Las versiones estables son publicadas cada dos semanas. Durante este tiempo son probadas como versiones candidatas.",
@@ -312,12 +315,12 @@
"This is a major version upgrade.": "Hay una actualización importante.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este ajuste controla el espacio libre necesario en el disco principal (por ejemplo, el índice de la base de datos).",
"Time": "Hora",
"Time the item was last modified": "Time the item was last modified",
"Time the item was last modified": "Tiempo en el que se modificó el ítem por última vez",
"Trash Can File Versioning": "Versionado de archivos de la papelera",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Undecided (will prompt)": "Undecided (will prompt)",
"Unavailable": "No disponible",
"Unavailable/Disabled by administrator or maintainer": "No disponible/Desactivado por el administrador o el mantenedor",
"Undecided (will prompt)": "Aún no decidido (se preguntará al usuario)",
"Unknown": "Desconocido",
"Unshared": "No compartido",
"Unused": "No usado",
@@ -331,20 +334,20 @@
"Usage reporting is always enabled for candidate releases.": "El informe de uso está siempre habilitado en las versiones candidatas.",
"Use HTTPS for GUI": "Usar HTTPS para la Interfaz Gráfica de Usuario (GUI)",
"Version": "Versión",
"Versions": "Versions",
"Versions": "Versiones",
"Versions Path": "Ruta de las versiones",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Las versiones se borran automáticamente si son más antiguas que la edad máxima o exceden el número de ficheros permitidos en un intervalo.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "¡Peligro! Esta ruta es un directorio principal de la carpeta ya existente \"{{otherFolder}}\".",
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "'Peligro! Esta ruta es un subdirectorio de la carpeta ya existente \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Peligro! Esta ruta es un subdirectorio de una carpeta ya existente llamada \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Peligro, esta ruta es un subdirectorio de una carpeta ya existente llamada \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Warning: If you are using an external watcher like {{syncthingInotify}}, you should make sure it is deactivated.",
"Watch for Changes": "Watch for Changes",
"Watching for Changes": "Watching for Changes",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Advertencia: Si estás usando un vigilante externo como {{syncthingInotify}}, deberías asegurarte de que está desactivado.",
"Watch for Changes": "Vigilar los cambios",
"Watching for Changes": "Vigilando los cambios",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añada un nuevo dispositivo, tenga en cuenta que este debe añadirse también en el otro lado.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Cuando añada una nueva carpeta, tenga en cuenta que su ID se usa para unir carpetas entre dispositivos. Son sensibles a las mayúsculas y deben coincidir exactamente entre todos los dispositivos.",
"Yes": "Si",
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
"You can also select one of these nearby devices:": "Puedes seleccionar también uno de estos dispositivos cercanos:",
"You can change your choice at any time in the Settings dialog.": "Puedes cambiar tu elección en cualquier momento en el panel de Ajustes.",
"You can read more about the two release channels at the link below.": "Puedes leer más sobre los dos método de publicación de versiones en el siguiente enlace.",
"You must keep at least one version.": "Debes mantener al menos una versión.",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Los ficheros son movidos a una carpeta .stversions a versiones con control de fecha cuando son reemplazados o borrados por Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Los ficheros son cambiados a versiones con indicación de fecha en una carpeta \".stversions\" cuando son reemplazados o borrados por 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.": "Los ficheros son protegidos por los cambios hechos en otros dispositivos, pero los cambios hechos en este dispositivo serán enviados al resto del grupo (cluster).",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Notificaciones del sistema de archivos",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filtrar por fecha",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guía rápida de patrones soportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatorio",
"Receive Only": "Receive Only",
"Recent Changes": "Cambios recientes",
"Reduced by ignore patterns": "Reducido por patrones de ignorar",
"Release Notes": "Notas de la versión",
@@ -233,6 +235,7 @@
"Resume": "Continuar",
"Resume All": "Continuar todo",
"Reused": "Reutilizado",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Guardar",
"Scan Time Remaining": "Tiempo Restante de Escaneo",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Syncthing-ek aldatzen edo kentzen dituelarik, fitxeroak \".stbertsioak\" peko-errepertoriorat lekutuak dira, jatorrizkoaren berdin berdina den zuhaitz-formako batean",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": ".stbersioak azpi karpetan lekutuko eta ordu-markatuko dira fitxeroak, egitura errelatibo berdin batean, Syncthing-ek aldatu edo ezeztatuko dituelarik.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Beste tresnetan eginak izanen diren aldaketetatik zainduak izanen dira fitxeroak; haatik, tresna huntan egindako aldaketak besteeri hedatuak izanen dira.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Eredu konpatibleen gidaliburuxka",
"RAM Utilization": "RAM aren erabiltzea",
"Random": "Aleatorioa",
"Receive Only": "Receive Only",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "Baztertze eredu batzuk mugatuak",
"Release Notes": "Bertsioen notak",
@@ -233,6 +235,7 @@
"Resume": "Berriz hastea",
"Resume All": "Dena berriz hastea",
"Reused": "Berriz erabilia",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Grabatu",
"Scan Time Remaining": "Gelditzen den azterketa denbora",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Quand ils sont remplacés ou supprimés par Syncthing, les fichiers sont déplacés et horodatés vers le sous-répertoire .stversions dans une arborescence relative identique à celle de l'original.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Quand ils sont remplacés ou supprimés par Syncthing, les fichiers sont déplacés et horodatés vers le sous-répertoire .stversions dans une arborescence relative identique à celle de l'original.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Les fichiers sont protégés des changements réalisés sur les autres appareils, mais les changements réalisés sur celui-ci seront transférés aux autres.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guide rapide des masques compatibles ci-dessous",
"RAM Utilization": "Utilisation de la RAM",
"Random": "Aléatoire",
"Receive Only": "Receive Only",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
"Release Notes": "Notes de version",
@@ -233,6 +235,7 @@
"Resume": "Reprise",
"Resume All": "Tout libérer",
"Reused": "Réutilisé",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Enregistrer",
"Scan Time Remaining": "Temps d'analyse restant",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Quand ils sont remplacés ou supprimés par Syncthing, les fichiers sont déplacés et horodatés vers le sous-répertoire .stversions dans une arborescence relative identique à celle de l'original.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Quand ils sont remplacés ou supprimés par Syncthing, les fichiers sont déplacés et horodatés vers le sous-répertoire .stversions dans une arborescence relative identique à celle de l'original.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Les fichiers sont protégés des changements réalisés sur les autres appareils, mais les changements réalisés sur celui-ci seront transférés aux autres.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Les fichiers sont synchronisés à partir du cluster, mais les modifications apportées localement ne seront pas envoyées aux autres appareils.",
"Filesystem Notifications": "Notifications du système de fichiers",
"Filesystem Watcher Errors": "Erreurs de la surveillance du système de fichiers",
"Filter by date": "Filtrer par date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guide rapide des masques compatibles ci-dessous",
"RAM Utilization": "Utilisation de la RAM",
"Random": "Aléatoire",
"Receive Only": "Réception seulement",
"Recent Changes": "Changements récents...",
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
"Release Notes": "Notes de version",
@@ -233,6 +235,7 @@
"Resume": "Reprise",
"Resume All": "Tout libérer",
"Reused": "Réutilisé",
"Revert Local Changes": "Annuler les modifications locales",
"Running": "En cours",
"Save": "Enregistrer",
"Scan Time Remaining": "Temps d'analyse restant",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Triemen wurde ferset nei mei datum stimpele ferzjes yn in .stversions map wannear troch Syncthing ferfangen of fuortsmiten.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Triemen wurde ferset nei in mei datum stimpele ferzjes yn in .stversions map wannear troch Syncthing ferfangen of fuortsmiten.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Triemen binne ymmún foar feroarings makke troch oare apparaten, mar feroarings makke op dit apparaat wurde nei de rest fan 'e bondel ferstjoerd.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Meldingen fan Triemsysteem",
"Filesystem Watcher Errors": "Triemsysteemsjoggerflaters",
"Filter by date": "Op datum filterje",
@@ -159,7 +160,7 @@
"Local State (Total)": "Lokale tastân (Folledich)",
"Log": "Loch",
"Log tailing paused. Click here to continue.": "Loch-sturt skofte. Klik hjir om fjirder te gean.",
"Log tailing paused. Scroll to bottom continue.": "Log tailing paused. Scroll to bottom continue.",
"Log tailing paused. Scroll to bottom continue.": "Loch-sturt skofte. Rolje helendal nei ûnder om fjirder te gean.",
"Logs": "Lochs",
"Major Upgrade": "Wichtige fernijing",
"Mass actions": "Massa-aksjes",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Fluch-paadwizer foar stipe patroanen",
"RAM Utilization": "RAM-brûken",
"Random": "Willekeurich",
"Receive Only": "Receive Only",
"Recent Changes": "Resinte Feroarings",
"Reduced by ignore patterns": "Ferlytse troch negear-patroanen",
"Release Notes": "Utjeftenotysjes",
@@ -233,6 +235,7 @@
"Resume": "Trochgean",
"Resume All": "Alles trochgean litte",
"Reused": "Opnij brûkt",
"Revert Local Changes": "Revert Local Changes",
"Running": "Rint",
"Save": "Bewarje",
"Scan Time Remaining": "Oerbleaune skentiid",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Ha a Syncthing felülírja vagy törli a fájlokat, akkor azok a .stversions könyvtárba lesznek áthelyezve, időbélyegzővel ellátva.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Ha a Syncthing felülírja vagy törli a fájlokat, akkor azok a .stversions mappába lesznek áthelyezve, időbélyegzővel ellátva.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "A fájlok védve vannak a más eszközökön történt változásokkal szemben, de az ezen az eszközön történt változások érvényesek lesznek a többire.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "A fájlok szinkronizálva lesznek a fürtről, de semmilyen helyi módosítás nem lesz elküldve más eszközökre.",
"Filesystem Notifications": "Fájlrendszer értesítések",
"Filesystem Watcher Errors": "Fájlrendszerfigyelési hibák",
"Filter by date": "Szűrés dátumra",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
"RAM Utilization": "Memóriahasználat",
"Random": "Véletlenszerű",
"Receive Only": "Csak fogadás",
"Recent Changes": "Utolsó módosítások",
"Reduced by ignore patterns": "Kihagyási mintákkal csökkentve",
"Release Notes": "Kiadási megjegyzések",
@@ -233,6 +235,7 @@
"Resume": "Folytatás",
"Resume All": "Mindent folytat",
"Reused": "Újrafelhasználva",
"Revert Local Changes": "Helyi módosítások visszavonása",
"Running": "Fut",
"Save": "Mentés",
"Scan Time Remaining": "Fennmaradó átnézési idő",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "I file sostituiti o eliminati da Syncthing vengono datati e spostati in una cartella .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "I file sostituiti o eliminati da Syncthing vengono datati e spostati in una cartella .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "I file sono protetti dalle modifiche effettuate negli altri dispositivi, ma le modifiche effettuate in questo dispositivo verranno inviate anche al resto del cluster.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Notifiche del filesystem",
"Filesystem Watcher Errors": "Errori nel monitoraggio del filesystem",
"Filter by date": "Filtra per data",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
"RAM Utilization": "Utilizzo RAM",
"Random": "Casuale",
"Receive Only": "Receive Only",
"Recent Changes": "Cambiamenti Recenti",
"Reduced by ignore patterns": "Ridotto da schemi di esclusione",
"Release Notes": "Note di Rilascio",
@@ -233,6 +235,7 @@
"Resume": "Riprendi",
"Resume All": "Riprendi Tutti",
"Reused": "Riutilizzato",
"Revert Local Changes": "Revert Local Changes",
"Running": "In esecuzione",
"Save": "Salva",
"Scan Time Remaining": "Tempo di Scansione Rimanente",

View File

@@ -109,6 +109,7 @@
"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.": "Syncthingによって置き換えられたり削除されたファイルは、タイムスタンプ付きのファイル名で .stversions フォルダーに移動します。",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "ファイルを他のデバイスによる変更から保護します。一方、このデバイスでの変更は他のデバイスに送信されます。",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filesystem Notifications",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filter by date",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "サポートされているパターンのクイックガイド",
"RAM Utilization": "メモリ使用量",
"Random": "ランダム",
"Receive Only": "Receive Only",
"Recent Changes": "Recent Changes",
"Reduced by ignore patterns": "無視パターン該当分を除く",
"Release Notes": "リリースノート",
@@ -233,6 +235,7 @@
"Resume": "再開",
"Resume All": "すべて再開",
"Reused": "中断後再利用",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "保存",
"Scan Time Remaining": "スキャン残り時間",

View File

@@ -109,6 +109,7 @@
"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.": "파일이 Syncthing에 의해서 교체되거나 삭제되면 .stversions 폴더에 있는 날짜가 바뀐 버전으로 이동됩니다.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "다른 장치가 파일을 편집할 수 없으며 반드시 이 장치의 내용을 기준으로 동기화합니다.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "파일시스템 알림",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "날짜별 정렬",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "지원하는 패턴에 대한 빠른 도움말",
"RAM Utilization": "RAM 사용량",
"Random": "무작위",
"Receive Only": "Receive Only",
"Recent Changes": "최근 변경",
"Reduced by ignore patterns": "무시 패턴으로 축소",
"Release Notes": "릴리즈 노트",
@@ -233,6 +235,7 @@
"Resume": "재개",
"Resume All": "모두 재개",
"Reused": "재개",
"Revert Local Changes": "Revert Local Changes",
"Running": "작동중",
"Save": "저장",
"Scan Time Remaining": "탐색 남은 시간",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Programai Syncthing pakeičiant ar ištrinant failus, jie yra perkeliami į datomis pažymėtas versijas, kataloge .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Programai Syncthing pakeičiant ar ištrinant failus, jie yra perkeliami į datomis pažymėtas versijas, aplanke .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Failai yra apsaugoti nuo kituose įrenginiuose atliktų pakeitimų, bet pakeitimai šiame įrenginyje bus nusiųsti kitiems įrenginiams.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Failai yra sinchronizuojami iš klasterio, tačiau bet kokie vietiniu mastu atlikti pakeitimai nebus išsiųsti į kitus įrenginius.",
"Filesystem Notifications": "Failų sistemos pranešimai",
"Filesystem Watcher Errors": "Failų sistemos prižiūrėtojo klaidos",
"Filter by date": "Filtruoti pagal datą",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
"RAM Utilization": "Atminties naudojimas",
"Random": "Atsitiktinė",
"Receive Only": "Tik gauti",
"Recent Changes": "Paskiausi keitimai",
"Reduced by ignore patterns": "Sumažinta pagal nepaisomus šablonus",
"Release Notes": "Laidos Informacija",
@@ -233,6 +235,7 @@
"Resume": "Pratęsti",
"Resume All": "Pratęsti visus",
"Reused": "Pakartotinas",
"Revert Local Changes": "Sugrąžinti vietinius pakeitimus",
"Running": "Vykdoma",
"Save": "Išsaugoti",
"Scan Time Remaining": "Likęs nuskaitymo laikas",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttes til en datostemplet versjon i .stversions-mappa når den oppdateres eller slettes av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttes til en datostemplet versjon i .stversions-mappa når den oppdateres eller slettes 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 er beskyttet mot endringer som er gjort på andre enheter, men endringer som er gjort på denne enheten blir sendt til resten av gruppen.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Filsystemvarsler ",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filtrer etter dato",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønstre",
"RAM Utilization": "RAM-utnyttelse",
"Random": "Tilfeldig",
"Receive Only": "Receive Only",
"Recent Changes": "Nylige endringer",
"Reduced by ignore patterns": "Reduser med utelatelsesmønster",
"Release Notes": "Utgivelsesnotat",
@@ -233,6 +235,7 @@
"Resume": "Gjenoppta",
"Resume All": "Gjenoppta alt",
"Reused": "Gjenbrukt",
"Revert Local Changes": "Revert Local Changes",
"Running": "Kjører",
"Save": "Lagre",
"Scan Time Remaining": "Gjenstående tid for gjennomsøking",

View File

@@ -12,7 +12,7 @@
"Add Remote Device": "Extern apparaat toevoegen",
"Add devices from the introducer to our device list, for mutually shared folders.": "Apparaten van het introductie-apparaat aan onze lijst met apparaten toevoegen, voor gemeenschappelijk gedeelde mappen.",
"Add new folder?": "Nieuwe map toevoegen?",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Het interval van volledige scan zal daarbij ook vermeerderd worden (maal 60, dit is een nieuwe standaardwaarde van 1 uur). U kunt het later voor elke map manueel configureren nadat u nee kiest.",
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Het interval van volledige scan zal daarbij ook vergroot worden (maal 60, dit is een nieuwe standaardwaarde van 1 uur). U kunt het later voor elke map manueel configureren nadat u nee kiest.",
"Address": "Adres",
"Addresses": "Adressen",
"Advanced": "Geavanceerd",
@@ -50,7 +50,7 @@
"Connection Error": "Verbindingsfout",
"Connection Type": "Soort verbinding",
"Connections": "Verbindingen",
"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.": "Continu opvolgen van wijzigingen is nu beschikbaar in Syncthing. Dit zal wijzigingen op een schijf detecteren en een scan uitvoeren op alleen de gewijzigde paden. De voordelen zijn dat wijzigingen sneller doorgevoerd worden en dat minder volledige scans nodig zijn.",
"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.": "Continu opvolgen van wijzigingen is nu beschikbaar in Syncthing. Dit zal wijzigingen op een schijf detecteren en alleen een scan uitvoeren op de gewijzigde paden. De voordelen zijn dat wijzigingen sneller doorgevoerd worden en dat minder volledige scans nodig zijn.",
"Copied from elsewhere": "Gekopieerd van ergens anders",
"Copied from original": "Gekopieerd van het origineel",
"Copyright © 2014-2016 the following Contributors:": "Auteursrecht © 2014-2016 voor de volgende bijdragers:",
@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Bestanden worden verplaatst naar versies met tijdsaanduiding in een .stversions-map wanneer ze vervangen of verwijderd zijn door Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Bestanden worden verplaatst naar versies met tijdsaanduiding in een .stversions-map wanneer ze vervangen of verwijderd zijn door 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.": "Bestanden zijn beschermd tegen wijzigingen die op andere apparaten gemaakt zijn, maar wijzigingen die op dit apparaat gemaakt zijn, worden naar de rest van de cluster verzonden.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Bestanden worden gesynchroniseerd vanuit de cluster, maar lokaal aangebrachte wijzigingen zullen niet naar andere apparaten verzonden worden.",
"Filesystem Notifications": "Meldingen van bestandssysteem",
"Filesystem Watcher Errors": "Fouten van bestandssysteem-opvolger",
"Filter by date": "Filteren op datum",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Snelgids voor ondersteunde patronen",
"RAM Utilization": "RAM-gebruik",
"Random": "Willekeurig",
"Receive Only": "Alleen ontvangen",
"Recent Changes": "Recente wijzigingen",
"Reduced by ignore patterns": "Verminderd door negeerpatronen",
"Release Notes": "Release-opmerkingen",
@@ -233,6 +235,7 @@
"Resume": "Hervatten",
"Resume All": "Alles hervatten",
"Reused": "Opnieuw gebruikt",
"Revert Local Changes": "Lokale wijzigingen terugdraaien",
"Running": "Draaiend",
"Save": "Opslaan",
"Scan Time Remaining": "Resterende scantijd",
@@ -248,7 +251,7 @@
"Send Only": "Alleen verzenden",
"Settings": "Instellingen",
"Share": "Delen",
"Share Folder": "Map dele",
"Share Folder": "Map delen",
"Share Folders With Device": "Mappen delen met apparaat",
"Share With Devices": "Delen met apparaten",
"Share this folder?": "Deze map delen?",
@@ -321,7 +324,7 @@
"Unknown": "Onbekend",
"Unshared": "Niet gedeeld",
"Unused": "Niet gebruikt",
"Up to Date": "Up-to-date",
"Up to Date": "Bijgewerkt",
"Updated": "Bijgewerkt",
"Upgrade": "Upgraden",
"Upgrade To {%version%}": "Upgraden naar {{version}}",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Pliki są datowane i przenoszone do folderu .stversions przez Syncthing, kiedy zostaną zmienione lub usunięte.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Pliki przenoszone są do wersji oznaczonych datą w folderze .stversions kiedy są zastępowane bądź usuwane przez 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.": "Pliki są zabezpieczone przed zmianami na innym urządzeniu, jednak zmiany w tym urządzeniu będą wysłane do reszty.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Powiadomienia systemowe",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filtruj według daty",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"RAM Utilization": "Użycie pamięci RAM",
"Random": "Losowo",
"Receive Only": "Receive Only",
"Recent Changes": "Ostatnie zmiany",
"Reduced by ignore patterns": "Ograniczono przez wzorce ignorowania",
"Release Notes": "Informacje o wydaniu",
@@ -233,6 +235,7 @@
"Resume": "Wznów",
"Resume All": "Wznów wszystkie",
"Reused": "Ponownie użyte",
"Revert Local Changes": "Revert Local Changes",
"Running": "Działa",
"Save": "Zapisz",
"Scan Time Remaining": "Pozostały czas skanowania",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas e movidos para o diretório .stversions quando substituídos ou apagados pelo Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando substituídos ou removidos pelo Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto dos dispositivos.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Notificação do Sistema de Arquivos",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filtrar por data",
@@ -201,7 +202,7 @@
"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:",
"Permissions": "Permissions",
"Permissions": "Permissões",
"Please consult the release notes before performing a major upgrade.": "Por favor, consulte as notas de lançamento antes de atualizar para uma versão \"major\".",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, defina um nome de usuário e senha para acesso à interface web, nas configurações.",
"Please wait": "Aguarde",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatória",
"Receive Only": "Receive Only",
"Recent Changes": "Mudanças recentes",
"Reduced by ignore patterns": "Reduzido por filtros",
"Release Notes": "Notas de lançamento",
@@ -233,6 +235,7 @@
"Resume": "Resumir",
"Resume All": "Resumir Todas",
"Reused": "Reutilizado",
"Revert Local Changes": "Revert Local Changes",
"Running": "Rodando",
"Save": "Salvar",
"Scan Time Remaining": "Tempo de verificação restante",
@@ -253,7 +256,7 @@
"Share With Devices": "Compartilhar com os dispositivos",
"Share this folder?": "Compartilhar esta pasta?",
"Shared With": "Compartilhada com",
"Sharing": "Sharing",
"Sharing": "Compartilhamento",
"Show ID": "Mostrar ID",
"Show QR": "Mostrar QR Code",
"Show diff with previous version": "Mostrar diferenças da versão anterior",
@@ -339,8 +342,8 @@
"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.",
"Watch for Changes": "Watch for Changes",
"Watching for Changes": "Watching for Changes",
"Watch for Changes": "Observar alterações",
"Watching for Changes": "Observando alterações",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando estiver adicionando um dispositivo, lembre-se de que este dispositivo deve ser adicionado do outro lado também.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e deve ser o mesmo em todos os dispositivos.",
"Yes": "Sim",

View File

@@ -77,7 +77,7 @@
"Discovery": "Pesquisa",
"Discovery Failures": "Falhas da pesquisa",
"Do not restore": "Não restaurar",
"Do not restore all": "Não restaurar todos",
"Do not restore all": "Não restaurar nenhum",
"Do you want to enable watching for changes for all your folders?": "Quer activar a vigilância de alterações para todas as suas pastas?",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
@@ -109,8 +109,9 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Os ficheiros são movidos para versões marcadas com data e hora numa pasta .stversions, ao serem substituídos ou eliminados pelo Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os ficheiros são movidos para versões carimbadas com o tempo numa pasta .stversions, ao serem substituídos ou apagados pelo Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os ficheiros estão protegidos contra alterações feitas noutros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Os ficheiros são sincronizados a partir do agrupamento, mas as alterações feitas localmente não serão enviadas para outros dispositivos.",
"Filesystem Notifications": "Notificações do sistema de ficheiros",
"Filesystem Watcher Errors": "Erros da vigilância do sistema de ficheiros",
"Filesystem Watcher Errors": "Erros na vigilância do sistema de ficheiros",
"Filter by date": "Filtrar por data",
"Filter by name": "Filtrar por nome",
"Folder": "Pasta",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Utilização da RAM",
"Random": "Aleatória",
"Receive Only": "Recebe apenas",
"Recent Changes": "Alterações recentes",
"Reduced by ignore patterns": "Reduzido por padrões de exclusão",
"Release Notes": "Notas de lançamento",
@@ -233,15 +235,16 @@
"Resume": "Retomar",
"Resume All": "Retomar todas",
"Reused": "Reutilizado",
"Revert Local Changes": "Reverter alterações locais",
"Running": "Em execução",
"Save": "Gravar",
"Scan Time Remaining": "Tempo restante da verificação",
"Scanning": "Verificação de alterações",
"Scanning": "Verificando",
"See external versioner help for supported templated command line parameters.": "Veja a ajuda do gestor de versões externo para saber que parâmetros da linha de comandos são suportados.",
"See external versioning help for supported templated command line parameters.": "Veja a ajuda externa sobre gestão de versões para ver os modelos suportados de parâmetros para a linha de comandos.",
"Select a version": "Seleccione uma versão",
"Select latest version": "Seleccione a última versão",
"Select oldest version": "Seleccione a versão mais antiga",
"Select latest version": "Seleccionar a última versão",
"Select oldest version": "Seleccionar a versão mais antiga",
"Select the devices to share this folder with.": "Seleccione os dispositivos com os quais vai partilhar esta pasta.",
"Select the folders to share with this device.": "Seleccione as pastas a partilhar com este dispositivo.",
"Send & Receive": "Envia e recebe",

View File

@@ -65,7 +65,7 @@
"Device ID": "ID устройства",
"Device Identification": "Идентификация устройства",
"Device Name": "Имя устройства",
"Device rate limits": "Device rate limits",
"Device rate limits": "Ограничения скорости для устройства",
"Device that last modified the item": "Устройство, последним изменившее объект",
"Devices": "Устройства",
"Disabled": "Отключено",
@@ -109,6 +109,7 @@
"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.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Уведомления файловой системы",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Отфильтровать по дате",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
"RAM Utilization": "Использование памяти",
"Random": "Случайно",
"Receive Only": "Receive Only",
"Recent Changes": "Последние изменения",
"Reduced by ignore patterns": "Уменьшено шаблонами игнорирования",
"Release Notes": "Примечания к выпуску",
@@ -233,6 +235,7 @@
"Resume": "Возобновить",
"Resume All": "Возобновить все",
"Reused": "Повторно использовано",
"Revert Local Changes": "Revert Local Changes",
"Running": "Запущено",
"Save": "Сохранить",
"Scan Time Remaining": "Оставшееся время сканирования",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Súbory premiestnené alebo zmazané aplikáciou Sycthing sú presunuté do verzií označených dátumov v adresári .stversions.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Súbory premiestnené alebo zmazané aplikáciou Syncthing sú premenované na verziu s dátumom a presunuté do adresára .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Soubory sú chránené pred zmenami na ostatních zariadeniach, ale zmeny provedené z tohto zariadenia budú rozoslané na zvyšok klastra.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Oznamy súborového systému",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Filtrovanie podľa dátumu",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Rýchly sprievodca podporovanými vzormi",
"RAM Utilization": "Využitie RAM",
"Random": "Náhodne",
"Receive Only": "Receive Only",
"Recent Changes": "Nedávne zmeny",
"Reduced by ignore patterns": "Znížené o ignorované vzory",
"Release Notes": "Poznámky k vydaniu",
@@ -233,6 +235,7 @@
"Resume": "Pokračovať",
"Resume All": "Pokračuj so všetkými",
"Reused": "Opakovane použité",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Uložiť",
"Scan Time Remaining": "Zostávajúci čas skenovania",

View File

@@ -109,6 +109,7 @@
"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.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "filsystemsnotifieringar",
"Filesystem Watcher Errors": "Filsystem bevakarfel",
"Filter by date": "Filtrera efter datum",
@@ -159,7 +160,7 @@
"Local State (Total)": "Lokalt tillstånd (totalt)",
"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.": "Log tailing paused. Scroll to bottom continue.",
"Log tailing paused. Scroll to bottom continue.": "Loggning pausad. Rulla till botten för att fortsätta.",
"Logs": "Loggar",
"Major Upgrade": "Större uppgradering",
"Mass actions": "Massåtgärder",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Snabb handledning till mönster som stöds",
"RAM Utilization": "RAM användning",
"Random": "Slumpmässig",
"Receive Only": "Receive Only",
"Recent Changes": "Senaste ändringar",
"Reduced by ignore patterns": "Minskas med ignorera mönster",
"Release Notes": "Versionsanteckningar",
@@ -233,6 +235,7 @@
"Resume": "Återuppta",
"Resume All": "Återuppta alla",
"Reused": "Återanvänds",
"Revert Local Changes": "Revert Local Changes",
"Running": "Körs",
"Save": "Spara",
"Scan Time Remaining": "Återstående skanningstid",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Файли будуть поміщатися у директорію .stversions із відповідною позначкою часу, коли вони будуть замінятися або видалятися програмою.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Файли будуть поміщатися у директорію .stversions із відповідною позначкою часу, коли вони будуть замінятися або видалятися програмою.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Вміст папки захищено від змін, зроблених на інших пристроях, але зміни зроблені на цьому пристрої можна розіслати решті пристроїв кластеру.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "Повідомлення файлової системи",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "Фільтрувати по даті",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
"RAM Utilization": "Використання RAM",
"Random": "Випадково",
"Receive Only": "Receive Only",
"Recent Changes": "Останні зміни",
"Reduced by ignore patterns": "Зменшено шаблонами ігнорування",
"Release Notes": "Примітки до випуску",
@@ -233,6 +235,7 @@
"Resume": "Продовжити",
"Resume All": "Продовжити всі",
"Reused": "Використано вдруге",
"Revert Local Changes": "Revert Local Changes",
"Running": "Running",
"Save": "Зберегти",
"Scan Time Remaining": "Час до кінця сканування",

View File

@@ -109,6 +109,7 @@
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "当某个文件在其他设备被替换或删除时,本设备将在 .stversions 目录中保留该文件的备份,并在文件名中加入时间戳信息。",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "当某个文件在其他设备被替换或删除时,本设备将会在 .stversions 文件夹中保留该文件的备份,并在文件名中加入时间戳信息。",
"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.": "文件将从集群同步,但本地所作的任何更改都不会被发送到其他设备。",
"Filesystem Notifications": "文件系统通知",
"Filesystem Watcher Errors": "文件系统监视器错误",
"Filter by date": "按日期筛选",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "支持的通配符的简单教程:",
"RAM Utilization": "内存使用量",
"Random": "随机顺序",
"Receive Only": "仅接收",
"Recent Changes": "最近更改",
"Reduced by ignore patterns": "已由忽略模式缩减",
"Release Notes": "发布说明",
@@ -233,6 +235,7 @@
"Resume": "恢复",
"Resume All": "全部恢复",
"Reused": "复用",
"Revert Local Changes": "恢复本地更改",
"Running": "运行中",
"Save": "保存",
"Scan Time Remaining": "扫描剩余时间",

View File

@@ -109,6 +109,7 @@
"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.": "當檔案被 Syncthing 取代或刪除時,它們將被移至 .stversions 資料夾並添加日期戳記。",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "其他裝置做的改變不會影響此裝置上的檔案,但在此裝置上的變動將被發送到叢集的其他部分。",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.",
"Filesystem Notifications": "檔案系統通知",
"Filesystem Watcher Errors": "Filesystem Watcher Errors",
"Filter by date": "以日期篩選",
@@ -212,6 +213,7 @@
"Quick guide to supported patterns": "可支援樣式的快速指南",
"RAM Utilization": "記憶體使用量",
"Random": "隨機",
"Receive Only": "Receive Only",
"Recent Changes": "最近變動",
"Reduced by ignore patterns": "已由忽略樣式縮減",
"Release Notes": "版本資訊",
@@ -233,6 +235,7 @@
"Resume": "繼續",
"Resume All": "全部繼續",
"Reused": "重用",
"Revert Local Changes": "Revert Local Changes",
"Running": "正在執行",
"Save": "儲存",
"Scan Time Remaining": "剩餘掃描時間",

View File

@@ -301,7 +301,9 @@
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id) | percent}}"></div>
<h4 class="panel-title">
<div class="panel-icon hidden-xs">
<span ng-class="[folder.type == 'sendonly' ? 'fas fa-fw fa-lock' : 'fas fa-fw fa-folder']"></span>
<span ng-if="folder.type == 'sendreceive'" class="fas fa-fw fa-folder"></span>
<span ng-if="folder.type == 'sendonly'" class="fas fa-fw fa-upload"></span>
<span ng-if="folder.type == 'receiveonly'" class="fas fa-fw fa-download"></span>
</div>
<div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="paused"><span class="hidden-xs" translate>Paused</span><span class="visible-xs">&#9724;</span></span>
@@ -386,9 +388,10 @@
</td>
</tr>
<tr ng-if="folder.type != 'sendreceive'">
<th><span class="fas fa-fw fa-lock"></span>&nbsp;<span translate>Folder Type</span></th>
<th><span class="fas fa-fw fa-folder"></span>&nbsp;<span translate>Folder Type</span></th>
<td class="text-right">
<span ng-if="folder.type == 'sendonly'" translate>Send Only</span>
<span ng-if="folder.type == 'receiveonly'" translate>Receive Only</span>
</td>
</tr>
<tr ng-if="folder.ignorePerms">
@@ -478,6 +481,9 @@
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="override(folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.type == 'sendonly'">
<span class="fas fa-arrow-circle-up"></span>&nbsp;<span translate>Override Changes</span>
</button>
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revert(folder.id)" ng-if="canRevert(folder.id)">
<span class="fa fa-arrow-circle-down"></span>&nbsp;<span translate>Revert Local Changes</span>
</button>
<span class="pull-right">
<button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
<span class="fas fa-pause"></span>&nbsp;<span translate>Pause</span>

View File

@@ -12,7 +12,7 @@
<p translate>Copyright &copy; 2014-2017 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, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, Aaron Bieber, Adam Piggott, Adel Qalieh, Alessandro G., Andrew Dunham, Andrew Rabert, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Graham Miln, Harrison Jones, Heiko Zuerker, Ian Johnson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Vromans, John Rinehart, Jose Manuel Delicado, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Kurt Fitzner, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nicholas Rishel, Niels Peter Roest, Nils Jakobi, Pascal Jungblut, Pawel Palenica, Peter Hoeg, Peter Marquardt, Phill Luby, Piotr Bejda, Pramodh KP, Robert Carosi, Roman Zaynetdinov, Ross Smith II, Sacheendra Talluri, Scott Klupfel, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tomas Cerveny, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Xavier O., Yannic A., xjtdy888
Jakob Borg, Audrius Butkevicius, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Wulf Weich, Aaron Bieber, Adam Piggott, Adel Qalieh, Alessandro G., Andrew Dunham, Andrew Rabert, Andrey D, Antoine Lamielle, Aranjedeath, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Chris Tonkinson, Colin Kennedy, Dale Visser, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, 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, Iain Barnett, Ian Johnson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonathan Cross, Jose Manuel Delicado, 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, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mark Pulford, Mateusz Naściszewski, Matic Potočnik, Matt Burke, Matteo Ruina, Max Schulze, MaximAL, Maxime Thirouin, Michael Jephcote, Michael Tilli, Mike Boone, MikeLund, Nicholas Rishel, Nicolas Braud-Santoni, Niels Peter Roest, Nils Jakobi, NoLooseEnds, Oyebanji Jacob Mayowa, Pascal Jungblut, Pawel Palenica, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Richard Hartmann, Robert Carosi, Roman Zaynetdinov, Ross Smith II, Sacheendra Talluri, Scott Klupfel, Sly_tom_cat, Stefan Kuntz, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Nygren, Tobias Tom, Tomas Cerveny, Tommy Thorn, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, chucic, derekriemer, janost, jaseg, klemens, marco-m, perewa, rubenbe, wangguoliang, xjtdy888, 佛跳墙
</div>
</div>
<hr/>

View File

@@ -1099,10 +1099,10 @@ angular.module('syncthing.core')
$scope.logging.timer = $timeout($scope.logging.fetch);
var textArea = $('#logViewerText');
textArea.on("scroll", $scope.logging.onScroll);
$('#logViewer').modal().on('shown.bs.modal', function() {
$('#logViewer').modal().one('shown.bs.modal', function() {
// Scroll to bottom.
textArea.scrollTop(textArea[0].scrollHeight);
}).on('hidden.bs.modal', function () {
}).one('hidden.bs.modal', function () {
$timeout.cancel($scope.logging.timer);
textArea.off("scroll", $scope.logging.onScroll);
$scope.logging.timer = null;
@@ -1178,7 +1178,7 @@ angular.module('syncthing.core')
$scope.tmpOptions.upgrades = "candidate";
}
$scope.tmpGUI = angular.copy($scope.config.gui);
$('#settings').modal().on('hidden.bs.modal', function () {
$('#settings').modal().one('hidden.bs.modal', function () {
window.location.hash = "";
});
};
@@ -1560,15 +1560,12 @@ angular.module('syncthing.core')
$scope.editFolderModal = function () {
$scope.folderPathErrors = {};
$scope.folderEditor.$setPristine();
$('#editFolder').modal().on({
'shown.bs.tab': function (e) {
if (e.target.attributes.href.value === "#folder-ignores") {
$('#folder-ignores textarea').focus();
}
},
'hidden.bs.modal': function () {
window.location.hash = "";
$('#editFolder').modal().one('shown.bs.tab', function (e) {
if (e.target.attributes.href.value === "#folder-ignores") {
$('#folder-ignores textarea').focus();
}
}).one('hidden.bs.modal', function () {
window.location.hash = "";
});
};
@@ -1884,10 +1881,10 @@ angular.module('syncthing.core')
var closed = false;
var modalShown = $q.defer();
$('#restoreVersions').modal().on('hidden.bs.modal', function () {
$('#restoreVersions').modal().one('hidden.bs.modal', function () {
closed = true;
resetRestoreVersions();
}).on('shown.bs.modal', function() {
}).one('shown.bs.modal', function() {
modalShown.resolve();
});
@@ -2060,7 +2057,7 @@ angular.module('syncthing.core')
$scope.showNeed = function (folder) {
$scope.neededFolder = folder;
refreshNeed(folder);
$('#needed').modal().on('hidden.bs.modal', function () {
$('#needed').modal().one('hidden.bs.modal', function () {
$scope.neededFolder = undefined;
$scope.needed = undefined;
$scope.neededCurrentPage = 1;
@@ -2078,7 +2075,7 @@ angular.module('syncthing.core')
$scope.remoteNeedFolders.push(folder);
$scope.refreshRemoteNeed(folder, 1, 10);
});
$('#remoteNeed').modal().on('hidden.bs.modal', function () {
$('#remoteNeed').modal().one('hidden.bs.modal', function () {
resetRemoteNeed();
});
};
@@ -2086,7 +2083,7 @@ angular.module('syncthing.core')
$scope.showFailed = function (folder) {
$scope.failed.folder = folder;
$scope.failed = $scope.refreshFailed(1, 10);
$('#failed').modal().on('hidden.bs.modal', function () {
$('#failed').modal().one('hidden.bs.modal', function () {
$scope.failed = {};
});
};
@@ -2102,6 +2099,22 @@ angular.module('syncthing.core')
$http.post(urlbase + "/db/override?folder=" + encodeURIComponent(folder));
};
$scope.revert = function (folder) {
$http.post(urlbase + "/db/revert?folder=" + encodeURIComponent(folder));
};
$scope.canRevert = function (folder) {
var f = $scope.model[folder];
if (!f) {
return false;
}
return f.receiveOnlyChangedBytes > 0 ||
f.receiveOnlyChangedDeletes > 0 ||
f.receiveOnlyChangedDirectories > 0 ||
f.receiveOnlyChangedFiles > 0 ||
f.receiveOnlyChangedSymlinks > 0;
};
$scope.advanced = function () {
$scope.advancedConfig = angular.copy($scope.config);
$('#advanced').modal('show');

View File

@@ -162,8 +162,10 @@
<select class="form-control" ng-model="currentFolder.type">
<option value="sendreceive" translate>Send &amp; Receive</option>
<option value="sendonly" translate>Send Only</option>
<option value="receiveonly" translate>Receive Only</option>
</select>
<p ng-if="currentFolder.type == 'sendonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
<p ng-if="currentFolder.type == 'receiveonly'" translate class="help-block">Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.</p>
</div>
<div class="col-md-6 form-group">
<label translate>File Pull Order</label>

View File

@@ -154,6 +154,7 @@ func (cfg Configuration) Copy() Configuration {
}
newCfg.Options = cfg.Options.Copy()
newCfg.GUI = cfg.GUI.Copy()
// DeviceIDs are values
newCfg.IgnoredDevices = make([]protocol.DeviceID, len(cfg.IgnoredDevices))

View File

@@ -11,6 +11,7 @@ type FolderType int
const (
FolderTypeSendReceive FolderType = iota // default is sendreceive
FolderTypeSendOnly
FolderTypeReceiveOnly
)
func (t FolderType) String() string {
@@ -19,6 +20,8 @@ func (t FolderType) String() string {
return "sendreceive"
case FolderTypeSendOnly:
return "sendonly"
case FolderTypeReceiveOnly:
return "receiveonly"
default:
return "unknown"
}
@@ -34,6 +37,8 @@ func (t *FolderType) UnmarshalText(bs []byte) error {
*t = FolderTypeSendReceive
case "readonly", "sendonly":
*t = FolderTypeSendOnly
case "receiveonly":
*t = FolderTypeReceiveOnly
default:
*t = FolderTypeSendReceive
}

View File

@@ -93,3 +93,7 @@ func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
return false
}
}
func (c GUIConfiguration) Copy() GUIConfiguration {
return c
}

View File

@@ -142,7 +142,7 @@ func (w *Wrapper) RawCopy() Configuration {
func (w *Wrapper) Replace(cfg Configuration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
return w.replaceLocked(cfg)
return w.replaceLocked(cfg.Copy())
}
func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
@@ -154,7 +154,7 @@ func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
for _, sub := range w.subs {
l.Debugln(sub, "verifying configuration")
if err := sub.VerifyConfiguration(from, to); err != nil {
if err := sub.VerifyConfiguration(from.Copy(), to.Copy()); err != nil {
l.Debugln(sub, "rejected config:", err)
return noopWaiter{}, err
}
@@ -164,7 +164,7 @@ func (w *Wrapper) replaceLocked(to Configuration) (Waiter, error) {
w.deviceMap = nil
w.folderMap = nil
return w.notifyListeners(from, to), nil
return w.notifyListeners(from.Copy(), to.Copy()), nil
}
func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
@@ -172,7 +172,7 @@ func (w *Wrapper) notifyListeners(from, to Configuration) Waiter {
wg.Add(len(w.subs))
for _, sub := range w.subs {
go func(commiter Committer) {
w.notifyListener(commiter, from.Copy(), to.Copy())
w.notifyListener(commiter, from, to)
wg.Done()
}(sub)
}
@@ -187,15 +187,14 @@ func (w *Wrapper) notifyListener(sub Committer, from, to Configuration) {
}
}
// Devices returns a map of devices. Device structures should not be changed,
// other than for the purpose of updating via SetDevice().
// Devices returns a map of devices.
func (w *Wrapper) Devices() map[protocol.DeviceID]DeviceConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
if w.deviceMap == nil {
w.deviceMap = make(map[protocol.DeviceID]DeviceConfiguration, len(w.cfg.Devices))
for _, dev := range w.cfg.Devices {
w.deviceMap[dev.DeviceID] = dev
w.deviceMap[dev.DeviceID] = dev.Copy()
}
}
return w.deviceMap
@@ -213,13 +212,13 @@ func (w *Wrapper) SetDevices(devs []DeviceConfiguration) (Waiter, error) {
replaced = false
for newIndex := range newCfg.Devices {
if newCfg.Devices[newIndex].DeviceID == devs[oldIndex].DeviceID {
newCfg.Devices[newIndex] = devs[oldIndex]
newCfg.Devices[newIndex] = devs[oldIndex].Copy()
replaced = true
break
}
}
if !replaced {
newCfg.Devices = append(newCfg.Devices, devs[oldIndex])
newCfg.Devices = append(newCfg.Devices, devs[oldIndex].Copy())
}
}
@@ -238,19 +237,14 @@ func (w *Wrapper) RemoveDevice(id protocol.DeviceID) (Waiter, error) {
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
removed := false
for i := range newCfg.Devices {
if newCfg.Devices[i].DeviceID == id {
newCfg.Devices = append(newCfg.Devices[:i], newCfg.Devices[i+1:]...)
removed = true
break
return w.replaceLocked(newCfg)
}
}
if !removed {
return noopWaiter{}, nil
}
return w.replaceLocked(newCfg)
return noopWaiter{}, nil
}
// Folders returns a map of folders. Folder structures should not be changed,
@@ -261,7 +255,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
if w.folderMap == nil {
w.folderMap = make(map[string]FolderConfiguration, len(w.cfg.Folders))
for _, fld := range w.cfg.Folders {
w.folderMap[fld.ID] = fld
w.folderMap[fld.ID] = fld.Copy()
}
}
return w.folderMap
@@ -271,7 +265,7 @@ func (w *Wrapper) Folders() map[string]FolderConfiguration {
func (w *Wrapper) FolderList() []FolderConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return append([]FolderConfiguration(nil), w.cfg.Folders...)
return w.cfg.Copy().Folders
}
// SetFolder adds a new folder to the configuration, or overwrites an existing
@@ -281,17 +275,15 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
replaced := false
for i := range newCfg.Folders {
if newCfg.Folders[i].ID == fld.ID {
newCfg.Folders[i] = fld
replaced = true
break
return w.replaceLocked(newCfg)
}
}
if !replaced {
newCfg.Folders = append(w.cfg.Folders, fld)
}
newCfg.Folders = append(newCfg.Folders, fld)
return w.replaceLocked(newCfg)
}
@@ -300,7 +292,7 @@ func (w *Wrapper) SetFolder(fld FolderConfiguration) (Waiter, error) {
func (w *Wrapper) Options() OptionsConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.Options
return w.cfg.Options.Copy()
}
// SetOptions replaces the current options configuration object.
@@ -308,7 +300,7 @@ func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
newCfg.Options = opts
newCfg.Options = opts.Copy()
return w.replaceLocked(newCfg)
}
@@ -316,7 +308,7 @@ func (w *Wrapper) SetOptions(opts OptionsConfiguration) (Waiter, error) {
func (w *Wrapper) GUI() GUIConfiguration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.GUI
return w.cfg.GUI.Copy()
}
// SetGUI replaces the current GUI configuration object.
@@ -324,7 +316,7 @@ func (w *Wrapper) SetGUI(gui GUIConfiguration) (Waiter, error) {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
newCfg.GUI = gui
newCfg.GUI = gui.Copy()
return w.replaceLocked(newCfg)
}
@@ -360,7 +352,7 @@ func (w *Wrapper) Device(id protocol.DeviceID) (DeviceConfiguration, bool) {
defer w.mut.Unlock()
for _, device := range w.cfg.Devices {
if device.DeviceID == id {
return device, true
return device.Copy(), true
}
}
return DeviceConfiguration{}, false
@@ -372,7 +364,7 @@ func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
defer w.mut.Unlock()
for _, folder := range w.cfg.Folders {
if folder.ID == id {
return folder, true
return folder.Copy(), true
}
}
return FolderConfiguration{}, false
@@ -380,6 +372,9 @@ func (w *Wrapper) Folder(id string) (FolderConfiguration, bool) {
// Save writes the configuration to disk, and generates a ConfigSaved event.
func (w *Wrapper) Save() error {
w.mut.Lock()
defer w.mut.Unlock()
fd, err := osutil.CreateAtomic(w.path)
if err != nil {
l.Debugln("CreateAtomic:", err)
@@ -403,7 +398,7 @@ func (w *Wrapper) Save() error {
func (w *Wrapper) GlobalDiscoveryServers() []string {
var servers []string
for _, srv := range w.cfg.Options.GlobalAnnServers {
for _, srv := range w.Options().GlobalAnnServers {
switch srv {
case "default":
servers = append(servers, DefaultDiscoveryServers...)
@@ -420,7 +415,7 @@ func (w *Wrapper) GlobalDiscoveryServers() []string {
func (w *Wrapper) ListenAddresses() []string {
var addresses []string
for _, addr := range w.cfg.Options.ListenAddresses {
for _, addr := range w.Options().ListenAddresses {
switch addr {
case "default":
addresses = append(addresses, DefaultListenAddresses...)

View File

@@ -20,3 +20,7 @@ var (
func init() {
l.SetDebug("db", strings.Contains(os.Getenv("STTRACE"), "db") || os.Getenv("STTRACE") == "all")
}
func shouldDebug() bool {
return l.ShouldDebug("db")
}

View File

@@ -221,6 +221,14 @@ func (db *Instance) withHaveSequence(folder []byte, startSeq int64, fn Iterator)
l.Debugln("missing file for sequence number", db.sequenceKeySequence(dbi.Key()))
continue
}
if shouldDebug() {
key := dbi.Key()
seq := int64(binary.BigEndian.Uint64(key[keyPrefixLen+keyFolderLen:]))
if f.Sequence != seq {
panic(fmt.Sprintf("sequence index corruption, file sequence %d != expected %d", f.Sequence, seq))
}
}
if !fn(f) {
return
}
@@ -586,7 +594,7 @@ func (db *Instance) checkGlobals(folder []byte, meta *metadataTracker) {
if i == 0 {
if fi, ok := db.getFile(fk); ok {
meta.addFile(globalDeviceID, fi)
meta.addFile(protocol.GlobalDeviceID, fi)
}
}
}

View File

@@ -305,7 +305,7 @@ func TestUpdate0to3(t *testing.T) {
t.Error("Unexpected additional file via sequence", f.FileName())
return true
}
if e := haveUpdate0to3[protocol.LocalDeviceID][0]; f.IsEquivalent(e, true, true) {
if e := haveUpdate0to3[protocol.LocalDeviceID][0]; f.IsEquivalentOptional(e, true, true, 0) {
found = true
} else {
t.Errorf("Wrong file via sequence, got %v, expected %v", f, e)
@@ -330,7 +330,7 @@ func TestUpdate0to3(t *testing.T) {
}
f := fi.(protocol.FileInfo)
delete(need, f.Name)
if !f.IsEquivalent(e, true, true) {
if !f.IsEquivalentOptional(e, true, true, 0) {
t.Errorf("Wrong needed file, got %v, expected %v", f, e)
}
return true

View File

@@ -140,11 +140,11 @@ func (t readWriteTransaction) updateGlobal(gk, folder, device []byte, file proto
if oldFile, ok := t.getFile(folder, oldGlobalFV.Device, name); ok {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
meta.removeFile(globalDeviceID, oldFile)
meta.removeFile(protocol.GlobalDeviceID, oldFile)
}
// Add the new global to the global size counter
meta.addFile(globalDeviceID, newGlobal)
meta.addFile(protocol.GlobalDeviceID, newGlobal)
l.Debugf(`new global for "%v" after update: %v`, file.Name, fl)
t.Put(gk, mustMarshal(&fl))
@@ -197,7 +197,7 @@ func (t readWriteTransaction) removeFromGlobal(gk, folder, device, file []byte,
// didn't exist anyway, apparently
continue
}
meta.removeFile(globalDeviceID, f)
meta.removeFile(protocol.GlobalDeviceID, f)
removed = true
}
fl.Versions = append(fl.Versions[:i], fl.Versions[i+1:]...)
@@ -215,7 +215,7 @@ func (t readWriteTransaction) removeFromGlobal(gk, folder, device, file []byte,
if f, ok := t.getFile(folder, fl.Versions[0].Device, file); ok {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
meta.addFile(globalDeviceID, f)
meta.addFile(protocol.GlobalDeviceID, f)
}
}
}

View File

@@ -7,25 +7,30 @@
package db
import (
"bytes"
"math/bits"
"time"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
// like protocol.LocalDeviceID but with 0xf8 in all positions
var globalDeviceID = protocol.DeviceID{0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8}
// metadataTracker keeps metadata on a per device, per local flag basis.
type metadataTracker struct {
mut sync.RWMutex
counts CountsSet
indexes map[protocol.DeviceID]int // device ID -> index in counts
indexes map[metaKey]int // device ID + local flags -> index in counts
}
type metaKey struct {
dev protocol.DeviceID
flags uint32
}
func newMetadataTracker() *metadataTracker {
return &metadataTracker{
mut: sync.NewRWMutex(),
indexes: make(map[protocol.DeviceID]int),
indexes: make(map[metaKey]int),
}
}
@@ -38,7 +43,7 @@ func (m *metadataTracker) Unmarshal(bs []byte) error {
// Initialize the index map
for i, c := range m.counts.Counts {
m.indexes[protocol.DeviceIDFromBytes(c.DeviceID)] = i
m.indexes[metaKey{protocol.DeviceIDFromBytes(c.DeviceID), c.LocalFlags}] = i
}
return nil
}
@@ -72,14 +77,15 @@ func (m *metadataTracker) fromDB(db *Instance, folder []byte) error {
// countsPtr returns a pointer to the corresponding Counts struct, if
// necessary allocating one in the process
func (m *metadataTracker) countsPtr(dev protocol.DeviceID) *Counts {
func (m *metadataTracker) countsPtr(dev protocol.DeviceID, flags uint32) *Counts {
// must be called with the mutex held
idx, ok := m.indexes[dev]
key := metaKey{dev, flags}
idx, ok := m.indexes[key]
if !ok {
idx = len(m.counts.Counts)
m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:]})
m.indexes[dev] = idx
m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:], LocalFlags: flags})
m.indexes[key] = idx
}
return &m.counts.Counts[idx]
}
@@ -87,12 +93,28 @@ func (m *metadataTracker) countsPtr(dev protocol.DeviceID) *Counts {
// addFile adds a file to the counts, adjusting the sequence number as
// appropriate
func (m *metadataTracker) addFile(dev protocol.DeviceID, f FileIntf) {
if f.IsInvalid() {
if f.IsInvalid() && f.FileLocalFlags() == 0 {
// This is a remote invalid file; it does not count.
return
}
m.mut.Lock()
cp := m.countsPtr(dev)
if flags := f.FileLocalFlags(); flags == 0 {
// Account regular files in the zero-flags bucket.
m.addFileLocked(dev, 0, f)
} else {
// Account in flag specific buckets.
eachFlagBit(flags, func(flag uint32) {
m.addFileLocked(dev, flag, f)
})
}
m.mut.Unlock()
}
func (m *metadataTracker) addFileLocked(dev protocol.DeviceID, flags uint32, f FileIntf) {
cp := m.countsPtr(dev, flags)
switch {
case f.IsDeleted():
@@ -109,18 +131,32 @@ func (m *metadataTracker) addFile(dev protocol.DeviceID, f FileIntf) {
if seq := f.SequenceNo(); seq > cp.Sequence {
cp.Sequence = seq
}
m.mut.Unlock()
}
// removeFile removes a file from the counts
func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) {
if f.IsInvalid() {
if f.IsInvalid() && f.FileLocalFlags() == 0 {
// This is a remote invalid file; it does not count.
return
}
m.mut.Lock()
cp := m.countsPtr(dev)
if flags := f.FileLocalFlags(); flags == 0 {
// Remove regular files from the zero-flags bucket
m.removeFileLocked(dev, 0, f)
} else {
// Remove from flag specific buckets.
eachFlagBit(flags, func(flag uint32) {
m.removeFileLocked(dev, flag, f)
})
}
m.mut.Unlock()
}
func (m *metadataTracker) removeFileLocked(dev protocol.DeviceID, flags uint32, f FileIntf) {
cp := m.countsPtr(dev, f.FileLocalFlags())
switch {
case f.IsDeleted():
@@ -153,14 +189,19 @@ func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) {
cp.Symlinks = 0
m.counts.Created = 0
}
m.mut.Unlock()
}
// resetAll resets all metadata for the given device
func (m *metadataTracker) resetAll(dev protocol.DeviceID) {
m.mut.Lock()
*m.countsPtr(dev) = Counts{DeviceID: dev[:]}
for i, c := range m.counts.Counts {
if bytes.Equal(c.DeviceID, dev[:]) {
m.counts.Counts[i] = Counts{
DeviceID: c.DeviceID,
LocalFlags: c.LocalFlags,
}
}
}
m.mut.Unlock()
}
@@ -169,23 +210,30 @@ func (m *metadataTracker) resetAll(dev protocol.DeviceID) {
func (m *metadataTracker) resetCounts(dev protocol.DeviceID) {
m.mut.Lock()
c := m.countsPtr(dev)
c.Bytes = 0
c.Deleted = 0
c.Directories = 0
c.Files = 0
c.Symlinks = 0
// c.Sequence deliberately untouched
for i, c := range m.counts.Counts {
if bytes.Equal(c.DeviceID, dev[:]) {
m.counts.Counts[i] = Counts{
DeviceID: c.DeviceID,
Sequence: c.Sequence,
LocalFlags: c.LocalFlags,
}
}
}
m.mut.Unlock()
}
// Counts returns the counts for the given device ID
func (m *metadataTracker) Counts(dev protocol.DeviceID) Counts {
// Counts returns the counts for the given device ID and flag. `flag` should
// be zero or have exactly one bit set.
func (m *metadataTracker) Counts(dev protocol.DeviceID, flag uint32) Counts {
if bits.OnesCount32(flag) > 1 {
panic("incorrect usage: set at most one bit in flag")
}
m.mut.RLock()
defer m.mut.RUnlock()
idx, ok := m.indexes[dev]
idx, ok := m.indexes[metaKey{dev, flag}]
if !ok {
return Counts{}
}
@@ -198,7 +246,7 @@ func (m *metadataTracker) nextSeq(dev protocol.DeviceID) int64 {
m.mut.Lock()
defer m.mut.Unlock()
c := m.countsPtr(dev)
c := m.countsPtr(dev, 0)
c.Sequence++
return c.Sequence
}
@@ -206,21 +254,26 @@ func (m *metadataTracker) nextSeq(dev protocol.DeviceID) int64 {
// devices returns the list of devices tracked, excluding the local device
// (which we don't know the ID of)
func (m *metadataTracker) devices() []protocol.DeviceID {
devs := make([]protocol.DeviceID, 0, len(m.counts.Counts))
devs := make(map[protocol.DeviceID]struct{}, len(m.counts.Counts))
m.mut.RLock()
for _, dev := range m.counts.Counts {
if dev.Sequence > 0 {
id := protocol.DeviceIDFromBytes(dev.DeviceID)
if id == globalDeviceID || id == protocol.LocalDeviceID {
if id == protocol.GlobalDeviceID || id == protocol.LocalDeviceID {
continue
}
devs = append(devs, id)
devs[id] = struct{}{}
}
}
m.mut.RUnlock()
return devs
devList := make([]protocol.DeviceID, 0, len(devs))
for dev := range devs {
devList = append(devList, dev)
}
return devList
}
func (m *metadataTracker) Created() time.Time {
@@ -234,3 +287,19 @@ func (m *metadataTracker) SetCreated() {
m.counts.Created = time.Now().UnixNano()
m.mut.Unlock()
}
// eachFlagBit calls the function once for every bit that is set in flags
func eachFlagBit(flags uint32, fn func(flag uint32)) {
// Test each bit from the right, as long as there are bits left in the
// flag set. Clear any bits found and stop testing as soon as there are
// no more bits set.
currentBit := uint32(1 << 0)
for flags != 0 {
if flags&currentBit != 0 {
fn(currentBit)
flags &^= currentBit
}
currentBit <<= 1
}
}

82
lib/db/meta_test.go Normal file
View File

@@ -0,0 +1,82 @@
// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package db
import (
"math/bits"
"sort"
"testing"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestEachFlagBit(t *testing.T) {
cases := []struct {
flags uint32
iterations int
}{
{0, 0},
{1<<0 | 1<<3, 2},
{1 << 0, 1},
{1 << 31, 1},
{1<<10 | 1<<20 | 1<<30, 3},
}
for _, tc := range cases {
var flags uint32
iterations := 0
eachFlagBit(tc.flags, func(f uint32) {
iterations++
flags |= f
if bits.OnesCount32(f) != 1 {
t.Error("expected exactly one bit to be set in every call")
}
})
if flags != tc.flags {
t.Errorf("expected 0x%x flags, got 0x%x", tc.flags, flags)
}
if iterations != tc.iterations {
t.Errorf("expected %d iterations, got %d", tc.iterations, iterations)
}
}
}
func TestMetaDevices(t *testing.T) {
d1 := protocol.DeviceID{1}
d2 := protocol.DeviceID{2}
meta := newMetadataTracker()
meta.addFile(d1, protocol.FileInfo{Sequence: 1})
meta.addFile(d1, protocol.FileInfo{Sequence: 2, LocalFlags: 1})
meta.addFile(d2, protocol.FileInfo{Sequence: 1})
meta.addFile(d2, protocol.FileInfo{Sequence: 2, LocalFlags: 2})
meta.addFile(protocol.LocalDeviceID, protocol.FileInfo{Sequence: 1})
// There are five device/flags combos
if l := len(meta.counts.Counts); l < 5 {
t.Error("expected at least five buckets, not", l)
}
// There are only two non-local devices
devs := meta.devices()
if l := len(devs); l != 2 {
t.Fatal("expected two devices, not", l)
}
// Check that we got the two devices we expect
sort.Slice(devs, func(a, b int) bool {
return devs[a].Compare(devs[b]) == -1
})
if devs[0] != d1 {
t.Error("first device should be d1")
}
if devs[1] != d2 {
t.Error("second device should be d2")
}
}

View File

@@ -14,6 +14,7 @@ package db
import (
"os"
"sort"
"time"
"github.com/syncthing/syncthing/lib/fs"
@@ -37,8 +38,12 @@ type FileSet struct {
type FileIntf interface {
FileSize() int64
FileName() string
FileLocalFlags() uint32
IsDeleted() bool
IsInvalid() bool
IsIgnored() bool
IsUnsupported() bool
MustRescan() bool
IsDirectory() bool
IsSymlink() bool
HasPermissionBits() bool
@@ -135,38 +140,56 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
if device == protocol.LocalDeviceID {
discards := make([]protocol.FileInfo, 0, len(fs))
updates := make([]protocol.FileInfo, 0, len(fs))
// db.UpdateFiles will sort unchanged files out -> save one db lookup
// filter slice according to https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
oldFs := fs
fs = fs[:0]
var dk []byte
folder := []byte(s.folder)
for _, nf := range oldFs {
dk = s.db.deviceKeyInto(dk, folder, device[:], []byte(osutil.NormalizedFilename(nf.Name)))
ef, ok := s.db.getFile(dk)
if ok && ef.Version.Equal(nf.Version) && ef.IsInvalid() == nf.IsInvalid() {
continue
}
defer s.meta.toDB(s.db, []byte(s.folder))
nf.Sequence = s.meta.nextSeq(protocol.LocalDeviceID)
fs = append(fs, nf)
if ok {
discards = append(discards, ef)
}
updates = append(updates, nf)
}
s.blockmap.Discard(discards)
s.blockmap.Update(updates)
s.db.removeSequences(folder, discards)
s.db.addSequences(folder, updates)
if device != protocol.LocalDeviceID {
// Easy case, just update the files and we're done.
s.db.updateFiles([]byte(s.folder), device[:], fs, s.meta)
return
}
// For the local device we have a bunch of metadata to track however...
discards := make([]protocol.FileInfo, 0, len(fs))
updates := make([]protocol.FileInfo, 0, len(fs))
// db.UpdateFiles will sort unchanged files out -> save one db lookup
// filter slice according to https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
oldFs := fs
fs = fs[:0]
var dk []byte
folder := []byte(s.folder)
for _, nf := range oldFs {
dk = s.db.deviceKeyInto(dk, folder, device[:], []byte(osutil.NormalizedFilename(nf.Name)))
ef, ok := s.db.getFile(dk)
if ok && ef.Version.Equal(nf.Version) && ef.IsInvalid() == nf.IsInvalid() {
continue
}
nf.Sequence = s.meta.nextSeq(protocol.LocalDeviceID)
fs = append(fs, nf)
if ok {
discards = append(discards, ef)
}
updates = append(updates, nf)
}
// The ordering here is important. We first remove stuff that point to
// files we are going to update, then update them, then add new index
// pointers etc. In addition, we do the discards in reverse order so
// that a reader traversing the sequence index will get a consistent
// view up until the point they meet the writer.
sort.Slice(discards, func(a, b int) bool {
// n.b. "b < a" instead of the usual "a < b"
return discards[b].Sequence < discards[a].Sequence
})
s.blockmap.Discard(discards)
s.db.removeSequences(folder, discards)
s.db.updateFiles([]byte(s.folder), device[:], fs, s.meta)
s.meta.toDB(s.db, []byte(s.folder))
s.db.addSequences(folder, updates)
s.blockmap.Update(updates)
}
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
@@ -248,15 +271,23 @@ func (s *FileSet) Availability(file string) []protocol.DeviceID {
}
func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
return s.meta.Counts(device).Sequence
return s.meta.Counts(device, 0).Sequence
}
func (s *FileSet) LocalSize() Counts {
return s.meta.Counts(protocol.LocalDeviceID)
local := s.meta.Counts(protocol.LocalDeviceID, 0)
recvOnlyChanged := s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
return local.Add(recvOnlyChanged)
}
func (s *FileSet) ReceiveOnlyChangedSize() Counts {
return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
}
func (s *FileSet) GlobalSize() Counts {
return s.meta.Counts(globalDeviceID)
global := s.meta.Counts(protocol.GlobalDeviceID, 0)
recvOnlyChanged := s.meta.Counts(protocol.GlobalDeviceID, protocol.FlagLocalReceiveOnly)
return global.Add(recvOnlyChanged)
}
func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {

View File

@@ -12,6 +12,7 @@ import (
"os"
"sort"
"testing"
"time"
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/db"
@@ -906,7 +907,7 @@ func TestWithHaveSequence(t *testing.T) {
i := 2
s.WithHaveSequence(int64(i), func(fi db.FileIntf) bool {
if f := fi.(protocol.FileInfo); !f.IsEquivalent(localHave[i-1], false, false) {
if f := fi.(protocol.FileInfo); !f.IsEquivalent(localHave[i-1]) {
t.Fatalf("Got %v\nExpected %v", f, localHave[i-1])
}
i++
@@ -914,10 +915,60 @@ func TestWithHaveSequence(t *testing.T) {
})
}
func TestStressWithHaveSequence(t *testing.T) {
// This races two loops against each other: one that contiously does
// updates, and one that continously does sequence walks. The test fails
// if the sequence walker sees a discontinuity.
if testing.Short() {
t.Skip("Takes a long time")
}
ldb := db.OpenMemory()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
var localHave []protocol.FileInfo
for i := 0; i < 100; i++ {
localHave = append(localHave, protocol.FileInfo{Name: fmt.Sprintf("file%d", i), Blocks: genBlocks(i * 10)})
}
done := make(chan struct{})
t0 := time.Now()
go func() {
for time.Since(t0) < 10*time.Second {
for j, f := range localHave {
localHave[j].Version = f.Version.Update(42)
}
s.Update(protocol.LocalDeviceID, localHave)
}
close(done)
}()
var prevSeq int64 = 0
loop:
for {
select {
case <-done:
break loop
default:
}
s.WithHaveSequence(prevSeq+1, func(fi db.FileIntf) bool {
if fi.SequenceNo() < prevSeq+1 {
t.Fatal("Skipped ", prevSeq+1, fi.SequenceNo())
}
prevSeq = fi.SequenceNo()
return true
})
}
}
func TestIssue4925(t *testing.T) {
ldb := db.OpenMemory()
folder := "test)"
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
localHave := fileList{
@@ -955,7 +1006,7 @@ func TestMoveGlobalBack(t *testing.T) {
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
t.Error("Expected 1 local need, got", need)
} else if !need[0].IsEquivalent(remote0Have[0], false, false) {
} else if !need[0].IsEquivalent(remote0Have[0]) {
t.Errorf("Local need incorrect;\n A: %v !=\n E: %v", need[0], remote0Have[0])
}
@@ -981,7 +1032,7 @@ func TestMoveGlobalBack(t *testing.T) {
if need := needList(s, remoteDevice0); len(need) != 1 {
t.Error("Expected 1 need for remote 0, got", need)
} else if !need[0].IsEquivalent(localHave[0], false, false) {
} else if !need[0].IsEquivalent(localHave[0]) {
t.Errorf("Need for remote 0 incorrect;\n A: %v !=\n E: %v", need[0], localHave[0])
}
@@ -1017,7 +1068,7 @@ func TestIssue5007(t *testing.T) {
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected 1 local need, got", need)
} else if !need[0].IsEquivalent(fs[0], false, false) {
} else if !need[0].IsEquivalent(fs[0]) {
t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0])
}
@@ -1052,7 +1103,7 @@ func TestNeedDeleted(t *testing.T) {
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected 1 local need, got", need)
} else if !need[0].IsEquivalent(fs[0], false, false) {
} else if !need[0].IsEquivalent(fs[0]) {
t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0])
}
@@ -1065,6 +1116,110 @@ func TestNeedDeleted(t *testing.T) {
}
}
func TestReceiveOnlyAccounting(t *testing.T) {
ldb := db.OpenMemory()
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := protocol.DeviceID{1}
remote := protocol.DeviceID{2}
// Three files that have been created by the remote device
version := protocol.Vector{Counters: []protocol.Counter{{ID: remote.Short(), Value: 1}}}
files := fileList{
protocol.FileInfo{Name: "f1", Size: 10, Sequence: 1, Version: version},
protocol.FileInfo{Name: "f2", Size: 10, Sequence: 1, Version: version},
protocol.FileInfo{Name: "f3", Size: 10, Sequence: 1, Version: version},
}
// We have synced them locally
replace(s, protocol.LocalDeviceID, files)
replace(s, remote, files)
if n := s.LocalSize().Files; n != 3 {
t.Fatal("expected 3 local files initially, not", n)
}
if n := s.LocalSize().Bytes; n != 30 {
t.Fatal("expected 30 local bytes initially, not", n)
}
if n := s.GlobalSize().Files; n != 3 {
t.Fatal("expected 3 global files initially, not", n)
}
if n := s.GlobalSize().Bytes; n != 30 {
t.Fatal("expected 30 global bytes initially, not", n)
}
if n := s.ReceiveOnlyChangedSize().Files; n != 0 {
t.Fatal("expected 0 receive only changed files initially, not", n)
}
if n := s.ReceiveOnlyChangedSize().Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes initially, not", n)
}
// Detected a local change in a receive only folder
changed := files[0]
changed.Version = changed.Version.Update(local.Short())
changed.Size = 100
changed.ModifiedBy = local.Short()
changed.LocalFlags = protocol.FlagLocalReceiveOnly
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed})
// Check that we see the files
if n := s.LocalSize().Files; n != 3 {
t.Fatal("expected 3 local files after local change, not", n)
}
if n := s.LocalSize().Bytes; n != 120 {
t.Fatal("expected 120 local bytes after local change, not", n)
}
if n := s.GlobalSize().Files; n != 3 {
t.Fatal("expected 3 global files after local change, not", n)
}
if n := s.GlobalSize().Bytes; n != 120 {
t.Fatal("expected 120 global bytes after local change, not", n)
}
if n := s.ReceiveOnlyChangedSize().Files; n != 1 {
t.Fatal("expected 1 receive only changed file after local change, not", n)
}
if n := s.ReceiveOnlyChangedSize().Bytes; n != 100 {
t.Fatal("expected 100 receive only changed btyes after local change, not", n)
}
// Fake a revert. That's a two step process, first converting our
// changed file into a less preferred variant, then pulling down the old
// version.
changed.Version = protocol.Vector{}
changed.LocalFlags &^= protocol.FlagLocalReceiveOnly
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{changed})
s.Update(protocol.LocalDeviceID, []protocol.FileInfo{files[0]})
// Check that we see the files, same data as initially
if n := s.LocalSize().Files; n != 3 {
t.Fatal("expected 3 local files after revert, not", n)
}
if n := s.LocalSize().Bytes; n != 30 {
t.Fatal("expected 30 local bytes after revert, not", n)
}
if n := s.GlobalSize().Files; n != 3 {
t.Fatal("expected 3 global files after revert, not", n)
}
if n := s.GlobalSize().Bytes; n != 30 {
t.Fatal("expected 30 global bytes after revert, not", n)
}
if n := s.ReceiveOnlyChangedSize().Files; n != 0 {
t.Fatal("expected 0 receive only changed files after revert, not", n)
}
if n := s.ReceiveOnlyChangedSize().Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes after revert, not", n)
}
}
func TestNeedAfterUnignore(t *testing.T) {
ldb := db.OpenMemory()
@@ -1090,11 +1245,32 @@ func TestNeedAfterUnignore(t *testing.T) {
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected one local need, got", need)
} else if !need[0].IsEquivalent(remote, false, false) {
} else if !need[0].IsEquivalent(remote) {
t.Fatalf("Got %v, expected %v", need[0], remote)
}
}
func TestRemoteInvalidNotAccounted(t *testing.T) {
// Remote files with the invalid bit should not count.
ldb := db.OpenMemory()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
files := []protocol.FileInfo{
{Name: "a", Size: 1234, Sequence: 42, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}}, // valid, should count
{Name: "b", Size: 1234, Sequence: 43, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, RawInvalid: true}, // invalid, doesn't count
}
s.Update(remoteDevice0, files)
global := s.GlobalSize()
if global.Files != 1 {
t.Error("Expected one file in global size, not", global.Files)
}
if global.Bytes != 1234 {
t.Error("Expected 1234 bytes in global size, not", global.Bytes)
}
}
func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo) {
fs.Drop(device)
fs.Update(device, files)

View File

@@ -40,6 +40,10 @@ func (f FileInfoTruncated) IsInvalid() bool {
return f.RawInvalid || f.LocalFlags&protocol.LocalInvalidFlags != 0
}
func (f FileInfoTruncated) IsUnsupported() bool {
return f.LocalFlags&protocol.FlagLocalUnsupported != 0
}
func (f FileInfoTruncated) IsIgnored() bool {
return f.LocalFlags&protocol.FlagLocalIgnored != 0
}
@@ -48,6 +52,10 @@ func (f FileInfoTruncated) MustRescan() bool {
return f.LocalFlags&protocol.FlagLocalMustRescan != 0
}
func (f FileInfoTruncated) IsReceiveOnlyChanged() bool {
return f.LocalFlags&protocol.FlagLocalReceiveOnly != 0
}
func (f FileInfoTruncated) IsDirectory() bool {
return f.Type == protocol.FileInfoTypeDirectory
}
@@ -86,6 +94,10 @@ func (f FileInfoTruncated) FileName() string {
return f.Name
}
func (f FileInfoTruncated) FileLocalFlags() uint32 {
return f.LocalFlags
}
func (f FileInfoTruncated) ModTime() time.Time {
return time.Unix(f.ModifiedS, int64(f.ModifiedNs))
}
@@ -110,3 +122,16 @@ func (f FileInfoTruncated) ConvertToIgnoredFileInfo(by protocol.ShortID) protoco
LocalFlags: protocol.FlagLocalIgnored,
}
}
func (c Counts) Add(other Counts) Counts {
return Counts{
Files: c.Files + other.Files,
Directories: c.Directories + other.Directories,
Symlinks: c.Symlinks + other.Symlinks,
Deleted: c.Deleted + other.Deleted,
Bytes: c.Bytes + other.Bytes,
Sequence: c.Sequence + other.Sequence,
DeviceID: protocol.EmptyDeviceID[:],
LocalFlags: c.LocalFlags | other.LocalFlags,
}
}

View File

@@ -91,6 +91,7 @@ type Counts struct {
Bytes int64 `protobuf:"varint,5,opt,name=bytes,proto3" json:"bytes,omitempty"`
Sequence int64 `protobuf:"varint,6,opt,name=sequence,proto3" json:"sequence,omitempty"`
DeviceID []byte `protobuf:"bytes,17,opt,name=deviceID,proto3" json:"deviceID,omitempty"`
LocalFlags uint32 `protobuf:"varint,18,opt,name=localFlags,proto3" json:"localFlags,omitempty"`
}
func (m *Counts) Reset() { *m = Counts{} }
@@ -357,6 +358,13 @@ func (m *Counts) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintStructs(dAtA, i, uint64(len(m.DeviceID)))
i += copy(dAtA[i:], m.DeviceID)
}
if m.LocalFlags != 0 {
dAtA[i] = 0x90
i++
dAtA[i] = 0x1
i++
i = encodeVarintStructs(dAtA, i, uint64(m.LocalFlags))
}
return i, nil
}
@@ -526,6 +534,9 @@ func (m *Counts) ProtoSize() (n int) {
if l > 0 {
n += 2 + l + sovStructs(uint64(l))
}
if m.LocalFlags != 0 {
n += 2 + sovStructs(uint64(m.LocalFlags))
}
return n
}
@@ -1312,6 +1323,25 @@ func (m *Counts) Unmarshal(dAtA []byte) error {
m.DeviceID = []byte{}
}
iNdEx = postIndex
case 18:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field LocalFlags", wireType)
}
m.LocalFlags = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.LocalFlags |= (uint32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipStructs(dAtA[iNdEx:])
@@ -1541,47 +1571,47 @@ var (
func init() { proto.RegisterFile("structs.proto", fileDescriptorStructs) }
var fileDescriptorStructs = []byte{
// 663 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6a, 0xdb, 0x40,
0x10, 0xb6, 0x62, 0xf9, 0x6f, 0x6c, 0xa7, 0xc9, 0x12, 0x82, 0x30, 0xd4, 0x16, 0x86, 0x82, 0x28,
0xd4, 0x6e, 0x13, 0x7a, 0x69, 0x6f, 0x6a, 0x08, 0x18, 0x4a, 0x5b, 0xd6, 0x21, 0xa7, 0x82, 0xd1,
0xcf, 0xda, 0x59, 0x22, 0x6b, 0x1d, 0xed, 0x3a, 0x41, 0x79, 0x92, 0x1e, 0xf3, 0x30, 0x3d, 0xe4,
0xd8, 0x73, 0x0f, 0x26, 0x75, 0x2f, 0x7d, 0x8c, 0xb2, 0xbb, 0x92, 0xa2, 0xf6, 0xd4, 0xde, 0xe6,
0x9b, 0x9f, 0x9d, 0x6f, 0x66, 0xbe, 0x85, 0x2e, 0x17, 0xc9, 0x3a, 0x10, 0x7c, 0xb4, 0x4a, 0x98,
0x60, 0x68, 0x27, 0xf4, 0x7b, 0x2f, 0x16, 0x54, 0x5c, 0xac, 0xfd, 0x51, 0xc0, 0x96, 0xe3, 0x05,
0x5b, 0xb0, 0xb1, 0x0a, 0xf9, 0xeb, 0xb9, 0x42, 0x0a, 0x28, 0x4b, 0x97, 0xf4, 0x5e, 0x97, 0xd2,
0x79, 0x1a, 0x07, 0xe2, 0x82, 0xc6, 0x8b, 0x92, 0x15, 0x51, 0x5f, 0xbf, 0x10, 0xb0, 0x68, 0xec,
0x93, 0x95, 0x2e, 0x1b, 0x5e, 0x41, 0xfb, 0x94, 0x46, 0xe4, 0x9c, 0x24, 0x9c, 0xb2, 0x18, 0xbd,
0x84, 0xc6, 0xb5, 0x36, 0x2d, 0xc3, 0x36, 0x9c, 0xf6, 0xd1, 0xde, 0x28, 0x2f, 0x1a, 0x9d, 0x93,
0x40, 0xb0, 0xc4, 0x35, 0xef, 0x37, 0x83, 0x0a, 0xce, 0xd3, 0xd0, 0x21, 0xd4, 0x43, 0x72, 0x4d,
0x03, 0x62, 0xed, 0xd8, 0x86, 0xd3, 0xc1, 0x19, 0x42, 0x16, 0x34, 0x68, 0x7c, 0xed, 0x45, 0x34,
0xb4, 0xaa, 0xb6, 0xe1, 0x34, 0x71, 0x0e, 0x87, 0xa7, 0xd0, 0xce, 0xda, 0xbd, 0xa7, 0x5c, 0xa0,
0x57, 0xd0, 0xcc, 0xde, 0xe2, 0x96, 0x61, 0x57, 0x9d, 0xf6, 0xd1, 0x93, 0x51, 0xe8, 0x8f, 0x4a,
0xac, 0xb2, 0x96, 0x45, 0xda, 0x1b, 0xf3, 0xcb, 0xdd, 0xa0, 0x32, 0x7c, 0x30, 0x61, 0x5f, 0x66,
0x4d, 0xe2, 0x39, 0x3b, 0x4b, 0xd6, 0x71, 0xe0, 0x09, 0x12, 0x22, 0x04, 0x66, 0xec, 0x2d, 0x89,
0xa2, 0xdf, 0xc2, 0xca, 0x46, 0xcf, 0xc1, 0x14, 0xe9, 0x4a, 0x33, 0xdc, 0x3d, 0x3a, 0x7c, 0x1c,
0xa9, 0x28, 0x4f, 0x57, 0x04, 0xab, 0x1c, 0x59, 0xcf, 0xe9, 0x2d, 0x51, 0xa4, 0xab, 0x58, 0xd9,
0xc8, 0x86, 0xf6, 0x8a, 0x24, 0x4b, 0xca, 0x35, 0x4b, 0xd3, 0x36, 0x9c, 0x2e, 0x2e, 0xbb, 0xd0,
0x53, 0x80, 0x25, 0x0b, 0xe9, 0x9c, 0x92, 0x70, 0xc6, 0xad, 0x9a, 0xaa, 0x6d, 0xe5, 0x9e, 0xa9,
0x5c, 0x46, 0x48, 0x22, 0x22, 0x48, 0x68, 0xd5, 0xf5, 0x32, 0x32, 0x88, 0x9c, 0xc7, 0x35, 0x35,
0x64, 0xc4, 0xdd, 0xdd, 0x6e, 0x06, 0x80, 0xbd, 0x9b, 0x89, 0xf6, 0x16, 0x6b, 0x43, 0xcf, 0x60,
0x37, 0x66, 0xb3, 0x32, 0x8f, 0xa6, 0x7a, 0xaa, 0x1b, 0xb3, 0x4f, 0x25, 0x26, 0xa5, 0x0b, 0xb6,
0xfe, 0xed, 0x82, 0x3d, 0x68, 0x72, 0x72, 0xb5, 0x26, 0x71, 0x40, 0x2c, 0x50, 0xcc, 0x0b, 0x8c,
0x06, 0xd0, 0x2e, 0xe6, 0x8a, 0xb9, 0xd5, 0xb6, 0x0d, 0xa7, 0x86, 0x8b, 0x51, 0x3f, 0x70, 0xf4,
0xb9, 0x94, 0xe0, 0xa7, 0x56, 0xc7, 0x36, 0x1c, 0xd3, 0x7d, 0x2b, 0x1b, 0x7c, 0xdf, 0x0c, 0x8e,
0xff, 0x43, 0x93, 0xa3, 0xe9, 0x05, 0x4b, 0xc4, 0xe4, 0xe4, 0xf1, 0x75, 0x37, 0x45, 0x63, 0x00,
0x3f, 0x62, 0xc1, 0xe5, 0x4c, 0x9d, 0xa4, 0x2b, 0xbb, 0xbb, 0x7b, 0xdb, 0xcd, 0xa0, 0x83, 0xbd,
0x1b, 0x57, 0x06, 0xa6, 0xf4, 0x96, 0xe0, 0x96, 0x9f, 0x9b, 0x72, 0x49, 0x3c, 0x5d, 0x46, 0x34,
0xbe, 0x9c, 0x09, 0x2f, 0x59, 0x10, 0x61, 0xed, 0x2b, 0x1d, 0x74, 0x33, 0xef, 0x99, 0x72, 0xca,
0x83, 0x46, 0x2c, 0xf0, 0xa2, 0xd9, 0x3c, 0xf2, 0x16, 0xdc, 0xfa, 0xd5, 0x50, 0x17, 0x05, 0xe5,
0x3b, 0x95, 0xae, 0x4c, 0x62, 0x5f, 0x0d, 0xa8, 0xbf, 0x63, 0xeb, 0x58, 0x70, 0x74, 0x00, 0xb5,
0x39, 0x8d, 0x08, 0x57, 0xc2, 0xaa, 0x61, 0x0d, 0xe4, 0x43, 0x21, 0x4d, 0xd4, 0x5a, 0x29, 0xe1,
0x4a, 0x60, 0x35, 0x5c, 0x76, 0xa9, 0xed, 0xea, 0xde, 0x5c, 0x69, 0xaa, 0x86, 0x0b, 0x5c, 0x96,
0x85, 0xa9, 0x42, 0x85, 0x2c, 0x0e, 0xa0, 0xe6, 0xa7, 0x82, 0xe4, 0x52, 0xd2, 0xe0, 0x8f, 0x4b,
0xd5, 0xff, 0xba, 0x54, 0x0f, 0x9a, 0xfa, 0xe7, 0x4d, 0x4e, 0xd4, 0xcc, 0x1d, 0x5c, 0xe0, 0xe1,
0x47, 0x68, 0xe9, 0x29, 0xa6, 0x44, 0x20, 0x07, 0xea, 0x81, 0x02, 0xd9, 0x6f, 0x03, 0xf9, 0xdb,
0x74, 0x38, 0x53, 0x46, 0x16, 0x97, 0xf4, 0x82, 0x84, 0xc8, 0x5f, 0xa5, 0x06, 0xab, 0xe2, 0x1c,
0xba, 0x07, 0xf7, 0x3f, 0xfa, 0x95, 0xfb, 0x6d, 0xdf, 0xf8, 0xb6, 0xed, 0x1b, 0x0f, 0xdb, 0x7e,
0xe5, 0xee, 0x67, 0xdf, 0xf0, 0xeb, 0xea, 0x96, 0xc7, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x9a,
0x4b, 0x16, 0x44, 0xcd, 0x04, 0x00, 0x00,
// 671 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4d, 0x6b, 0xdb, 0x4c,
0x10, 0xb6, 0x62, 0xf9, 0x6b, 0x6c, 0xe7, 0x4d, 0x96, 0x10, 0x84, 0xe1, 0xb5, 0x85, 0xa1, 0x20,
0x0a, 0xb5, 0xdb, 0x84, 0x5e, 0xda, 0x9b, 0x1a, 0x02, 0x86, 0xd2, 0x96, 0x75, 0xc8, 0xa9, 0x60,
0xf4, 0xb1, 0x76, 0x96, 0xc8, 0x5a, 0x47, 0xbb, 0x4e, 0x50, 0x7e, 0x49, 0x8f, 0xf9, 0x39, 0x39,
0xf6, 0xdc, 0x83, 0x49, 0xdd, 0x1e, 0xfa, 0x33, 0xca, 0xee, 0x4a, 0x8a, 0x9a, 0x53, 0x7b, 0x9b,
0x67, 0x3e, 0x76, 0x9e, 0x99, 0x79, 0x16, 0xba, 0x5c, 0x24, 0xeb, 0x40, 0xf0, 0xd1, 0x2a, 0x61,
0x82, 0xa1, 0x9d, 0xd0, 0xef, 0xbd, 0x58, 0x50, 0x71, 0xb1, 0xf6, 0x47, 0x01, 0x5b, 0x8e, 0x17,
0x6c, 0xc1, 0xc6, 0x2a, 0xe4, 0xaf, 0xe7, 0x0a, 0x29, 0xa0, 0x2c, 0x5d, 0xd2, 0x7b, 0x5d, 0x4a,
0xe7, 0x69, 0x1c, 0x88, 0x0b, 0x1a, 0x2f, 0x4a, 0x56, 0x44, 0x7d, 0xfd, 0x42, 0xc0, 0xa2, 0xb1,
0x4f, 0x56, 0xba, 0x6c, 0x78, 0x05, 0xed, 0x53, 0x1a, 0x91, 0x73, 0x92, 0x70, 0xca, 0x62, 0xf4,
0x12, 0x1a, 0xd7, 0xda, 0xb4, 0x0c, 0xdb, 0x70, 0xda, 0x47, 0x7b, 0xa3, 0xbc, 0x68, 0x74, 0x4e,
0x02, 0xc1, 0x12, 0xd7, 0xbc, 0xdf, 0x0c, 0x2a, 0x38, 0x4f, 0x43, 0x87, 0x50, 0x0f, 0xc9, 0x35,
0x0d, 0x88, 0xb5, 0x63, 0x1b, 0x4e, 0x07, 0x67, 0x08, 0x59, 0xd0, 0xa0, 0xf1, 0xb5, 0x17, 0xd1,
0xd0, 0xaa, 0xda, 0x86, 0xd3, 0xc4, 0x39, 0x1c, 0x9e, 0x42, 0x3b, 0x6b, 0xf7, 0x9e, 0x72, 0x81,
0x5e, 0x41, 0x33, 0x7b, 0x8b, 0x5b, 0x86, 0x5d, 0x75, 0xda, 0x47, 0xff, 0x8d, 0x42, 0x7f, 0x54,
0x62, 0x95, 0xb5, 0x2c, 0xd2, 0xde, 0x98, 0x5f, 0xee, 0x06, 0x95, 0xe1, 0x83, 0x09, 0xfb, 0x32,
0x6b, 0x12, 0xcf, 0xd9, 0x59, 0xb2, 0x8e, 0x03, 0x4f, 0x90, 0x10, 0x21, 0x30, 0x63, 0x6f, 0x49,
0x14, 0xfd, 0x16, 0x56, 0x36, 0x7a, 0x0e, 0xa6, 0x48, 0x57, 0x9a, 0xe1, 0xee, 0xd1, 0xe1, 0xe3,
0x48, 0x45, 0x79, 0xba, 0x22, 0x58, 0xe5, 0xc8, 0x7a, 0x4e, 0x6f, 0x89, 0x22, 0x5d, 0xc5, 0xca,
0x46, 0x36, 0xb4, 0x57, 0x24, 0x59, 0x52, 0xae, 0x59, 0x9a, 0xb6, 0xe1, 0x74, 0x71, 0xd9, 0x85,
0xfe, 0x07, 0x58, 0xb2, 0x90, 0xce, 0x29, 0x09, 0x67, 0xdc, 0xaa, 0xa9, 0xda, 0x56, 0xee, 0x99,
0xca, 0x65, 0x84, 0x24, 0x22, 0x82, 0x84, 0x56, 0x5d, 0x2f, 0x23, 0x83, 0xc8, 0x79, 0x5c, 0x53,
0x43, 0x46, 0xdc, 0xdd, 0xed, 0x66, 0x00, 0xd8, 0xbb, 0x99, 0x68, 0x6f, 0xb1, 0x36, 0xf4, 0x0c,
0x76, 0x63, 0x36, 0x2b, 0xf3, 0x68, 0xaa, 0xa7, 0xba, 0x31, 0xfb, 0x54, 0x62, 0x52, 0xba, 0x60,
0xeb, 0xef, 0x2e, 0xd8, 0x83, 0x26, 0x27, 0x57, 0x6b, 0x12, 0x07, 0xc4, 0x02, 0xc5, 0xbc, 0xc0,
0x68, 0x00, 0xed, 0x62, 0xae, 0x98, 0x5b, 0x6d, 0xdb, 0x70, 0x6a, 0xb8, 0x18, 0xf5, 0x03, 0x47,
0x9f, 0x4b, 0x09, 0x7e, 0x6a, 0x75, 0x6c, 0xc3, 0x31, 0xdd, 0xb7, 0xb2, 0xc1, 0xb7, 0xcd, 0xe0,
0xf8, 0x1f, 0x34, 0x39, 0x9a, 0x5e, 0xb0, 0x44, 0x4c, 0x4e, 0x1e, 0x5f, 0x77, 0x53, 0x34, 0x06,
0xf0, 0x23, 0x16, 0x5c, 0xce, 0xd4, 0x49, 0xba, 0xb2, 0xbb, 0xbb, 0xb7, 0xdd, 0x0c, 0x3a, 0xd8,
0xbb, 0x71, 0x65, 0x60, 0x4a, 0x6f, 0x09, 0x6e, 0xf9, 0xb9, 0x29, 0x97, 0xc4, 0xd3, 0x65, 0x44,
0xe3, 0xcb, 0x99, 0xf0, 0x92, 0x05, 0x11, 0xd6, 0xbe, 0xd2, 0x41, 0x37, 0xf3, 0x9e, 0x29, 0xa7,
0x3c, 0x68, 0xc4, 0x02, 0x2f, 0x9a, 0xcd, 0x23, 0x6f, 0xc1, 0xad, 0x5f, 0x0d, 0x75, 0x51, 0x50,
0xbe, 0x53, 0xe9, 0xca, 0x24, 0xf6, 0xd3, 0x80, 0xfa, 0x3b, 0xb6, 0x8e, 0x05, 0x47, 0x07, 0x50,
0x9b, 0xd3, 0x88, 0x70, 0x25, 0xac, 0x1a, 0xd6, 0x40, 0x3e, 0x14, 0xd2, 0x44, 0xad, 0x95, 0x12,
0xae, 0x04, 0x56, 0xc3, 0x65, 0x97, 0xda, 0xae, 0xee, 0xcd, 0x95, 0xa6, 0x6a, 0xb8, 0xc0, 0x65,
0x59, 0x98, 0x2a, 0x54, 0xc8, 0xe2, 0x00, 0x6a, 0x7e, 0x2a, 0x48, 0x2e, 0x25, 0x0d, 0xfe, 0xb8,
0x54, 0xfd, 0xc9, 0xa5, 0x7a, 0xd0, 0xd4, 0x3f, 0x6f, 0x72, 0xa2, 0x66, 0xee, 0xe0, 0x02, 0xa3,
0x3e, 0x94, 0x46, 0xb3, 0xd0, 0xd3, 0x61, 0x87, 0x1f, 0xa1, 0xa5, 0xa7, 0x9c, 0x12, 0x81, 0x1c,
0xa8, 0x07, 0x0a, 0x64, 0xbf, 0x11, 0xe4, 0x6f, 0xd4, 0xe1, 0x4c, 0x39, 0x59, 0x5c, 0xd2, 0x0f,
0x12, 0x22, 0x7f, 0x9d, 0x1a, 0xbc, 0x8a, 0x73, 0xe8, 0x1e, 0xdc, 0x7f, 0xef, 0x57, 0xee, 0xb7,
0x7d, 0xe3, 0xeb, 0xb6, 0x6f, 0x3c, 0x6c, 0xfb, 0x95, 0xbb, 0x1f, 0x7d, 0xc3, 0xaf, 0xab, 0x5b,
0x1f, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xc4, 0x4d, 0xd7, 0x14, 0xed, 0x04, 0x00, 0x00,
}

View File

@@ -46,13 +46,14 @@ message FileInfoTruncated {
// For each folder and device we keep one of these to track the current
// counts and sequence. We also keep one for the global state of the folder.
message Counts {
int32 files = 1;
int32 directories = 2;
int32 symlinks = 3;
int32 deleted = 4;
int64 bytes = 5;
int64 sequence = 6; // zero for the global state
bytes deviceID = 17; // device ID for remote devices, or special values for local/global
int32 files = 1;
int32 directories = 2;
int32 symlinks = 3;
int32 deleted = 4;
int64 bytes = 5;
int64 sequence = 6; // zero for the global state
bytes deviceID = 17; // device ID for remote devices, or special values for local/global
uint32 localFlags = 18; // the local flag for this count bucket
}
message CountsSet {

View File

@@ -25,8 +25,7 @@ var (
// The BasicFilesystem implements all aspects by delegating to package os.
// All paths are relative to the root and cannot (should not) escape the root directory.
type BasicFilesystem struct {
root string
rootSymlinkEvaluated string
root string
}
func newBasicFilesystem(root string) *BasicFilesystem {
@@ -36,7 +35,8 @@ func newBasicFilesystem(root string) *BasicFilesystem {
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
root = filepath.Dir(root + string(filepath.Separator))
sep := string(filepath.Separator)
root = filepath.Dir(root + sep)
// Attempt tilde expansion; leave unchanged in case of error
if path, err := ExpandTilde(root); err == nil {
@@ -53,34 +53,13 @@ func newBasicFilesystem(root string) *BasicFilesystem {
}
}
rootSymlinkEvaluated, err := filepath.EvalSymlinks(root)
if err != nil {
rootSymlinkEvaluated = root
}
return &BasicFilesystem{
root: adjustRoot(root),
rootSymlinkEvaluated: adjustRoot(rootSymlinkEvaluated),
}
}
func adjustRoot(root string) string {
// Attempt to enable long filename support on Windows. We may still not
// have an absolute path here if the previous steps failed.
if runtime.GOOS == "windows" {
if filepath.IsAbs(root) && !strings.HasPrefix(root, `\\`) {
root = `\\?\` + root
}
return root
root = longFilenameSupport(root)
}
// If we're not on Windows, we want the path to end with a slash to
// penetrate symlinks. On Windows, paths must not end with a slash.
if root[len(root)-1] != filepath.Separator {
root = root + string(filepath.Separator)
}
return root
return &BasicFilesystem{root}
}
// rooted expands the relative path to the full path that is then used with os
@@ -91,57 +70,26 @@ func (f *BasicFilesystem) rooted(rel string) (string, error) {
return rooted(rel, f.root)
}
// rootedSymlinkEvaluated does the same as rooted, but the returned path will not
// contain any symlinks. package. If the relative path somehow causes the final
// path to escape the root directory, this returns an error, to prevent accessing
// files that are not in the shared directory.
func (f *BasicFilesystem) rootedSymlinkEvaluated(rel string) (string, error) {
return rooted(rel, f.rootSymlinkEvaluated)
}
func rooted(rel, root string) (string, error) {
// The root must not be empty.
if root == "" {
return "", ErrInvalidFilename
}
pathSep := string(PathSeparator)
// The expected prefix for the resulting path is the root, with a path
// separator at the end.
expectedPrefix := filepath.FromSlash(root)
if !strings.HasSuffix(expectedPrefix, pathSep) {
expectedPrefix += pathSep
}
var err error
// Takes care that rel does not try to escape
rel, err = Canonicalize(rel)
if err != nil {
return "", err
}
// The supposedly correct path is the one filepath.Join will return, as
// it does cleaning and so on. Check that one first to make sure no
// obvious escape attempts have been made.
joined := filepath.Join(root, rel)
if rel == "." && !strings.HasSuffix(joined, pathSep) {
joined += pathSep
}
if !strings.HasPrefix(joined, expectedPrefix) {
return "", ErrNotRelative
}
return joined, nil
return filepath.Join(root, rel), nil
}
func (f *BasicFilesystem) unrooted(path string) string {
return rel(path, f.root)
}
func (f *BasicFilesystem) unrootedSymlinkEvaluated(path string) string {
return rel(path, f.rootSymlinkEvaluated)
}
func rel(path, prefix string) string {
return strings.TrimPrefix(strings.TrimPrefix(path, prefix), string(PathSeparator))
}
@@ -372,3 +320,13 @@ func (e fsFileInfo) IsRegular() bool {
// Must use fsFileInfo.Mode() because it may apply magic.
return e.Mode()&ModeType == 0
}
// longFilenameSupport adds the necessary prefix to the path to enable long
// filename support on windows if necessary.
// This does NOT check the current system, i.e. will also take effect on unix paths.
func longFilenameSupport(path string) string {
if filepath.IsAbs(path) && !strings.HasPrefix(path, `\\`) {
return `\\?\` + path
}
return path
}

View File

@@ -12,6 +12,7 @@ import (
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
"time"
)
@@ -331,10 +332,14 @@ func TestRooted(t *testing.T) {
{"baz/foo/", "/bar/baz", "baz/foo/bar/baz", true},
// Not escape attempts, but oddly formatted relative paths.
{"foo", "", "foo/", true},
{"foo", "/", "foo/", true},
{"foo", "/..", "foo/", true},
{"foo", "", "foo", true},
{"foo", "/", "foo", true},
{"foo", "/..", "foo", true},
{"foo", "./bar", "foo/bar", true},
{"foo/", "", "foo", true},
{"foo/", "/", "foo", true},
{"foo/", "/..", "foo", true},
{"foo/", "./bar", "foo/bar", true},
{"baz/foo", "./bar", "baz/foo/bar", true},
{"foo", "./bar/baz", "foo/bar/baz", true},
{"baz/foo", "./bar/baz", "baz/foo/bar/baz", true},
@@ -395,6 +400,10 @@ func TestRooted(t *testing.T) {
{`\\?\c:\`, `\\foo`, ``, false},
{`\\?\c:\`, ``, `\\?\c:\`, true},
{`\\?\c:\`, `\`, `\\?\c:\`, true},
{`\\?\c:\test`, `.`, `\\?\c:\test`, true},
{`c:\test`, `.`, `c:\test`, true},
{`\\?\c:\test`, `/`, `\\?\c:\test`, true},
{`c:\test`, ``, `c:\test`, true},
// makes no sense, but will be treated simply as a bad filename
{`c:\foo`, `d:\bar`, `c:\foo\d:\bar`, true},
@@ -449,3 +458,35 @@ func TestRooted(t *testing.T) {
}
}
}
func TestNewBasicFilesystem(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("non-windows root paths")
}
testCases := []struct {
input string
expectedRoot string
expectedURI string
}{
{"/foo/bar/baz", "/foo/bar/baz", "/foo/bar/baz"},
{"/foo/bar/baz/", "/foo/bar/baz", "/foo/bar/baz"},
{"", "/", "/"},
{"/", "/", "/"},
}
for _, testCase := range testCases {
fs := newBasicFilesystem(testCase.input)
if fs.root != testCase.expectedRoot {
t.Errorf("root %q != %q", fs.root, testCase.expectedRoot)
}
if fs.URI() != testCase.expectedURI {
t.Errorf("uri %q != %q", fs.URI(), testCase.expectedURI)
}
}
fs := newBasicFilesystem("relative/path")
if fs.root == "relative/path" || !strings.HasPrefix(fs.root, string(PathSeparator)) {
t.Errorf(`newBasicFilesystem("relative/path").root == %q, expected absolutification`, fs.root)
}
}

View File

@@ -13,6 +13,7 @@ import (
"errors"
"fmt"
"path/filepath"
"runtime"
"strings"
"github.com/syncthing/notify"
@@ -24,7 +25,15 @@ import (
var backendBuffer = 500
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
absName, err := f.rootedSymlinkEvaluated(name)
evalRoot, err := filepath.EvalSymlinks(f.root)
if err != nil {
return nil, err
}
if runtime.GOOS == "windows" {
evalRoot = longFilenameSupport(evalRoot)
}
absName, err := rooted(name, evalRoot)
if err != nil {
return nil, err
}
@@ -39,7 +48,7 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
if ignore.SkipIgnoredDirs() {
absShouldIgnore := func(absPath string) bool {
return ignore.ShouldIgnore(f.unrootedChecked(absPath))
return ignore.ShouldIgnore(f.unrootedChecked(absPath, evalRoot))
}
err = notify.WatchWithFilter(filepath.Join(absName, "..."), backendChan, absShouldIgnore, eventMask)
} else {
@@ -53,12 +62,12 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
return nil, err
}
go f.watchLoop(name, backendChan, outChan, ignore, ctx)
go f.watchLoop(name, evalRoot, backendChan, outChan, ignore, ctx)
return outChan, nil
}
func (f *BasicFilesystem) watchLoop(name string, backendChan chan notify.EventInfo, outChan chan<- Event, ignore Matcher, ctx context.Context) {
func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan notify.EventInfo, outChan chan<- Event, ignore Matcher, ctx context.Context) {
for {
// Detect channel overflow
if len(backendChan) == backendBuffer {
@@ -77,7 +86,7 @@ func (f *BasicFilesystem) watchLoop(name string, backendChan chan notify.EventIn
select {
case ev := <-backendChan:
relPath := f.unrootedChecked(ev.Path())
relPath := f.unrootedChecked(ev.Path(), evalRoot)
if ignore.ShouldIgnore(relPath) {
l.Debugln(f.Type(), f.URI(), "Watch: Ignoring", relPath)
continue
@@ -110,12 +119,13 @@ func (f *BasicFilesystem) eventType(notifyType notify.Event) EventType {
// unrooted). It panics if the given path is not a subpath and handles the
// special case when the given path is the folder root without a trailing
// pathseparator.
func (f *BasicFilesystem) unrootedChecked(absPath string) string {
if absPath+string(PathSeparator) == f.rootSymlinkEvaluated {
func (f *BasicFilesystem) unrootedChecked(absPath, root string) string {
absPath = f.resolveWin83(absPath)
if absPath+string(PathSeparator) == root {
return "."
}
if !strings.HasPrefix(absPath, f.rootSymlinkEvaluated) {
panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: root==%v, rootSymEval==%v, path==%v", f.root, f.rootSymlinkEvaluated, absPath))
if !strings.HasPrefix(absPath, root) {
panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: f.root==%v, root==%v, path==%v", f.root, root, absPath))
}
return f.unrootedSymlinkEvaluated(f.resolveWin83(absPath))
return rel(absPath, root)
}

View File

@@ -33,11 +33,17 @@ func TestMain(m *testing.M) {
if err != nil {
panic("Cannot get absolute path to working dir")
}
dir, err = filepath.EvalSymlinks(dir)
if err != nil {
panic("Cannot get real path to working dir")
}
testDirAbs = filepath.Join(dir, testDir)
if runtime.GOOS == "windows" {
testDirAbs = longFilenameSupport(testDirAbs)
}
testFs = NewFilesystem(FilesystemTypeBasic, testDirAbs)
backendBuffer = 10
@@ -154,7 +160,7 @@ func TestWatchOutside(t *testing.T) {
}
cancel()
}()
fs.watchLoop(".", backendChan, outChan, fakeMatcher{}, ctx)
fs.watchLoop(".", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
}()
backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
@@ -177,7 +183,7 @@ func TestWatchSubpath(t *testing.T) {
fs := newBasicFilesystem(testDirAbs)
abs, _ := fs.rooted("sub")
go fs.watchLoop("sub", backendChan, outChan, fakeMatcher{}, ctx)
go fs.watchLoop("sub", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
@@ -291,7 +297,7 @@ func TestUnrootedChecked(t *testing.T) {
}
}()
fs := newBasicFilesystem(testDirAbs)
unrooted = fs.unrootedChecked("/random/other/path")
unrooted = fs.unrootedChecked("/random/other/path", testDirAbs)
}
func TestWatchIssue4877(t *testing.T) {

View File

@@ -170,12 +170,12 @@ func (f *BasicFilesystem) resolveWin83(absPath string) string {
}
// Failed getting the long path. Return the part of the path which is
// already a long path.
for absPath = filepath.Dir(absPath); strings.HasPrefix(absPath, f.rootSymlinkEvaluated); absPath = filepath.Dir(absPath) {
for absPath = filepath.Dir(absPath); strings.HasPrefix(absPath, f.root); absPath = filepath.Dir(absPath) {
if !isMaybeWin83(absPath) {
return absPath
}
}
return f.rootSymlinkEvaluated
return f.root
}
func isMaybeWin83(absPath string) bool {

View File

@@ -106,7 +106,7 @@ func (l *logger) callHandlers(level LogLevel, s string) {
// Debugln logs a line with a DEBUG prefix.
func (l *logger) Debugln(vals ...interface{}) {
l.debugln(3, vals)
l.debugln(3, vals...)
}
func (l *logger) debugln(level int, vals ...interface{}) {
s := fmt.Sprintln(vals...)

View File

@@ -27,6 +27,7 @@ var errWatchNotStarted = errors.New("not started")
type folder struct {
stateTracker
config.FolderConfiguration
localFlags uint32
model *Model
shortID protocol.ShortID
@@ -175,6 +176,8 @@ func (f *folder) BringToFront(string) {}
func (f *folder) Override(fs *db.FileSet, updateFn func([]protocol.FileInfo)) {}
func (f *folder) Revert(fs *db.FileSet, updateFn func([]protocol.FileInfo)) {}
func (f *folder) DelayScan(next time.Duration) {
f.Delay(next)
}
@@ -263,7 +266,7 @@ func (f *folder) getHealthError() error {
}
func (f *folder) scanSubdirs(subDirs []string) error {
if err := f.model.internalScanFolderSubdirs(f.ctx, f.folderID, subDirs); err != nil {
if err := f.model.internalScanFolderSubdirs(f.ctx, f.folderID, subDirs, f.localFlags); err != nil {
// Potentially sets the error twice, once in the scanner just
// by doing a check, and once here, if the error returned is
// the same one as returned by CheckHealth, though

View File

@@ -0,0 +1,210 @@
// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package model
import (
"sort"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/versioner"
)
func init() {
folderFactories[config.FolderTypeReceiveOnly] = newReceiveOnlyFolder
}
/*
receiveOnlyFolder is a folder that does not propagate local changes outward.
It does this by the following general mechanism (not all of which is
implemted in this file):
- Local changes are scanned and versioned as usual, but get the
FlagLocalReceiveOnly bit set.
- When changes are sent to the cluster this bit gets converted to the
Invalid bit (like all other local flags, currently) and also the Version
gets set to the empty version. The reason for clearing the Version is to
ensure that other devices will not consider themselves out of date due to
our change.
- The database layer accounts sizes per flag bit, so we can know how many
files have been changed locally. We use this to trigger a "Revert" option
on the folder when the amount of locally changed data is nonzero.
- To revert we take the files which have changed and reset their version
counter down to zero. The next pull will replace our changed version with
the globally latest. As this is a user-initiated operation we do not cause
conflict copies when reverting.
- When pulling normally (i.e., not in the revert case) with local changes,
normal conflict resolution will apply. Conflict copies will be created,
but not propagated outwards (because receive only, right).
Implementation wise a receiveOnlyFolder is just a sendReceiveFolder that
sets an extra bit on local changes and has a Revert method.
*/
type receiveOnlyFolder struct {
*sendReceiveFolder
}
func newReceiveOnlyFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Versioner, fs fs.Filesystem) service {
sr := newSendReceiveFolder(model, cfg, ver, fs).(*sendReceiveFolder)
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
return &receiveOnlyFolder{sr}
}
func (f *receiveOnlyFolder) Revert(fs *db.FileSet, updateFn func([]protocol.FileInfo)) {
f.setState(FolderScanning)
defer f.setState(FolderIdle)
// XXX: This *really* should be given to us in the constructor...
f.model.fmut.RLock()
ignores := f.model.folderIgnores[f.folderID]
f.model.fmut.RUnlock()
delQueue := &deleteQueue{
handler: f, // for the deleteFile and deleteDir methods
ignores: ignores,
}
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
fs.WithHave(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
fi := intf.(protocol.FileInfo)
if !fi.IsReceiveOnlyChanged() {
// We're only interested in files that have changed locally in
// receive only mode.
return true
}
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.
// We'll delete files directly, directories get queued and
// handled below.
handled, err := delQueue.handle(fi)
if err != nil {
l.Infof("Revert: deleting %s: %v\n", fi.Name, err)
return true // continue
}
if !handled {
return true // continue
}
fi = protocol.FileInfo{
Name: fi.Name,
Type: fi.Type,
ModifiedS: fi.ModifiedS,
ModifiedNs: fi.ModifiedNs,
ModifiedBy: f.shortID,
Deleted: true,
Version: protocol.Vector{}, // if this file ever resurfaces anywhere we want our delete to be strictly older
}
} else {
// Revert means to throw away our local changes. We reset the
// version to the empty vector, which is strictly older than any
// other existing version. It is not in conflict with anything,
// 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)
batchSizeBytes += fi.ProtoSize()
if len(batch) >= maxBatchSizeFiles || batchSizeBytes >= maxBatchSizeBytes {
updateFn(batch)
batch = batch[:0]
batchSizeBytes = 0
}
return true
})
if len(batch) > 0 {
updateFn(batch)
}
batch = batch[:0]
batchSizeBytes = 0
// Handle any queued directories
deleted, err := delQueue.flush()
if err != nil {
l.Infoln("Revert:", err)
}
now := time.Now()
for _, dir := range deleted {
batch = append(batch, protocol.FileInfo{
Name: dir,
Type: protocol.FileInfoTypeDirectory,
ModifiedS: now.Unix(),
ModifiedBy: f.shortID,
Deleted: true,
Version: protocol.Vector{},
})
}
if len(batch) > 0 {
updateFn(batch)
}
// We will likely have changed our local index, but that won't trigger a
// pull by itself. Make sure we schedule one so that we start
// downloading files.
f.SchedulePull()
}
// deleteQueue handles deletes by delegating to a handler and queuing
// directories for last.
type deleteQueue struct {
handler interface {
deleteFile(file protocol.FileInfo) (dbUpdateJob, error)
deleteDir(dir string, ignores *ignore.Matcher, scanChan chan<- string) error
}
ignores *ignore.Matcher
dirs []string
}
func (q *deleteQueue) handle(fi protocol.FileInfo) (bool, error) {
// Things that are ignored but not marked deletable are not processed.
ign := q.ignores.Match(fi.Name)
if ign.IsIgnored() && !ign.IsDeletable() {
return false, nil
}
// Directories are queued for later processing.
if fi.IsDirectory() {
q.dirs = append(q.dirs, fi.Name)
return false, nil
}
// Kill it.
_, err := q.handler.deleteFile(fi)
return true, err
}
func (q *deleteQueue) flush() ([]string, error) {
// Process directories from the leaves inward.
sort.Sort(sort.Reverse(sort.StringSlice(q.dirs)))
var firstError error
var deleted []string
for _, dir := range q.dirs {
if err := q.handler.deleteDir(dir, q.ignores, nil); err == nil {
deleted = append(deleted, dir)
} else if err != nil && firstError == nil {
firstError = err
}
}
return deleted, firstError
}

View File

@@ -0,0 +1,266 @@
// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package model
import (
"bytes"
"context"
"io/ioutil"
"os"
"testing"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
)
func TestRecvOnlyRevertDeletes(t *testing.T) {
// Make sure that we delete extraneous files and directories when we hit
// Revert.
os.RemoveAll("_recvonly")
defer os.RemoveAll("_recvonly")
// Create some test data
os.MkdirAll("_recvonly/.stfolder", 0755)
os.MkdirAll("_recvonly/ignDir", 0755)
os.MkdirAll("_recvonly/unknownDir", 0755)
ioutil.WriteFile("_recvonly/ignDir/ignFile", []byte("hello\n"), 0644)
ioutil.WriteFile("_recvonly/unknownDir/unknownFile", []byte("hello\n"), 0644)
ioutil.WriteFile("_recvonly/.stignore", []byte("ignDir\n"), 0644)
knownFiles := setupKnownFiles(t, []byte("hello\n"))
// Get us a model up and running
m := setupROFolder()
defer m.Stop()
// Send and index update for the known stuff
m.Index(device1, "ro", knownFiles)
m.updateLocalsFromScanning("ro", knownFiles)
size := m.GlobalSize("ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
// Start the folder. This will cause a scan, should discover the other stuff in the folder
m.StartFolder("ro")
m.ScanFolder("ro")
// We should now have two files and two directories.
size = m.GlobalSize("ro")
if size.Files != 2 || size.Directories != 2 {
t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
}
size = m.LocalSize("ro")
if size.Files != 2 || size.Directories != 2 {
t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
}
size = m.ReceiveOnlyChangedSize("ro")
if size.Files+size.Directories == 0 {
t.Fatalf("ROChanged: expected something: %+v", size)
}
// Revert should delete the unknown stuff
m.Revert("ro")
// These should still exist
for _, p := range []string{"_recvonly/knownDir/knownFile", "_recvonly/ignDir/ignFile"} {
_, err := os.Stat(p)
if err != nil {
t.Error("Unexpected error:", err)
}
}
// These should have been removed
for _, p := range []string{"_recvonly/unknownDir", "_recvonly/unknownDir/unknownFile"} {
_, err := os.Stat(p)
if !os.IsNotExist(err) {
t.Error("Unexpected existing thing:", p)
}
}
// We should now have one file and directory again.
size = m.GlobalSize("ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
}
size = m.LocalSize("ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
}
}
func TestRecvOnlyRevertNeeds(t *testing.T) {
// Make sure that a new file gets picked up and considered latest, then
// gets considered old when we hit Revert.
os.RemoveAll("_recvonly")
defer os.RemoveAll("_recvonly")
// Create some test data
os.MkdirAll("_recvonly/.stfolder", 0755)
oldData := []byte("hello\n")
knownFiles := setupKnownFiles(t, oldData)
// Get us a model up and running
m := setupROFolder()
defer m.Stop()
// Send and index update for the known stuff
m.Index(device1, "ro", knownFiles)
m.updateLocalsFromScanning("ro", knownFiles)
// Start the folder. This will cause a scan.
m.StartFolder("ro")
m.ScanFolder("ro")
// Everything should be in sync.
size := m.GlobalSize("ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
}
size = m.LocalSize("ro")
if size.Files != 1 || size.Directories != 1 {
t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
}
size = m.NeedSize("ro")
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = m.ReceiveOnlyChangedSize("ro")
if size.Files+size.Directories > 0 {
t.Fatalf("ROChanged: expected nothing: %+v", size)
}
// Update the file.
newData := []byte("totally different data\n")
if err := ioutil.WriteFile("_recvonly/knownDir/knownFile", newData, 0644); err != nil {
t.Fatal(err)
}
// Rescan.
if err := m.ScanFolder("ro"); err != nil {
t.Fatal(err)
}
// We now have a newer file than the rest of the cluster. Global state should reflect this.
size = m.GlobalSize("ro")
const sizeOfDir = 128
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
t.Fatalf("Global: expected the new file to be reflected: %+v", size)
}
size = m.LocalSize("ro")
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
t.Fatalf("Local: expected the new file to be reflected: %+v", size)
}
size = m.NeedSize("ro")
if size.Files+size.Directories > 0 {
t.Fatalf("Need: expected nothing: %+v", size)
}
size = m.ReceiveOnlyChangedSize("ro")
if size.Files+size.Directories == 0 {
t.Fatalf("ROChanged: expected something: %+v", size)
}
// We hit the Revert button. The file that was new should become old.
m.Revert("ro")
size = m.GlobalSize("ro")
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
t.Fatalf("Global: expected the global size to revert: %+v", size)
}
size = m.LocalSize("ro")
if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
t.Fatalf("Local: expected the local size to remain: %+v", size)
}
size = m.NeedSize("ro")
if size.Files != 1 || size.Bytes != int64(len(oldData)) {
t.Fatalf("Local: expected to need the old file data: %+v", size)
}
}
func setupKnownFiles(t *testing.T, data []byte) []protocol.FileInfo {
if err := os.MkdirAll("_recvonly/knownDir", 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile("_recvonly/knownDir/knownFile", data, 0644); err != nil {
t.Fatal(err)
}
t0 := time.Now().Add(-1 * time.Minute)
if err := os.Chtimes("_recvonly/knownDir/knownFile", t0, t0); err != nil {
t.Fatal(err)
}
fi, err := os.Stat("_recvonly/knownDir/knownFile")
if err != nil {
t.Fatal(err)
}
blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true)
knownFiles := []protocol.FileInfo{
{
Name: "knownDir",
Type: protocol.FileInfoTypeDirectory,
Permissions: 0755,
Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
Sequence: 42,
},
{
Name: "knownDir/knownFile",
Type: protocol.FileInfoTypeFile,
Permissions: 0644,
Size: fi.Size(),
ModifiedS: fi.ModTime().Unix(),
ModifiedNs: int32(fi.ModTime().UnixNano() % 1e9),
Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
Sequence: 42,
Blocks: blocks,
},
}
return knownFiles
}
func setupROFolder() *Model {
fcfg := config.NewFolderConfiguration(protocol.LocalDeviceID, "ro", "receive only test", fs.FilesystemTypeBasic, "_recvonly")
fcfg.Type = config.FolderTypeReceiveOnly
fcfg.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
cfg := defaultCfg.Copy()
cfg.Folders = append(cfg.Folders, fcfg)
wrp := config.Wrap("/dev/null", cfg)
db := db.OpenMemory()
m := NewModel(wrp, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
m.ServeBackground()
m.AddFolder(fcfg)
return m
}

View File

@@ -76,7 +76,7 @@ func (f *sendOnlyFolder) pull() bool {
}
file := intf.(protocol.FileInfo)
if !file.IsEquivalent(curFile, f.IgnorePerms, false) {
if !file.IsEquivalentOptional(curFile, f.IgnorePerms, false, 0) {
return true
}

View File

@@ -66,6 +66,7 @@ var (
errDirHasToBeScanned = errors.New("directory contains unexpected files, scheduling scan")
errDirHasIgnored = errors.New("directory contains ignored files (see ignore documentation for (?d) prefix)")
errDirNotEmpty = errors.New("directory is not empty")
errNotAvailable = errors.New("no connected device has the required version of this file")
)
const (
@@ -318,8 +319,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, dbUpdateChan
changed++
case runtime.GOOS == "windows" && fs.WindowsInvalidFilename(file.Name):
f.newError("need", file.Name, fs.ErrInvalidFilename)
changed++
f.newError("pull", file.Name, fs.ErrInvalidFilename)
case file.IsDeleted():
if file.IsDirectory() {
@@ -353,7 +353,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, dbUpdateChan
return true
}
}
l.Debugln(f, "Needed file is unavailable", file)
f.newError("pull", file.Name, errNotAvailable)
case runtime.GOOS == "windows" && file.IsSymlink():
file.SetUnsupported(f.shortID)
@@ -458,6 +458,7 @@ nextFile:
}
if !f.checkParent(fi.Name, scanChan) {
f.queue.Done(fileName)
continue
}
@@ -501,7 +502,11 @@ func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeleti
}
l.Debugln(f, "Deleting file", file.Name)
f.deleteFile(file, dbUpdateChan)
if update, err := f.deleteFile(file); err != nil {
f.newError("delete file", file.Name, err)
} else {
dbUpdateChan <- update
}
}
for i := range dirDeletions {
@@ -736,7 +741,7 @@ func (f *sendReceiveFolder) handleDeleteDir(file protocol.FileInfo, ignores *ign
}
// deleteFile attempts to delete the given file
func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo) (dbUpdateJob, error) {
// Used in the defer closure below, updated by the function body. Take
// care not declare another err.
var err error
@@ -775,16 +780,18 @@ func (f *sendReceiveFolder) deleteFile(file protocol.FileInfo, dbUpdateChan chan
if err == nil || fs.IsNotExist(err) {
// It was removed or it doesn't exist to start with
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
} else if _, serr := f.fs.Lstat(file.Name); serr != nil && !fs.IsPermission(serr) {
return dbUpdateJob{file, dbUpdateDeleteFile}, nil
}
if _, serr := f.fs.Lstat(file.Name); serr != nil && !fs.IsPermission(serr) {
// We get an error just looking at the file, and it's not a permission
// problem. Lets assume the error is in fact some variant of "file
// does not exist" (possibly expressed as some parent being a file and
// not a directory etc) and that the delete is handled.
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
} else {
f.newError("delete file", file.Name, err)
return dbUpdateJob{file, dbUpdateDeleteFile}, nil
}
return dbUpdateJob{}, err
}
// renameFile attempts to rename an existing file to a destination
@@ -1778,10 +1785,14 @@ func (f *sendReceiveFolder) deleteDir(dir string, ignores *ignore.Matcher, scanC
} else if ignores != nil && ignores.Match(fullDirFile).IsIgnored() {
hasIgnored = true
} else if cf, ok := f.model.CurrentFolderFile(f.ID, fullDirFile); !ok || cf.IsDeleted() || cf.IsInvalid() {
// Something appeared in the dir that we either are not
// aware of at all, that we think should be deleted or that
// is invalid, but not currently ignored -> schedule scan
scanChan <- fullDirFile
// Something appeared in the dir that we either are not aware of
// at all, that we think should be deleted or that is invalid,
// but not currently ignored -> schedule scan. The scanChan
// might be nil, in which case we trust the scanning to be
// handled later as a result of our error return.
if scanChan != nil {
scanChan <- fullDirFile
}
hasToBeScanned = true
} else {
// Dir contains file that is valid according to db and

View File

@@ -74,12 +74,12 @@ func setUpFile(filename string, blockNumbers []int) protocol.FileInfo {
}
}
func setUpModel(file protocol.FileInfo) *Model {
func setUpModel(files ...protocol.FileInfo) *Model {
db := db.OpenMemory()
model := NewModel(defaultCfgWrapper, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
model.AddFolder(defaultFolderConfig)
// Update index
model.updateLocalsFromScanning("default", []protocol.FileInfo{file})
model.updateLocalsFromScanning("default", files)
return model
}

View File

@@ -57,6 +57,7 @@ const (
type service interface {
BringToFront(string)
Override(*db.FileSet, func([]protocol.FileInfo))
Revert(*db.FileSet, func([]protocol.FileInfo))
DelayScan(d time.Duration)
IgnoresUpdated() // ignore matcher was updated notification
SchedulePull() // something relevant changed, we should try a pull
@@ -690,6 +691,18 @@ func (m *Model) LocalSize(folder string) db.Counts {
return db.Counts{}
}
// ReceiveOnlyChangedSize returns the number of files, deleted files and
// total bytes for all files that have changed locally in a receieve only
// folder.
func (m *Model) ReceiveOnlyChangedSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
return rf.ReceiveOnlyChangedSize()
}
return db.Counts{}
}
// NeedSize returns the number and total size of currently needed files.
func (m *Model) NeedSize(folder string) db.Counts {
m.fmut.RLock()
@@ -940,12 +953,17 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
if cfg.Paused {
continue
}
fs, ok := m.folderFiles[folder.ID]
if !ok {
// Shouldn't happen because !cfg.Paused, but might happen
// if the folder is about to be unpaused, but not yet.
continue
}
if !folder.DisableTempIndexes {
tempIndexFolders = append(tempIndexFolders, folder.ID)
}
fs := m.folderFiles[folder.ID]
myIndexID := fs.IndexID(protocol.LocalDeviceID)
mySequence := fs.Sequence(protocol.LocalDeviceID)
var startSequence int64
@@ -1289,8 +1307,6 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
return protocol.ErrInvalid
}
m.fmut.RLock()
if cfg, ok := m.cfg.Folder(folder); !ok || !cfg.SharedWith(deviceID) {
l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
return protocol.ErrNoSuchFile
@@ -1299,10 +1315,16 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
return protocol.ErrInvalid
}
folderCfg := m.folderCfgs[folder]
m.fmut.RLock()
folderCfg, ok := m.folderCfgs[folder]
folderIgnores := m.folderIgnores[folder]
m.fmut.RUnlock()
if !ok {
// The folder might be already unpaused in the config, but not yet
// in the model.
l.Debugf("Request from %s for file %s in unstarted folder %q", deviceID, name, folder)
return protocol.ErrInvalid
}
// Make sure the path is valid and in canonical form
var err error
@@ -1743,10 +1765,25 @@ func sendIndexTo(prevSequence int64, conn protocol.Connection, folder string, fs
batchSizeBytes = 0
}
if shouldDebug() {
if fi.SequenceNo() < prevSequence+1 {
panic(fmt.Sprintln("sequence lower than requested, got:", fi.SequenceNo(), ", asked to start at:", prevSequence+1))
}
if f.Sequence > 0 && fi.SequenceNo() <= f.Sequence {
panic(fmt.Sprintln("non-increasing sequence, current:", fi.SequenceNo(), "<= previous:", f.Sequence))
}
}
f = fi.(protocol.FileInfo)
// Mark the file as invalid if any of the local bad stuff flags are set.
f.RawInvalid = f.IsInvalid()
// If the file is marked LocalReceive (i.e., changed locally on a
// receive only folder) we do not want it to ever become the
// globally best version, invalid or not.
if f.IsReceiveOnlyChanged() {
f.Version = protocol.Vector{}
}
f.LocalFlags = 0 // never sent externally
if dropSymlinks && f.IsSymlink() {
@@ -1940,7 +1977,7 @@ func (m *Model) ScanFolderSubdirs(folder string, subs []string) error {
return runner.Scan(subs)
}
func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, subDirs []string) error {
func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, subDirs []string, localFlags uint32) error {
m.fmut.RLock()
if err := m.checkFolderRunningLocked(folder); err != nil {
m.fmut.RUnlock()
@@ -2010,6 +2047,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
ShortID: m.shortID,
ProgressTickIntervalS: folderCfg.ScanProgressIntervalS,
UseLargeBlocks: folderCfg.UseLargeBlocks,
LocalFlags: localFlags,
})
if err := runner.CheckHealth(); err != nil {
@@ -2106,6 +2144,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
ModifiedBy: m.id.Short(),
Deleted: true,
Version: f.Version.Update(m.shortID),
LocalFlags: localFlags,
}
// We do not want to override the global version
// with the deleted file. Keeping only our local
@@ -2289,6 +2328,24 @@ func (m *Model) Override(folder string) {
})
}
func (m *Model) Revert(folder string) {
// Grab the runner and the file set.
m.fmut.RLock()
fs, fsOK := m.folderFiles[folder]
runner, runnerOK := m.folderRunners[folder]
m.fmut.RUnlock()
if !fsOK || !runnerOK {
return
}
// Run the revert, taking updates as if they came from scanning.
runner.Revert(fs, func(files []protocol.FileInfo) {
m.updateLocalsFromScanning(folder, files)
})
}
// CurrentSequence returns the change version for the given folder.
// This is guaranteed to increment if the contents of the local folder has
// changed.

View File

@@ -68,6 +68,10 @@ func init() {
DeviceID: device1,
AutoAcceptFolders: true,
},
{
DeviceID: device2,
AutoAcceptFolders: true,
},
},
Options: config.OptionsConfiguration{
DefaultFolderPath: "testdata",
@@ -161,7 +165,10 @@ func newState(cfg config.Configuration) (*config.Wrapper, *Model) {
}
}
m.ServeBackground()
m.AddConnection(&fakeConnection{id: device1}, protocol.HelloResult{})
for _, dev := range cfg.Devices {
m.AddConnection(&fakeConnection{id: dev.DeviceID}, protocol.HelloResult{})
}
return wcfg, m
}
@@ -1094,6 +1101,35 @@ func TestIssue4897(t *testing.T) {
}
}
func TestIssue5063(t *testing.T) {
wcfg, m := newState(defaultAutoAcceptCfg)
addAndVerify := func(wg *sync.WaitGroup) {
id := srand.String(8)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
os.RemoveAll(filepath.Join("testdata", id))
wg.Done()
if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
}
wg := &sync.WaitGroup{}
for i := 0; i <= 10; i++ {
wg.Add(1)
go addAndVerify(wg)
}
wg.Wait()
}
func TestAutoAcceptRejected(t *testing.T) {
// Nothing happens if AutoAcceptFolders not set
tcfg := defaultAutoAcceptCfg.Copy()
@@ -1135,6 +1171,111 @@ func TestAutoAcceptNewFolder(t *testing.T) {
}
}
func TestAutoAcceptNewFolderFromTwoDevices(t *testing.T) {
wcfg, m := newState(defaultAutoAcceptCfg)
id := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
if fcfg, ok := wcfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected expected shared", id)
}
m.ClusterConfig(device2, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device2) {
t.Error("expected shared", id)
}
m.Stop()
}
func TestAutoAcceptNewFolderFromOnlyOneDevice(t *testing.T) {
modifiedCfg := defaultAutoAcceptCfg.Copy()
modifiedCfg.Devices[2].AutoAcceptFolders = false
wcfg, m := newState(modifiedCfg)
id := srand.String(8)
defer os.RemoveAll(filepath.Join("testdata", id))
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || !fcfg.SharedWith(device1) {
t.Error("expected shared", id)
}
if fcfg, ok := wcfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected expected shared", id)
}
m.ClusterConfig(device2, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: id,
Label: id,
},
},
})
if fcfg, ok := wcfg.Folder(id); !ok || fcfg.SharedWith(device2) {
t.Error("unexpected shared", id)
}
m.Stop()
}
func TestAutoAcceptNewFolderPremutationsNoPanic(t *testing.T) {
if testing.Short() {
t.Skip("short tests only")
}
id := srand.String(8)
label := srand.String(8)
premutations := []protocol.Folder{
{ID: id, Label: id},
{ID: id, Label: label},
{ID: label, Label: id},
{ID: label, Label: label},
}
localFolders := append(premutations, protocol.Folder{})
for _, localFolder := range localFolders {
for _, localFolderPaused := range []bool{false, true} {
for _, dev1folder := range premutations {
for _, dev2folder := range premutations {
cfg := defaultAutoAcceptCfg.Copy()
if localFolder.Label != "" {
fcfg := config.NewFolderConfiguration(protocol.LocalDeviceID, localFolder.ID, localFolder.Label, fs.FilesystemTypeBasic, filepath.Join("testdata", localFolder.ID))
fcfg.Paused = localFolderPaused
cfg.Folders = append(cfg.Folders, fcfg)
}
_, m := newState(cfg)
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{dev1folder},
})
m.ClusterConfig(device2, protocol.ClusterConfig{
Folders: []protocol.Folder{dev2folder},
})
m.Stop()
os.RemoveAll(filepath.Join("testdata", id))
os.RemoveAll(filepath.Join("testdata", label))
}
}
}
}
}
func TestAutoAcceptMultipleFolders(t *testing.T) {
// Multiple new folders
wcfg, m := newState(defaultAutoAcceptCfg)

View File

@@ -49,6 +49,10 @@ func (f FileInfo) IsInvalid() bool {
return f.RawInvalid || f.LocalFlags&LocalInvalidFlags != 0
}
func (f FileInfo) IsUnsupported() bool {
return f.LocalFlags&FlagLocalUnsupported != 0
}
func (f FileInfo) IsIgnored() bool {
return f.LocalFlags&FlagLocalIgnored != 0
}
@@ -57,6 +61,10 @@ func (f FileInfo) MustRescan() bool {
return f.LocalFlags&FlagLocalMustRescan != 0
}
func (f FileInfo) IsReceiveOnlyChanged() bool {
return f.LocalFlags&FlagLocalReceiveOnly != 0
}
func (f FileInfo) IsDirectory() bool {
return f.Type == FileInfoTypeDirectory
}
@@ -99,6 +107,10 @@ func (f FileInfo) FileName() string {
return f.Name
}
func (f FileInfo) FileLocalFlags() uint32 {
return f.LocalFlags
}
func (f FileInfo) ModTime() time.Time {
return time.Unix(f.ModifiedS, int64(f.ModifiedNs))
}
@@ -114,7 +126,7 @@ func (f FileInfo) FileVersion() Vector {
// WinsConflict returns true if "f" is the one to choose when it is in
// conflict with "other".
func (f FileInfo) WinsConflict(other FileInfo) bool {
// If only one of the files is invalid, that one loses
// If only one of the files is invalid, that one loses.
if f.IsInvalid() != other.IsInvalid() {
return !f.IsInvalid()
}
@@ -145,7 +157,15 @@ func (f FileInfo) IsEmpty() bool {
return f.Version.Counters == nil
}
// IsEquivalent checks that the two file infos represent the same actual file content,
func (f FileInfo) IsEquivalent(other FileInfo) bool {
return f.isEquivalent(other, false, false, 0)
}
func (f FileInfo) IsEquivalentOptional(other FileInfo, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
return f.isEquivalent(other, ignorePerms, ignoreBlocks, ignoreFlags)
}
// isEquivalent checks that the two file infos represent the same actual file content,
// i.e. it does purposely not check only selected (see below) struct members.
// Permissions (config) and blocks (scanning) can be excluded from the comparison.
// Any file info is not "equivalent", if it has different
@@ -160,7 +180,7 @@ func (f FileInfo) IsEmpty() bool {
// A symlink is not "equivalent", if it has different
// - target
// A directory does not have anything specific to check.
func (f FileInfo) IsEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bool) bool {
func (f FileInfo) isEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
if f.MustRescan() || other.MustRescan() {
// These are per definition not equivalent because they don't
// represent a valid state, even if both happen to have the
@@ -168,6 +188,10 @@ func (f FileInfo) IsEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bo
return false
}
// Mask out the ignored local flags before checking IsInvalid() below
f.LocalFlags &^= ignoreFlags
other.LocalFlags &^= ignoreFlags
if f.Name != other.Name || f.Type != other.Type || f.Deleted != other.Deleted || f.IsInvalid() != other.IsInvalid() {
return false
}

View File

@@ -19,10 +19,18 @@ type DeviceID [DeviceIDLength]byte
type ShortID uint64
var (
LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
EmptyDeviceID = DeviceID{ /* all zeroes */ }
LocalDeviceID = repeatedDeviceID(0xff)
GlobalDeviceID = repeatedDeviceID(0xf8)
EmptyDeviceID = DeviceID{ /* all zeroes */ }
)
func repeatedDeviceID(v byte) (d DeviceID) {
for i := range d {
d[i] = v
}
return
}
// NewDeviceID generates a new device ID from the raw bytes of a certificate
func NewDeviceID(rawCert []byte) DeviceID {
var n DeviceID

View File

@@ -94,14 +94,15 @@ const (
FlagLocalUnsupported = 1 << 0 // The kind is unsupported, e.g. symlinks on Windows
FlagLocalIgnored = 1 << 1 // Matches local ignore patterns
FlagLocalMustRescan = 1 << 2 // Doesn't match content on disk, must be rechecked fully
FlagLocalReceiveOnly = 1 << 3 // Change detected on receive only folder
// Flags that should result in the Invalid bit on outgoing updates
LocalInvalidFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan
LocalInvalidFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalMustRescan | FlagLocalReceiveOnly
// Flags that should result in a file being in conflict with its
// successor, due to us not having an up to date picture of its state on
// disk.
LocalConflictFlags = FlagLocalUnsupported | FlagLocalIgnored
LocalConflictFlags = FlagLocalUnsupported | FlagLocalIgnored | FlagLocalReceiveOnly
)
var (

View File

@@ -423,6 +423,7 @@ func TestIsEquivalent(t *testing.T) {
b FileInfo
ignPerms *bool // nil means should not matter, we'll test both variants
ignBlocks *bool
ignFlags uint32
eq bool
}
cases := []testCase{
@@ -491,6 +492,17 @@ func TestIsEquivalent(t *testing.T) {
b: FileInfo{LocalFlags: FlagLocalUnsupported},
eq: true,
},
{
a: FileInfo{LocalFlags: 0},
b: FileInfo{LocalFlags: FlagLocalReceiveOnly},
eq: false,
},
{
a: FileInfo{LocalFlags: 0},
b: FileInfo{LocalFlags: FlagLocalReceiveOnly},
ignFlags: FlagLocalReceiveOnly,
eq: true,
},
// Difference in blocks is not OK
{
@@ -588,10 +600,10 @@ func TestIsEquivalent(t *testing.T) {
continue
}
if res := tc.a.IsEquivalent(tc.b, ignPerms, ignBlocks); res != tc.eq {
if res := tc.a.isEquivalent(tc.b, ignPerms, ignBlocks, tc.ignFlags); res != tc.eq {
t.Errorf("Case %d:\na: %v\nb: %v\na.IsEquivalent(b, %v, %v) => %v, expected %v", i, tc.a, tc.b, ignPerms, ignBlocks, res, tc.eq)
}
if res := tc.b.IsEquivalent(tc.a, ignPerms, ignBlocks); res != tc.eq {
if res := tc.b.isEquivalent(tc.a, ignPerms, ignBlocks, tc.ignFlags); res != tc.eq {
t.Errorf("Case %d:\na: %v\nb: %v\nb.IsEquivalent(a, %v, %v) => %v, expected %v", i, tc.a, tc.b, ignPerms, ignBlocks, res, tc.eq)
}
}

View File

@@ -68,6 +68,8 @@ type Config struct {
ProgressTickIntervalS int
// Whether to use large blocks for large files or the old standard of 128KiB for everything.
UseLargeBlocks bool
// Local flags to set on scanned files
LocalFlags uint32
}
type CurrentFiler interface {
@@ -367,10 +369,11 @@ func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileIn
ModifiedBy: w.ShortID,
Size: info.Size(),
RawBlockSize: int32(blockSize),
LocalFlags: w.LocalFlags,
}
if hasCurFile {
if curFile.IsEquivalent(f, w.IgnorePerms, true) {
if curFile.IsEquivalentOptional(f, w.IgnorePerms, true, w.LocalFlags) {
return nil
}
if curFile.ShouldConflict() {
@@ -407,10 +410,11 @@ func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo,
ModifiedS: info.ModTime().Unix(),
ModifiedNs: int32(info.ModTime().Nanosecond()),
ModifiedBy: w.ShortID,
LocalFlags: w.LocalFlags,
}
if ok {
if cf.IsEquivalent(f, w.IgnorePerms, true) {
if cf.IsEquivalentOptional(f, w.IgnorePerms, true, w.LocalFlags) {
return nil
}
if cf.ShouldConflict() {
@@ -463,10 +467,11 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan pro
NoPermissions: true, // Symlinks don't have permissions of their own
SymlinkTarget: target,
ModifiedBy: w.ShortID,
LocalFlags: w.LocalFlags,
}
if ok {
if cf.IsEquivalent(f, w.IgnorePerms, true) {
if cf.IsEquivalentOptional(f, w.IgnorePerms, true, w.LocalFlags) {
return nil
}
if cf.ShouldConflict() {

View File

@@ -221,8 +221,8 @@ func TestNormalization(t *testing.T) {
// make sure it all gets done. In production, things will be correct
// eventually...
walkDir(testFs, "normalization", nil, nil)
tmp := walkDir(testFs, "normalization", nil, nil)
walkDir(testFs, "normalization", nil, nil, 0)
tmp := walkDir(testFs, "normalization", nil, nil, 0)
files := fileList(tmp).testfiles()
@@ -267,7 +267,7 @@ func TestWalkSymlinkUnix(t *testing.T) {
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks")
for _, path := range []string{".", "link"} {
// Scan it
files := walkDir(fs, path, nil, nil)
files := walkDir(fs, path, nil, nil, 0)
// Verify that we got one symlink and with the correct attributes
if len(files) != 1 {
@@ -300,7 +300,7 @@ func TestWalkSymlinkWindows(t *testing.T) {
for _, path := range []string{".", "link"} {
// Scan it
files := walkDir(fs, path, nil, nil)
files := walkDir(fs, path, nil, nil, 0)
// Verify that we got zero symlinks
if len(files) != 0 {
@@ -329,7 +329,7 @@ func TestWalkRootSymlink(t *testing.T) {
}
// Scan it
files := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, link), ".", nil, nil)
files := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, link), ".", nil, nil, 0)
// Verify that we got two files
if len(files) != 2 {
@@ -353,7 +353,7 @@ func TestBlocksizeHysteresis(t *testing.T) {
current := make(fakeCurrentFiler)
runTest := func(expectedBlockSize int) {
files := walkDir(sf, ".", current, nil)
files := walkDir(sf, ".", current, nil, 0)
if len(files) != 1 {
t.Fatalf("expected one file, not %d", len(files))
}
@@ -407,7 +407,57 @@ func TestBlocksizeHysteresis(t *testing.T) {
runTest(512 << 10)
}
func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.Matcher) []protocol.FileInfo {
func TestWalkReceiveOnly(t *testing.T) {
sf := fs.NewWalkFilesystem(&singleFileFS{
name: "testfile.dat",
filesize: 1024,
})
current := make(fakeCurrentFiler)
// Initial scan, no files in the CurrentFiler. Should pick up the file and
// set the ReceiveOnly flag on it, because that's the flag we give the
// walker to set.
files := walkDir(sf, ".", current, nil, protocol.FlagLocalReceiveOnly)
if len(files) != 1 {
t.Fatal("Should have scanned one file")
}
if files[0].LocalFlags != protocol.FlagLocalReceiveOnly {
t.Fatal("Should have set the ReceiveOnly flag")
}
// Update the CurrentFiler and scan again. It should not return
// anything, because the file has not changed. This verifies that the
// ReceiveOnly flag is properly ignored and doesn't trigger a rescan
// every time.
cur := files[0]
current[cur.Name] = cur
files = walkDir(sf, ".", current, nil, protocol.FlagLocalReceiveOnly)
if len(files) != 0 {
t.Fatal("Should not have scanned anything")
}
// Now pretend the file was previously ignored instead. We should pick up
// the difference in flags and set just the LocalReceive flags.
cur.LocalFlags = protocol.FlagLocalIgnored
current[cur.Name] = cur
files = walkDir(sf, ".", current, nil, protocol.FlagLocalReceiveOnly)
if len(files) != 1 {
t.Fatal("Should have scanned one file")
}
if files[0].LocalFlags != protocol.FlagLocalReceiveOnly {
t.Fatal("Should have set the ReceiveOnly flag")
}
}
func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.Matcher, localFlags uint32) []protocol.FileInfo {
fchan := Walk(context.TODO(), Config{
Filesystem: fs,
Subs: []string{dir},
@@ -416,6 +466,7 @@ func walkDir(fs fs.Filesystem, dir string, cfiler CurrentFiler, matcher *ignore.
UseLargeBlocks: true,
CurrentFiler: cfiler,
Matcher: matcher,
LocalFlags: localFlags,
})
var tmp []protocol.FileInfo
@@ -579,7 +630,7 @@ func TestIssue4799(t *testing.T) {
}
fd.Close()
files := walkDir(fs, "/foo", nil, nil)
files := walkDir(fs, "/foo", nil, nil, 0)
if len(files) != 1 || files[0].Name != "foo" {
t.Error(`Received unexpected file infos when walking "/foo"`, files)
}
@@ -597,7 +648,7 @@ func TestRecurseInclude(t *testing.T) {
t.Fatal(err)
}
files := walkDir(testFs, ".", nil, ignores)
files := walkDir(testFs, ".", nil, ignores, 0)
expected := []string{
filepath.Join("dir1"),

View File

@@ -72,6 +72,15 @@ type upnpRoot struct {
Device upnpDevice `xml:"device"`
}
// UnsupportedDeviceTypeError for unsupported UPnP device types (i.e upnp:rootdevice)
type UnsupportedDeviceTypeError struct {
deviceType string
}
func (e UnsupportedDeviceTypeError) Error() string {
return fmt.Sprintf("Unsupported UPnP device of type %s", e.deviceType)
}
// Discover discovers UPnP InternetGatewayDevices.
// The order in which the devices appear in the results list is not deterministic.
func Discover(renewal, timeout time.Duration) []nat.Device {
@@ -180,7 +189,12 @@ USER-AGENT: syncthing/1.0
}
igds, err := parseResponse(deviceType, resp[:n])
if err != nil {
l.Infoln("UPnP parse:", err)
switch err.(type) {
case *UnsupportedDeviceTypeError:
l.Debugln(err.Error())
default:
l.Infoln("UPnP parse:", err)
}
continue
}
for _, igd := range igds {
@@ -203,7 +217,7 @@ func parseResponse(deviceType string, resp []byte) ([]IGDService, error) {
respondingDeviceType := response.Header.Get("St")
if respondingDeviceType != deviceType {
return nil, errors.New("unrecognized UPnP device of type " + respondingDeviceType)
return nil, &UnsupportedDeviceTypeError{deviceType: respondingDeviceType}
}
deviceDescriptionLocation := response.Header.Get("Location")

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STDISCOSRV" "1" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "STDISCOSRV" "1" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STRELAYSRV" "1" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "STRELAYSRV" "1" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.
@@ -765,10 +765,8 @@ directions.
.UNINDENT
.SS Send Only
.sp
In send\-only mode, a device does not apply any updates from the cluster, but
publishes changes of its local folder to the cluster as usual. The local
folder can be seen as a “master copy” that is never affected by the actions
of other cluster devices.
In send only mode, a device does not apply any updates from the cluster, but
publishes changes of its local folder to the cluster as usual.
.INDENT 0.0
.INDENT 3.5
.sp
@@ -783,6 +781,24 @@ of other cluster devices.
.fi
.UNINDENT
.UNINDENT
.SS Receive Only
.sp
In receive only mode, a device does not send any updates to the cluster, but
accepts changes to its local folder from the cluster as usual.
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
+\-\-\-\-\-\-\-\-\-\-\-\-+ Updates /\-\-\-\-\-\-\-\-\-\e
| | <\-\-\-\-\-\-\-\-\-\-\- / \e
| Device | | Cluster |
| | \e /
+\-\-\-\-\-\-\-\-\-\-\-\-+ \e\-\-\-\-\-\-\-\-\-/
.ft P
.fi
.UNINDENT
.UNINDENT
.SH DELTA INDEX EXCHANGE
.sp
Index data must be exchanged whenever two devices connect so that one knows

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.
@@ -82,7 +82,7 @@ The following shows an example of the default configuration file (IDs will diffe
.nf
.ft C
<configuration version="26">
<folder id="zj2AA\-q55a7" label="Default Folder" path="/Users/jb/Sync/" type="readwrite" rescanIntervalS="60" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
<folder id="zj2AA\-q55a7" label="Default Folder" path="/Users/jb/Sync/" type="sendreceive" rescanIntervalS="60" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
<device id="3LT2GA5\-CQI4XJM\-WTZ264P\-MLOGMHL\-MCRLDNT\-MZV4RD3\-KA745CL\-OGAERQZ"></device>
<filesystemType>basic</filesystemType>
<minDiskFree unit="%">1</minDiskFree>
@@ -210,7 +210,7 @@ logged, but there will be no dialog about it in the web GUI.
.sp
.nf
.ft C
<folder id="zj2AA\-q55a7" label="Default Folder" path="/Users/jb/Sync/" type="readwrite" rescanIntervalS="60" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
<folder id="zj2AA\-q55a7" label="Default Folder" path="/Users/jb/Sync/" type="sendreceive" rescanIntervalS="60" fsWatcherEnabled="false" fsWatcherDelayS="10" ignorePerms="false" autoNormalize="true">
<device id="3LT2GA5\-CQI4XJM\-WTZ264P\-MLOGMHL\-MCRLDNT\-MZV4RD3\-KA745CL\-OGAERQZ"></device>
<filesystemType>basic</filesystemType>
<minDiskFree unit="%">1</minDiskFree>
@@ -255,12 +255,20 @@ device; not sent to other devices. (mandatory)
Controls how the folder is handled by Syncthing. Possible values are:
.INDENT 7.0
.TP
.B readwrite
.B sendreceive
The folder is in default mode. Sending local and accepting remote changes.
Note that this type was previously called “readwrite” which is deprecated
but still accepted in incoming configs.
.TP
.B readonly
The folder is in “send\-only” mode it will not be modified by
.B sendonly
The folder is in “send only” mode it will not be modified by
Syncthing on this device.
Note that this type was previously called “readonly” which is deprecated
but still accepted in incoming configs.
.TP
.B receiveonly
The folder is in “receive only” mode it will not propagate
changes to other devices.
.UNINDENT
.TP
.B rescanIntervalS
@@ -869,10 +877,10 @@ that the files you are backing up are in a folder\-sendonly to prevent other
devices from overwriting the per device configuration. The folder on the remote
device(s) should not be used as configuration for the remote devices.
.sp
If youd like to sync your home folder in non\-send\-only mode, you may add the
If youd like to sync your home folder in non\-send only mode, you may add the
folder that stores the configuration files to the ignore list\&.
If youd also like to backup your configuration files, add another folder in
send\-only mode for just the configuration folder.
send only mode for just the configuration folder.
.SH AUTHOR
The Syncthing Authors
.SH COPYRIGHT

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.
@@ -94,6 +94,7 @@ itself.
.ft C
{
"id": 50,
"globalID": 50,
"type": "ConfigSaved",
"time": "2014\-12\-13T00:09:13.5166486Z",
"data": {
@@ -118,6 +119,7 @@ Generated each time a connection to a device has been established.
.ft C
{
"id": 2,
"globalID": 2,
"type": "DeviceConnected",
"time": "2014\-07\-13T21:04:33.687836696+02:00",
"data": {
@@ -143,6 +145,7 @@ Generated each time a connection to a device has been terminated.
.ft C
{
"id": 48,
"globalID": 48,
"type": "DeviceDisconnected",
"time": "2014\-07\-13T21:18:52.859929215+02:00",
"data": {
@@ -174,6 +177,7 @@ Emitted when a new device is discovered using local discovery.
.ft C
{
"id": 13,
"globalID": 13,
"type": "DeviceDiscovered",
"time": "2014\-07\-17T13:28:05.043465207+02:00",
"data": {
@@ -197,6 +201,7 @@ Emitted when a device was paused.
.ft C
{
"id": 13,
"globalID": 13,
"type": "DevicePaused",
"time": "2014\-07\-17T13:28:05.043465207+02:00",
"data": {
@@ -218,10 +223,12 @@ to talk to.
.ft C
{
"id": 24,
"globalID": 24,
"type": "DeviceRejected",
"time": "2014\-08\-19T10:43:00.562821045+02:00",
"data": {
"address": "127.0.0.1:51807",
"name": "My dusty computer",
"device": "EJHMPAQ\-OGCVORE\-ISB4IS3\-SYYVJXF\-TKJGLTU\-66DIQPF\-GJ5D2GX\-GQ3OWQK"
}
}
@@ -239,6 +246,7 @@ Generated each time a device was resumed.
.ft C
{
"id": 2,
"globalID": 2,
"type": "DeviceResumed",
"time": "2014\-07\-13T21:04:33.687836696+02:00",
"data": {
@@ -261,6 +269,7 @@ configuration can cause multiple files to be shown.
.ft C
{
"id": 221,
"globalID": 221,
"type": "DownloadProgress",
"time": "2014\-12\-13T00:26:12.9876937Z",
"data": {
@@ -356,6 +365,7 @@ device.
.ft C
{
"id": 84,
"globalID": 84,
"type": "FolderCompletion",
"time": "2015\-04\-17T14:14:27.043576583+09:00",
"data": {
@@ -419,6 +429,7 @@ have, or have but do not share with the device in question.
.ft C
{
"id": 27,
"globalID": 27,
"type": "FolderRejected",
"time": "2014\-08\-19T10:41:06.761751399+02:00",
"data": {
@@ -469,6 +480,7 @@ state.
.ft C
{
"id": 16,
"globalID": 16,
"type": "FolderSummary",
"time": "2015\-04\-17T14:12:20.460121585+09:00",
"data": {
@@ -507,6 +519,7 @@ successful operation:
.ft C
{
"id": 93,
"globalID": 93,
"type": "ItemFinished",
"time": "2014\-07\-13T21:22:03.414609034+02:00",
"data": {
@@ -530,6 +543,7 @@ An unsuccessful operation:
.ft C
{
"id": 44,
"globalID": 44,
"type": "ItemFinished",
"time": "2015\-05\-27T11:21:05.711133004+02:00",
"data": {
@@ -559,6 +573,7 @@ Generated when Syncthing begins synchronizing a file to a newer version.
.ft C
{
"id": 93,
"globalID": 93,
"type": "ItemStarted",
"time": "2014\-07\-13T21:22:03.414609034+02:00",
"data": {
@@ -674,6 +689,7 @@ changes during a scan.
.ft C
{
"id": 59,
"globalID": 59,
"type": "LocalIndexUpdated",
"time": "2014\-07\-17T13:27:28.051369434+02:00",
"data": {
@@ -774,6 +790,7 @@ Generated each time new index information is received from a device.
.ft C
{
"id": 44,
"globalID": 44,
"type": "RemoteIndexUpdated",
"time": "2014\-07\-13T21:04:35.394184435+02:00",
"data": {
@@ -797,6 +814,7 @@ configuration etc.
.ft C
{
"id": 1,
"globalID": 1,
"type": "Starting",
"time": "2014\-07\-17T13:13:32.044470055+02:00",
"data": {
@@ -818,6 +836,7 @@ ready to start exchanging data with other devices.
.ft C
{
"id": 1,
"globalID": 1,
"type": "StartupComplete",
"time": "2014\-07\-13T21:03:18.383239179+02:00",
"data": null
@@ -840,6 +859,7 @@ seconds and is now in state \fBidle\fP\&.
.ft C
{
"id": 8,
"globalID": 8,
"type": "StateChanged",
"time": "2014\-07\-17T13:14:28.697493016+02:00",
"data": {

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.
@@ -223,10 +223,11 @@ you know will always exist in the folder.
.SH I REALLY HATE THE .STFOLDER DIRECTORY, CAN I REMOVE IT?
.sp
See the previous question.
.SH AM I ABLE TO USE NESTED SYNCTHING FOLDERS?
.SH AM I ABLE TO NEST SHARED FOLDERS IN SYNCTHING?
.sp
Do not nest shared folders. This behaviour is in no way supported,
recommended or coded for in any way, and comes with many pitfalls.
Do not share a folder which is inside another shared folder. This behaviour
is in no way supported, recommended or coded for in any way, and comes with
many pitfalls.
.SH HOW DO I RENAME/MOVE A SYNCED FOLDER?
.sp
Syncthing doesnt have a direct way to do this, as its potentially

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-RELAY" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.
@@ -103,7 +103,7 @@ Returns the current configuration.
"id": "GXWxf\-3zgnU",
"label": "MyFolder",
"path": "...",
"type": "readwrite",
"type": "sendreceive",
"devices": [
{
"deviceID": "..."
@@ -195,7 +195,8 @@ Returns the current configuration.
"overwriteRemoteDeviceNamesOnConnect": false,
"tempIndexMinBlocks": 10
},
"ignoredDevices": []
"ignoredDevices": [],
"ignoredFolders": []
}
}
.ft P
@@ -486,6 +487,8 @@ $ curl \-X POST \-H "X\-API\-Key: abc123" http://localhost:8384/rest/system/rese
.fi
.UNINDENT
.UNINDENT
.sp
\fBCaution\fP: See \fB\-reset\-database\fP for \fB\&.stfolder\fP creation side\-effect and caution regarding mountpoints.
.SS POST /rest/system/restart
.sp
Post with empty body to immediately restart Syncthing.
@@ -838,7 +841,7 @@ This is an expensive call, increasing CPU and RAM usage on the device. Use spari
.UNINDENT
.SS POST /rest/db/override
.sp
Request override of a send\-only folder.
Request override of a send only folder.
Takes the mandatory parameter \fIfolder\fP (folder ID).
.INDENT 0.0
.INDENT 3.5
@@ -1074,7 +1077,7 @@ Returns the data sent in the anonymous usage report.
"folderUses" : {
"ignorePerms" : 0,
"autoNormalize" : 0,
"readonly" : 0,
"sendonly" : 0,
"ignoreDelete" : 0
},
"memoryUsageMiB" : 13,

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-SECURITY" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-STIGNORE" "5" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-VERSIONING" "7" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.

View File

@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING" "1" "Jun 21, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING" "1" "Jul 28, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing \- Syncthing
.

View File

@@ -1,150 +0,0 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// Checks for authors that are not mentioned in AUTHORS
package meta
import (
"bytes"
"io/ioutil"
"os/exec"
"regexp"
"strings"
"testing"
)
// list of commits that we don't include in our checks; because they are
// legacy things that don't check code, are committed with incorrect address,
// or for other reasons.
var excludeCommits = stringSetFromStrings([]string{
"63bd0136fb40a91efaa279cb4b4159d82e8e6904",
"4e2feb6fbc791bb8a2daf0ab8efb10775d66343e",
"f2459ef3319b2f060dbcdacd0c35a1788a94b8bd",
"b61f418bf2d1f7d5a9d7088a20a2a448e5e66801",
"a9339d0627fff439879d157c75077f02c9fac61b",
"254c63763a3ad42fd82259f1767db526cff94a14",
"4b76ec40c07078beaa2c5e250ed7d9bd6276a718",
"32a76901a91ff0f663db6f0830e0aedec946e4d0",
"3626003f680bad3e63677982576d3a05421e88e9",
"342036408e65bd25bb6afbcc705e2e2c013bb01f",
"e37cefdbee1c1cd95ad095b5da6d1252723f103b",
"bcc5d7c00f52552303b463d43a636f27b7f7e19b",
"bc7639b0ffcea52b2197efb1c0bb68b338d1c915",
})
func TestCheckAuthors(t *testing.T) {
if testing.Short() {
t.Skip("skipping slow test")
}
actual, hashes := actualAuthorEmails(t, ".", "../cmd/", "../lib/", "../gui/", "../test/", "../script/")
listed := listedAuthorEmails(t)
missing := actual.except(listed)
for author := range missing {
t.Logf("Missing author: %s", author)
for _, hash := range hashes[author] {
t.Logf(" in hash: %s", hash)
}
}
if len(missing) > 0 {
t.Errorf("Missing %d author(s)", len(missing))
}
}
// actualAuthorEmails returns the set of author emails found in the actual git
// commit log, except those in excluded commits.
func actualAuthorEmails(t *testing.T, paths ...string) (stringSet, map[string][]string) {
args := append([]string{"log", "--format=%H %ae"}, paths...)
cmd := exec.Command("git", args...)
bs, err := cmd.Output()
if err != nil {
t.Fatal("authorEmails:", err)
}
hashes := make(map[string][]string)
authors := newStringSet()
for _, line := range bytes.Split(bs, []byte{'\n'}) {
fields := strings.Fields(string(line))
if len(fields) != 2 {
continue
}
hash, author := fields[0], fields[1]
if excludeCommits.has(hash) {
continue
}
if strings.Contains(strings.ToLower(body(t, hash)), "skip-check: authors") {
continue
}
authors.add(author)
hashes[author] = append(hashes[author], hash)
}
return authors, hashes
}
// listedAuthorEmails returns the set of author emails mentioned in AUTHORS
func listedAuthorEmails(t *testing.T) stringSet {
bs, err := ioutil.ReadFile("../AUTHORS")
if err != nil {
t.Fatal("listedAuthorEmails:", err)
}
emailRe := regexp.MustCompile(`<([^>]+)>`)
matches := emailRe.FindAllStringSubmatch(string(bs), -1)
authors := newStringSet()
for _, match := range matches {
authors.add(match[1])
}
return authors
}
func body(t *testing.T, hash string) string {
cmd := exec.Command("git", "show", "--pretty=format:%b", "-s", hash)
bs, err := cmd.Output()
if err != nil {
t.Fatal("body:", err)
}
return string(bs)
}
// A simple string set type
type stringSet map[string]struct{}
func newStringSet() stringSet {
return make(stringSet)
}
func stringSetFromStrings(ss []string) stringSet {
s := newStringSet()
for _, e := range ss {
s.add(e)
}
return s
}
func (s stringSet) add(e string) {
s[e] = struct{}{}
}
func (s stringSet) has(e string) bool {
_, ok := s[e]
return ok
}
func (s stringSet) except(other stringSet) stringSet {
diff := newStringSet()
for e := range s {
if !other.has(e) {
diff.add(e)
}
}
return diff
}

View File

@@ -32,12 +32,18 @@ var (
)
const authorsHeader = `# This is the official list of Syncthing authors for copyright purposes.
# The format is:
#
# THIS FILE IS MOSTLY AUTO GENERATED. IF YOU'VE MADE A COMMIT TO THE
# REPOSITORY YOU WILL BE ADDED HERE AUTOMATICALLY WITHOUT THE NEED FOR
# ANY MANUAL ACTION.
#
# That said, you are welcome to correct your name or add a nickname / GitHub
# user name as appropriate. The format is:
#
# Name Name Name (nickname) <email1@example.com> <email2@example.com>
#
# After changing this list, run "go run script/authors.go" to sort and update
# the GUI HTML.
# The in-GUI authors list is periodically automatically updated from the
# contents of this file.
#
`
@@ -50,8 +56,42 @@ type author struct {
}
func main() {
// Read authors from the AUTHORS file
authors := getAuthors()
// Grab the set of thus known email addresses
listed := make(stringSet)
names := make(map[string]int)
for i, a := range authors {
names[a.name] = i
for _, e := range a.emails {
listed.add(e)
}
}
// Grab the set of all known authors based on the git log, and add any
// missing ones to the authors list.
all := allAuthors()
for email, name := range all {
if listed.has(email) {
continue
}
if _, ok := names[name]; ok && name != "" {
// We found a match on name
authors[names[name]].emails = append(authors[names[name]].emails, email)
listed.add(email)
continue
}
authors = append(authors, author{
name: name,
emails: []string{email},
})
names[name] = len(authors) - 1
listed.add(email)
}
// Write author names in GUI about modal
getContributions(authors)
@@ -167,6 +207,46 @@ next:
}
}
// list of commits that we don't include in our author file; because they
// are legacy things that don't affect code, are committed with incorrect
// address, or for other reasons.
var excludeCommits = stringSetFromStrings([]string{
"a9339d0627fff439879d157c75077f02c9fac61b",
"254c63763a3ad42fd82259f1767db526cff94a14",
"32a76901a91ff0f663db6f0830e0aedec946e4d0",
"bc7639b0ffcea52b2197efb1c0bb68b338d1c915",
})
// allAuthors returns the set of authors in the git commit log, except those
// in excluded commits.
func allAuthors() map[string]string {
args := append([]string{"log", "--format=%H %ae %an"})
cmd := exec.Command("git", args...)
bs, err := cmd.Output()
if err != nil {
log.Fatal("git:", err)
}
names := make(map[string]string)
for _, line := range bytes.Split(bs, []byte{'\n'}) {
fields := strings.SplitN(string(line), " ", 3)
if len(fields) != 3 {
continue
}
hash, email, name := fields[0], fields[1], fields[2]
if excludeCommits.has(hash) {
continue
}
if names[email] == "" {
names[email] = name
}
}
return names
}
type byContributions []author
func (l byContributions) Len() int { return len(l) }
@@ -194,3 +274,34 @@ func (l byName) Less(a, b int) bool {
}
func (l byName) Swap(a, b int) { l[a], l[b] = l[b], l[a] }
// A simple string set type
type stringSet map[string]struct{}
func stringSetFromStrings(ss []string) stringSet {
s := make(stringSet)
for _, e := range ss {
s.add(e)
}
return s
}
func (s stringSet) add(e string) {
s[e] = struct{}{}
}
func (s stringSet) has(e string) bool {
_, ok := s[e]
return ok
}
func (s stringSet) except(other stringSet) stringSet {
diff := make(stringSet)
for e := range s {
if !other.has(e) {
diff.add(e)
}
}
return diff
}

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