Compare commits

...

40 Commits

Author SHA1 Message Date
Jakob Borg
962b917150 build: handle (ignore) new docker artifacts 2025-12-23 09:10:48 +01:00
Jakob Borg
f57e92c20a chore: tweak pull retry logic (#10491)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-12-23 08:26:58 +01:00
Jakob Borg
b9ab05af02 build: fix hash failure by limiting globbing (#10505)
The glob in **/go.sum fails in some builds because there are a lot of files in ** due to things like the zig cache directory. We can be more specific. Also, avoid a huge build context sent to Docker for the container builds.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-12-22 19:28:16 +00:00
Syncthing Release Automation
43d826913f chore(gui, man, authors): update docs, translations, and contributors 2025-12-22 04:06:33 +00:00
Marcus B Spencer
801ef0e22d fix(beacon): don't join multicast groups on non-multicast interfaces (fixes #10497) (#10498)
fix(beacon): don't join multicast groups on non-multicast interfaces

Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
2025-12-18 08:58:06 +01:00
Marcus B Spencer
e5dfd2c549 chore(beacon): more verbose debug logging (#10496)
Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
2025-12-17 15:04:47 +00:00
Syncthing Release Automation
5800d1acc3 chore(gui, man, authors): update docs, translations, and contributors 2025-12-15 04:06:28 +00:00
Jakob Borg
fd9dcbb8c2 build: fix docker build by ensuring qemu (#10492)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-12-13 12:28:12 +00:00
Syncthing Release Automation
bc7e56fdcd chore(gui, man, authors): update docs, translations, and contributors 2025-12-08 04:02:18 +00:00
Jakob Borg
7f7f5d87df Merge branch 'infrastructure'
* infrastructure:
  chore(stdiscosrv): use log/slog
  chore(stdiscosrv): larger write buffer
2025-12-02 08:43:15 +01:00
Syncthing Release Automation
49f2736adb chore(gui, man, authors): update docs, translations, and contributors 2025-12-01 04:08:13 +00:00
Jakob Borg
cde867cf74 chore(stdiscosrv): use log/slog
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-11-30 11:17:29 +01:00
Jakob Borg
70292b4902 chore(stdiscosrv): larger write buffer
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-11-30 11:17:29 +01:00
Jakob Borg
553c02f244 chore(model): refactor context handling for folder type (#10472)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-11-27 20:34:35 +00:00
André Colomb
ce884e5d72 chore(cli): clean up generated usage strings for config commands (fixes #10462) (#10463)
* Show proper subcommand prefix in generated config CLI.
* Remove useless author info and copy command group description.
* Really accept (implicit) -h and --help flags.

These were disabled by HideHelp, leading to an error message in every
usage output.  This way, the flags get documented as well.

* Override AppHelpTemplate to better match Kong's style.
* Override (Sub)commandHelpTemplate to better match Kong's style.
* Use <command> and [flags] like Kong.

Signed-off-by: André Colomb <src@andre.colomb.de>
2025-11-24 16:49:42 +01:00
Syncthing Release Automation
5f702c1406 chore(gui, man, authors): update docs, translations, and contributors 2025-11-24 04:02:55 +00:00
Syncthing Release Automation
a6bcd02739 chore(gui, man, authors): update docs, translations, and contributors 2025-11-17 03:57:56 +00:00
Jakob Borg
488c33aef5 chore: update quic-go, adapt to lack of write tracking (#10456)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-11-16 00:48:28 +01:00
Syncthing Release Automation
9241a475e9 chore(gui, man, authors): update docs, translations, and contributors 2025-11-10 03:59:12 +00:00
Syncthing Release Automation
01eef47bbc chore(gui, man, authors): update docs, translations, and contributors 2025-11-03 03:59:21 +00:00
Jakob Borg
c518d99c35 build: do not clobber .deb files when publishing 2025-10-31 11:01:09 +01:00
Syncthing Release Automation
81c99e07db chore(gui, man, authors): update docs, translations, and contributors 2025-10-27 03:59:48 +00:00
André Colomb
5279330c1d chore(gui): add Azerbaijani (az) and Kurdish (ckb) l10n templates. (#10442)
Based on user requests from Weblate:

* `@miryusifrahimov` for Azerbaijani
* `@halbast` für Kurdish (Central)

Both seem to be legit and have previously contributed translations on
Weblate.

Signed-off-by: André Colomb <src@andre.colomb.de>
2025-10-26 17:55:03 +01:00
Jakob Borg
194b59b3ed chore: job for adding org members 2025-10-24 08:10:30 +02:00
Jakob Borg
8e796ddb94 chore: linter: errorlint
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
7c9d06b4d2 chore: linter: embeddedstructfieldcheck
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
df8d8c276e chore: linter: staticcheck
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
98cf5872e9 chore: linter: perfsprint
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
c883f49a24 chore: linter: usestdlibvars
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
d84280107c chore: linter: canonicalheader
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
d97fd638bc chore: linter: dupword
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
a1069a0d70 chore: linter: intrange
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
465804161b chore: linter: staticcheck
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Jakob Borg
d08f483811 chore: linter: unused
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2025-10-23 22:48:54 +02:00
Marcus B Spencer
655b4568c1 fix(fs): only apply case option to fakefs in stress test (#10440)
Fixes a regression introduced in #10439, mentioned in [a comment](https://github.com/syncthing/syncthing/pull/10439#issuecomment-3436515824) made by @imsodin:

> That might not be the greatest way to do this, but nevertheless it afaik means that the benchmarks now do case checking once when it shouldn't happen at all, and twice otherwise.

Benchmarks do approximately as well as before the regression, and I think most of them are random chance:

```
                                             │ ../oldold.txt │             ../new.txt             │
                                             │    sec/op     │   sec/op     vs base               │
WalkCaseFakeFS100k/rawfs-8                       654.6m ± 1%   652.6m ± 3%       ~ (p=0.971 n=10)
WalkCaseFakeFS100k/casefs-8                       1.049 ± 2%    1.071 ± 3%       ~ (p=0.190 n=10)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8      1.053 ± 3%    1.081 ± 5%       ~ (p=0.165 n=10)
geomean                                          897.7m        910.8m       +1.46%

                                             │ ../oldold.txt │              ../new.txt               │
                                             │    B/entry    │   B/entry     vs base                 │
WalkCaseFakeFS100k/rawfs-8                      1.274Ki ± 0%   1.274Ki ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-8                     1.771Ki ± 0%   1.771Ki ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8    1.772Ki ± 0%   1.772Ki ± 0%       ~ (p=1.000 n=10) ¹
geomean                                         1.587Ki        1.587Ki       +0.00%
¹ all samples are equal

                                             │ ../oldold.txt  │               ../new.txt                │
                                             │ DirNames/entry │ DirNames/entry  vs base                 │
WalkCaseFakeFS100k/rawfs-8                        512.5m ± 0%      512.5m ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-8                        1.025 ± 0%       1.025 ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8       1.025 ± 0%       1.025 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                           813.5m           813.5m       +0.00%
¹ all samples are equal

                                             │ ../oldold.txt │              ../new.txt              │
                                             │  DirNames/op  │ DirNames/op  vs base                 │
WalkCaseFakeFS100k/rawfs-8                       51.25k ± 0%   51.25k ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-8                      102.5k ± 0%   102.5k ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8     102.5k ± 0%   102.5k ± 0%       ~ (p=1.000 n=10) ¹
geomean                                          81.35k        81.35k       +0.00%
¹ all samples are equal

                                             │ ../oldold.txt │              ../new.txt              │
                                             │  Lstat/entry  │ Lstat/entry  vs base                 │
WalkCaseFakeFS100k/rawfs-8                        5.535 ± 0%    5.535 ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-8                       5.535 ± 0%    5.535 ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8      5.540 ± 0%    5.540 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                           5.537         5.537       +0.00%
¹ all samples are equal

                                             │ ../oldold.txt │              ../new.txt              │
                                             │   Lstat/op    │  Lstat/op    vs base                 │
WalkCaseFakeFS100k/rawfs-8                       553.5k ± 0%   553.5k ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-8                      553.5k ± 0%   553.5k ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8     554.0k ± 0%   554.0k ± 0%       ~ (p=1.000 n=10) ¹
geomean                                          553.7k        553.7k       +0.00%
¹ all samples are equal

                                             │ ../oldold.txt │              ../new.txt               │
                                             │ allocs/entry  │ allocs/entry  vs base                 │
WalkCaseFakeFS100k/rawfs-8                        19.00 ± 0%     19.00 ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-8                       35.35 ± 0%     35.35 ± 0%       ~ (p=1.000 n=10) ¹
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8      35.38 ± 0%     35.38 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                           28.75          28.75       +0.00%
¹ all samples are equal

                                             │ ../oldold.txt │             ../new.txt             │
                                             │   sec/entry   │  sec/entry   vs base               │
WalkCaseFakeFS100k/rawfs-8                       4.328µ ± 1%   4.315µ ± 3%       ~ (p=0.971 n=10)
WalkCaseFakeFS100k/casefs-8                      6.936µ ± 2%   7.082µ ± 3%       ~ (p=0.171 n=10)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8     6.965µ ± 3%   7.147µ ± 5%       ~ (p=0.165 n=10)
geomean                                          5.935µ        6.022µ       +1.46%

                                             │ ../oldold.txt │             ../new.txt              │
                                             │     B/op      │     B/op      vs base               │
WalkCaseFakeFS100k/rawfs-8                      188.3Mi ± 0%   188.3Mi ± 0%  -0.00% (p=0.006 n=10)
WalkCaseFakeFS100k/casefs-8                     261.5Mi ± 0%   261.5Mi ± 0%       ~ (p=0.142 n=10)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8    261.7Mi ± 0%   261.7Mi ± 0%       ~ (p=0.315 n=10)
geomean                                         234.4Mi        234.4Mi       -0.00%

                                             │ ../oldold.txt │             ../new.txt             │
                                             │   allocs/op   │  allocs/op   vs base               │
WalkCaseFakeFS100k/rawfs-8                       2.873M ± 0%   2.873M ± 0%  -0.00% (p=0.026 n=10)
WalkCaseFakeFS100k/casefs-8                      5.346M ± 0%   5.346M ± 0%       ~ (p=0.136 n=10)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8     5.351M ± 0%   5.351M ± 0%       ~ (p=0.305 n=10)
geomean                                          4.348M        4.348M       -0.00%
```

Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
2025-10-23 13:12:03 -05:00
Marcus B Spencer
c6a887865f fix(fs): apply case option to fakefs in casefs tests (#10439)
Required for the casefs tests/benchmarks to test the casefs.

Benchmarks do significantly worse (as expected).

```
                                             │ ../old.txt  │             ../new.txt             │
                                             │   sec/op    │   sec/op     vs base               │
WalkCaseFakeFS100k/rawfs-8                     626.5m ± 5%   993.4m ± 1%  +58.56% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                     1.011 ± 1%    1.425 ± 1%  +40.94% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8    1.014 ± 2%    1.439 ± 1%  +41.97% (p=0.002 n=6)
geomean                                        862.9m         1.268       +46.94%

                                             │  ../old.txt  │             ../new.txt              │
                                             │   B/entry    │   B/entry     vs base               │
WalkCaseFakeFS100k/rawfs-8                     1.274Ki ± 0%   1.766Ki ± 0%  +38.54% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                    1.771Ki ± 0%   2.354Ki ± 0%  +32.98% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8   1.772Ki ± 0%   2.356Ki ± 0%  +32.95% (p=0.002 n=6)
geomean                                        1.587Ki        2.140Ki       +34.80%

                                             │   ../old.txt   │               ../new.txt               │
                                             │ DirNames/entry │ DirNames/entry  vs base                │
WalkCaseFakeFS100k/rawfs-8                        512.5m ± 0%     1025.0m ± 0%  +100.00% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                        1.025 ± 0%       1.537 ± 0%   +49.95% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8       1.025 ± 0%       1.537 ± 0%   +49.95% (p=0.002 n=6)
geomean                                           813.5m            1.343        +65.06%

                                             │ ../old.txt  │              ../new.txt              │
                                             │ DirNames/op │ DirNames/op   vs base                │
WalkCaseFakeFS100k/rawfs-8                     51.25k ± 0%   102.49k ± 0%  +100.00% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                    102.5k ± 0%    153.7k ± 0%   +50.00% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8   102.5k ± 0%    153.7k ± 0%   +50.00% (p=0.002 n=6)
geomean                                        81.35k         134.3k        +65.10%

                                             │  ../old.txt  │             ../new.txt              │
                                             │ allocs/entry │ allocs/entry  vs base               │
WalkCaseFakeFS100k/rawfs-8                       19.00 ± 0%     35.35 ± 0%  +86.05% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                      35.35 ± 0%     54.40 ± 0%  +53.89% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8     35.38 ± 0%     54.46 ± 0%  +53.93% (p=0.002 n=6)
geomean                                          28.75          47.14       +63.95%

                                             │ ../old.txt  │             ../new.txt             │
                                             │  sec/entry  │  sec/entry   vs base               │
WalkCaseFakeFS100k/rawfs-8                     4.143µ ± 5%   6.568µ ± 1%  +58.55% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                    6.686µ ± 1%   9.424µ ± 1%  +40.95% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8   6.703µ ± 2%   9.517µ ± 1%  +41.97% (p=0.002 n=6)
geomean                                        5.705µ        8.383µ       +46.94%

                                             │  ../old.txt  │             ../new.txt              │
                                             │     B/op     │     B/op      vs base               │
WalkCaseFakeFS100k/rawfs-8                     188.3Mi ± 0%   260.8Mi ± 0%  +38.51% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                    261.5Mi ± 0%   347.7Mi ± 0%  +32.98% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8   261.7Mi ± 0%   348.0Mi ± 0%  +32.96% (p=0.002 n=6)
geomean                                        234.4Mi        316.0Mi       +34.79%

                                             │ ../old.txt  │             ../new.txt             │
                                             │  allocs/op  │  allocs/op   vs base               │
WalkCaseFakeFS100k/rawfs-8                     2.873M ± 0%   5.346M ± 0%  +86.04% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-8                    5.346M ± 0%   8.228M ± 0%  +53.91% (p=0.002 n=6)
WalkCaseFakeFS100k/casefs-otherOpEvery1000-8   5.351M ± 0%   8.236M ± 0%  +53.92% (p=0.002 n=6)
geomean                                        4.348M        7.129M       +63.96%
```

Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
2025-10-23 08:40:42 +00:00
Marcus B Spencer
b4565c87ee fix(fs): store getExpireAdd mutex in caseCache (fixes #9836) (#10430)
In #9701 there was a change that put the mutex used for `getExpireAdd` directly in `defaultRealCaser`, which is erroneous because multiple filesystems can share the same `caseCache`.

### Purpose

Fixes #9836 and [Slow sync sending files from Android](https://forum.syncthing.net/t/slow-sync-sending-files-from-android/24208?u=marbens). There may be other issues caused by `getExpireAdd` conflicting with itself, though.

### Testing

Unit tests pass and the case cache and conflict detection _seem_ to behave correctly.

Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
2025-10-18 21:56:03 +02:00
Simon Frei
20d2406a0e chore(upnp): remove incorrect embedding of nat.Service (fixes #10426) (#10428) 2025-10-13 07:11:00 +02:00
Marcus B Spencer
d3d3fc2d0e fix(policy): only allow approvals by non-author contributors (#10419)
This replaces `allow_contributor` with `allow_non_author_contributor`,
because the former allows authors to approve their own pull requests.

From https://github.com/syncthing/syncthing/pull/9818#issue-2651431707:

> This adds `allow_contributor: true` which allows approvals by contributors to the PR (*but still not the author themself*, which is a different thing).

This statement conflicts with [the policybot README](c013552248/README.md), which says:

> If true, the approvals of someone who has committed to the pull request are
> considered when calculating the status.
> *The pull request author is considered a contributor.*

Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
2025-10-06 08:42:58 +02:00
bt90
f8c44923c7 docs(docker): make host network mode the default (#10416)
Signed-off-by: bt90 <btom1990@googlemail.com>
2025-09-29 15:20:44 -04:00
138 changed files with 1481 additions and 1171 deletions

View File

@@ -59,6 +59,9 @@ jobs:
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
done
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

View File

@@ -197,7 +197,7 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-windows-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-windows-${{ hashFiles('go.sum') }}
- name: Install dependencies
run: |
@@ -235,7 +235,7 @@ jobs:
- package-windows
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: unsigned-packages-windows
path: packages
@@ -306,7 +306,7 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('go.sum') }}
- name: Create packages
run: |
@@ -401,7 +401,7 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-package-${{ hashFiles('go.sum') }}
- name: Import signing certificate
if: env.CODESIGN_IDENTITY != ''
@@ -484,7 +484,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: packages-macos
@@ -529,7 +529,7 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-cross-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-cross-${{ hashFiles('go.sum') }}
- name: Create packages
run: |
@@ -641,7 +641,11 @@ jobs:
path: tools
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
pattern: packages-*
path: packages
merge-multiple: true
- uses: actions/setup-go@v6
with:
@@ -657,8 +661,6 @@ jobs:
export PRIVATE_KEY="$RUNNER_TEMP/privkey.pem"
export PATH="$PATH:$(go env GOPATH)/bin"
echo "$STSIGTOOL_PRIVATE_KEY" | base64 -d > "$PRIVATE_KEY"
mkdir packages
mv packages-*/* packages
pushd packages
"$GITHUB_WORKSPACE/tools/sign-only"
rm -f "$PRIVATE_KEY"
@@ -736,7 +738,7 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-debian-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-debian-${{ hashFiles('go.sum') }}
- name: Package for Debian (CGO)
run: |
@@ -776,7 +778,7 @@ jobs:
path: tools
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: packages-signed
path: packages
@@ -831,13 +833,13 @@ jobs:
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
- name: Download signed packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: packages-signed
path: packages
- name: Download debian packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: debian-packages
path: packages
@@ -927,7 +929,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download packages
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: debian-packages
path: packages
@@ -949,7 +951,7 @@ jobs:
- name: Prepare packages
run: |
sudo chown -R $(id -u) apt/pool
mv packages/*.deb apt/pool
mv -n packages/*.deb apt/pool
- name: Update archive
uses: docker://ghcr.io/kastelo/ezapt:latest
@@ -1018,21 +1020,23 @@ jobs:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-docker-${{ matrix.pkg }}-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-go-${{ needs.facts.outputs.go-version }}-docker-${{ matrix.pkg }}-${{ hashFiles('go.sum') }}
- name: Build binaries (CGO)
run: |
mkdir bin
# amd64
go run build.go -goos linux -goarch amd64 -tags "${{env.TAGS_LINUX}}" -cc "zig cc -target x86_64-linux-musl" -no-upgrade build ${{ matrix.pkg }}
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-amd64
mv ${{ matrix.pkg }} bin/${{ matrix.pkg }}-linux-amd64
# arm64
go run build.go -goos linux -goarch arm64 -tags "${{env.TAGS_LINUX}}" -cc "zig cc -target aarch64-linux-musl" -no-upgrade build ${{ matrix.pkg }}
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm64
mv ${{ matrix.pkg }} bin/${{ matrix.pkg }}-linux-arm64
# arm
go run build.go -goos linux -goarch arm -tags "${{env.TAGS_LINUX}}" -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" -no-upgrade build ${{ matrix.pkg }}
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-arm
mv ${{ matrix.pkg }} bin/${{ matrix.pkg }}-linux-arm
env:
CGO_ENABLED: "1"
BUILD_USER: docker
@@ -1045,6 +1049,9 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -1071,10 +1078,15 @@ jobs:
echo Pushing to $tags
echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV
- name: Prepare context dir
run: |
mkdir ctx
mv bin/* script ctx
- name: Build and push Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
context: ctx
file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64,linux/arm/7
tags: ${{ env.DOCKER_TAGS }}

20
.github/workflows/org-members.yaml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Org membership recommendations
on:
workflow_dispatch:
schedule:
- cron: '0 0 1 * *'
jobs:
run-recommendation:
runs-on: ubuntu-latest
name: Check for a recommendation
steps:
- uses: docker://ghcr.io/calmh/github-org-members:latest
env:
GITHUB_ORGANISATION: syncthing
GITHUB_TOKEN: ${{ secrets.GOM_GITHUB_TOKEN }}
GOM_IGNORE_USERS: ${{ secrets.GOM_IGNORE_USERS }}
GOM_ALSO_REPOS: ${{ secrets.GOM_ALSO_REPOS }}

View File

@@ -70,6 +70,12 @@ linters:
# Rollback errors can be ignored
- linters: [errcheck]
source: Rollback
# Embedded fields named in selectors may add clarity
- linters: [staticcheck]
text: QF1008
# Don't necessarily rewrite !(foo || bar) to !foo && !bar
- linters: [staticcheck]
text: QF1001
settings:
sloglint:
context: "scope"

View File

@@ -52,7 +52,7 @@ approval_rules:
- syncthing/maintainers
options:
ignore_update_merges: true
allow_contributor: true
allow_non_author_contributor: true
# Regular pull requests require approval by an active contributor
- name: is approved by a syncthing contributor
@@ -62,7 +62,7 @@ approval_rules:
- syncthing/contributors
options:
ignore_update_merges: true
allow_contributor: true
allow_non_author_contributor: true
# Changes to some files (translations, dependencies, compatibility) do not
# require approval if they were proposed by a contributor and have a

View File

@@ -32,6 +32,7 @@ Evgeny Kuznetsov <evgeny@kuznetsov.md>
greatroar <61184462+greatroar@users.noreply.github.com>
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
Lode Hoste (Zillode) <zillode@zillode.be>
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
Michael Ploujnikov (plouj) <ploujj@gmail.com>
Ross Smith II (rasa) <ross@smithii.com>
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> <stefan@rumpelsepp.org>
@@ -198,7 +199,6 @@ Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
Marc Laporte (marclaporte) <marc@marclaporte.com> <marc@laporte.name>
Marcel Meyer <mm.marcelmeyer@gmail.com>
Marcin Dziadus (marcindziadus) <dziadus.marcin@gmail.com>
Marcus B Spencer <marcus@marcusspencer.xyz> <marcus@marcusspencer.us>
Marcus Legendre <marcus.legendre@gmail.com>
Mario Majila <mariustshipichik@gmail.com>
Mark Pulford (mpx) <mark@kyne.com.au>

View File

@@ -23,52 +23,7 @@ example `UMASK=002`.
**Docker cli**
```
$ docker pull syncthing/syncthing
$ docker run -p 8384:8384 -p 22000:22000/tcp -p 22000:22000/udp -p 21027:21027/udp \
-v /wherever/st-sync:/var/syncthing \
--hostname=my-syncthing \
syncthing/syncthing:latest
```
**Docker compose**
```yml
---
version: "3"
services:
syncthing:
image: syncthing/syncthing
container_name: syncthing
hostname: my-syncthing
environment:
- PUID=1000
- PGID=1000
volumes:
- /wherever/st-sync:/var/syncthing
ports:
- 8384:8384 # Web UI
- 22000:22000/tcp # TCP file transfers
- 22000:22000/udp # QUIC file transfers
- 21027:21027/udp # Receive local discovery broadcasts
restart: unless-stopped
healthcheck:
test: curl -fkLsS -m 2 127.0.0.1:8384/rest/noauth/health | grep -o --color=never OK || exit 1
interval: 1m
timeout: 10s
retries: 3
```
## Discovery
Note that Docker's default network mode prevents local IP addresses from
being discovered, as Syncthing is only able to see the internal IP of the
container on the `172.17.0.0/16` subnet. This will result in poor transfer rates
if local device addresses are not manually configured.
It is therefore advisable to use the [host network mode](https://docs.docker.com/network/host/) instead:
**Docker cli**
```
$ docker pull syncthing/syncthing
$ docker run --network=host \
$ docker run --network=host -e STGUIADDRESS= \
-v /wherever/st-sync:/var/syncthing \
syncthing/syncthing:latest
```
@@ -85,6 +40,7 @@ services:
environment:
- PUID=1000
- PGID=1000
- STGUIADDRESS=
volumes:
- /wherever/st-sync:/var/syncthing
network_mode: host
@@ -96,27 +52,27 @@ services:
retries: 3
```
## Discovery
Please note that Docker's default network mode prevents local IP addresses
from being discovered, as Syncthing can only see the internal IP address of
the container on the `172.17.0.0/16` subnet. This would likely break the ability
for nodes to establish LAN connections properly, resulting in poor transfer
rates unless local device addresses are configured manually.
It is therefore strongly recommended to stick to the [host network mode](https://docs.docker.com/network/host/),
as shown above.
Be aware that syncthing alone is now in control of what interfaces and ports it
listens on. You can edit the syncthing configuration to change the defaults if
there are conflicts.
## GUI Security
By default Syncthing inside the Docker image listens on 0.0.0.0:8384 to
allow GUI connections via the Docker proxy. This is set by the
`STGUIADDRESS` environment variable in the Dockerfile, as it differs from
what Syncthing would otherwise use by default. This means you should set up
authentication in the GUI, like for any other externally reachable Syncthing
instance. If you do not require the GUI, or you use host networking, you can
unset the `STGUIADDRESS` variable to have Syncthing fall back to listening
on 127.0.0.1:
```
$ docker pull syncthing/syncthing
$ docker run -e STGUIADDRESS= \
-v /wherever/st-sync:/var/syncthing \
syncthing/syncthing:latest
```
With the environment variable unset Syncthing will follow what is set in the
configuration file / GUI settings dialog.
By default Syncthing inside the Docker image listens on `0.0.0.0:8384`. This
allows GUI connections when running without host network mode. The example
above unsets the `STGUIADDRESS` environment variable to have Syncthing fall
back to listening on what has been configured in the configuration file or the
GUI settings dialog. By default this is the localhost IP address `127.0.0.1`.
If you configure your GUI to be externally reachable, make sure you set up
authentication and enable TLS.

View File

@@ -118,7 +118,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
bs, err := io.ReadAll(lr)
req.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -130,7 +130,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
var reports []ur.FailureReport
err = json.Unmarshal(bs, &reports)
if err != nil {
http.Error(w, err.Error(), 400)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(reports) == 0 {
@@ -141,7 +141,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
version, err := build.ParseVersion(reports[0].Version)
if err != nil {
http.Error(w, err.Error(), 400)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
for _, r := range reports {

View File

@@ -90,7 +90,7 @@ func sendReport(dsn string, pkt *raven.Packet, userID string) error {
}
// The client sets release and such on the packet before sending, in the
// misguided idea that it knows this better than than the packet we give
// misguided idea that it knows this better than the packet we give
// it. So we copy the values from the packet to the client first...
cli.SetRelease(pkt.Release)
cli.SetEnvironment(pkt.Environment)
@@ -136,7 +136,7 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
r := bytes.NewReader(report)
ctx, _, err := stack.ScanSnapshot(r, io.Discard, stack.DefaultOpts())
if err != nil && err != io.EOF {
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
if ctx == nil || len(ctx.Goroutines) == 0 {

View File

@@ -10,7 +10,7 @@ import (
"bytes"
"compress/gzip"
"crypto/sha256"
"fmt"
"encoding/hex"
"net"
"net/http"
"os"
@@ -23,7 +23,7 @@ import (
// remote IP, and the current month.
func userIDFor(req *http.Request) string {
addr := req.RemoteAddr
if fwd := req.Header.Get("x-forwarded-for"); fwd != "" {
if fwd := req.Header.Get("X-Forwarded-For"); fwd != "" {
addr = fwd
}
if host, _, err := net.SplitHostPort(addr); err == nil {
@@ -32,7 +32,7 @@ func userIDFor(req *http.Request) string {
now := time.Now().Format("200601")
salt := "stcrashreporter"
hash := sha256.Sum256([]byte(salt + addr + now))
return fmt.Sprintf("%x", hash[:8])
return hex.EncodeToString(hash[:8])
}
// 01234567890abcdef... => 01/23

View File

@@ -162,7 +162,7 @@ func main() {
testCert = createTestCertificate()
for i := 0; i < requestProcessors; i++ {
for range requestProcessors {
go requestProcessor(geoip)
}
@@ -180,7 +180,7 @@ func main() {
relayTestsTotal.WithLabelValues("success").Inc()
}
}
// Run the the stats refresher once the relays are loaded.
// Run the stats refresher once the relays are loaded.
statsRefresher(statsRefresh)
}()
@@ -653,6 +653,7 @@ func getLocation(host string, geoip *geoip.Provider) location {
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
}

View File

@@ -47,7 +47,7 @@ func newMetricsSet(srv *server) *metricsSet {
var initForType func(reflect.Type)
initForType = func(t reflect.Type) {
for i := 0; i < t.NumField(); i++ {
for i := range t.NumField() {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
initForType(field.Type)
@@ -175,7 +175,7 @@ func (s *metricsSet) addReport(r *contract.Report) {
func (s *metricsSet) addReportStruct(v reflect.Value, gaugeVecs map[string][]string) {
t := v.Type()
for i := 0; i < v.NumField(); i++ {
for i := range v.NumField() {
field := v.Field(i)
if field.Kind() == reflect.Struct {
s.addReportStruct(field, gaugeVecs)

View File

@@ -10,7 +10,7 @@ import (
"context"
"fmt"
"io"
"log"
"log/slog"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/thejerf/suture/v4"
@@ -23,6 +23,7 @@ import (
type amqpReplicator struct {
suture.Service
broker string
sender *amqpSender
receiver *amqpReceiver
@@ -172,7 +173,7 @@ func (s *amqpReceiver) Serve(ctx context.Context) error {
id, err = protocol.DeviceIDFromString(string(rec.Key))
}
if err != nil {
log.Println("Replication device ID:", err)
slog.Warn("Failed to parse replication device ID", "error", err)
replicationRecvsTotal.WithLabelValues("error").Inc()
continue
}

View File

@@ -18,6 +18,7 @@ import (
"fmt"
io "io"
"log"
"log/slog"
"math/rand"
"net"
"net/http"
@@ -93,7 +94,7 @@ func (s *apiSrv) Serve(ctx context.Context) error {
if s.useHTTP {
listener, err := net.Listen("tcp", s.addr)
if err != nil {
log.Println("Listen:", err)
slog.ErrorContext(ctx, "Failed to listen", "error", err)
return err
}
s.listener = listener
@@ -107,7 +108,7 @@ func (s *apiSrv) Serve(ctx context.Context) error {
tlsListener, err := tls.Listen("tcp", s.addr, tlsCfg)
if err != nil {
log.Println("Listen:", err)
slog.ErrorContext(ctx, "Failed to listen", "error", err)
return err
}
s.listener = tlsListener
@@ -132,7 +133,7 @@ func (s *apiSrv) Serve(ctx context.Context) error {
err := srv.Serve(s.listener)
if err != nil {
log.Println("Serve:", err)
slog.ErrorContext(ctx, "Failed to serve", "error", err)
}
return err
}
@@ -151,9 +152,7 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
reqID := requestID(rand.Int63())
req = req.WithContext(context.WithValue(req.Context(), idKey, reqID))
if debug {
log.Println(reqID, req.Method, req.URL, req.Proto)
}
slog.Debug("Handling request", "id", reqID, "method", req.Method, "url", req.URL, "proto", req.Proto)
remoteAddr := &net.TCPAddr{
IP: nil,
@@ -174,7 +173,7 @@ func (s *apiSrv) handler(w http.ResponseWriter, req *http.Request) {
var err error
remoteAddr, err = net.ResolveTCPAddr("tcp", req.RemoteAddr)
if err != nil {
log.Println("remoteAddr:", err)
slog.Warn("Failed to resolve remote address", "address", req.RemoteAddr, "error", err)
lw.Header().Set("Retry-After", errorRetryAfterString())
http.Error(lw, "Internal Server Error", http.StatusInternalServerError)
apiRequestsTotal.WithLabelValues("no_remote_addr").Inc()
@@ -197,9 +196,7 @@ func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
deviceID, err := protocol.DeviceIDFromString(req.URL.Query().Get("device"))
if err != nil {
if debug {
log.Println(reqID, "bad device param:", err)
}
slog.Debug("Request with bad device param", "id", reqID, "error", err)
lookupRequestsTotal.WithLabelValues("bad_request").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Bad Request", http.StatusBadRequest)
@@ -259,9 +256,7 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
rawCert, err := certificateBytes(req)
if err != nil {
if debug {
log.Println(reqID, "no certificates:", err)
}
slog.Debug("Request without certificates", "id", reqID, "error", err)
announceRequestsTotal.WithLabelValues("no_certificate").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Forbidden", http.StatusForbidden)
@@ -270,9 +265,7 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
var ann announcement
if err := json.NewDecoder(req.Body).Decode(&ann); err != nil {
if debug {
log.Println(reqID, "decode:", err)
}
slog.Debug("Failed to decode request", "id", reqID, "error", err)
announceRequestsTotal.WithLabelValues("bad_request").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Bad Request", http.StatusBadRequest)
@@ -283,9 +276,7 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
addresses := fixupAddresses(remoteAddr, ann.Addresses)
if len(addresses) == 0 {
if debug {
log.Println(reqID, "no addresses")
}
slog.Debug("Request without addresses", "id", reqID, "error", err)
announceRequestsTotal.WithLabelValues("bad_request").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Bad Request", http.StatusBadRequest)
@@ -293,9 +284,7 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
}
if err := s.handleAnnounce(deviceID, addresses); err != nil {
if debug {
log.Println(reqID, "handle:", err)
}
slog.Debug("Failed to handle request", "id", reqID, "error", err)
announceRequestsTotal.WithLabelValues("internal_error").Inc()
w.Header().Set("Retry-After", errorRetryAfterString())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
@@ -306,9 +295,7 @@ func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req
w.Header().Set("Reannounce-After", reannounceAfterString())
w.WriteHeader(http.StatusNoContent)
if debug {
log.Println(reqID, "announced", deviceID, addresses)
}
slog.Debug("Device announced", "id", reqID, "device", deviceID, "addresses", addresses)
}
func (s *apiSrv) Stop() {
@@ -350,7 +337,7 @@ func certificateBytes(req *http.Request) ([]byte, error) {
var bs []byte
if hdr := req.Header.Get("X-SSL-Cert"); hdr != "" {
if hdr := req.Header.Get("X-Ssl-Cert"); hdr != "" {
if strings.Contains(hdr, "%") {
// Nginx using $ssl_client_escaped_cert
// The certificate is in PEM format with url encoding.
@@ -513,6 +500,7 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
}

View File

@@ -13,7 +13,7 @@ import (
"encoding/binary"
"errors"
"io"
"log"
"log/slog"
"os"
"path"
"runtime"
@@ -74,24 +74,24 @@ func newInMemoryStore(dir string, flushInterval time.Duration, blobs blob.Store)
// Try to read from blob storage
latestKey, cerr := blobs.LatestKey(context.Background())
if cerr != nil {
log.Println("Error finding database from blob storage:", cerr)
slog.Error("Failed to find database in blob storage", "error", cerr)
return s
}
fd, cerr := os.Create(path.Join(s.dir, "records.db"))
if cerr != nil {
log.Println("Error creating database file:", cerr)
slog.Error("Failed to create database file", "error", cerr)
return s
}
if cerr := blobs.Download(context.Background(), latestKey, fd); cerr != nil {
log.Printf("Error downloading database from blob storage: %v", cerr)
slog.Error("Failed to download database from blob storage", "error", cerr)
}
_ = fd.Close()
nr, err = s.read()
}
if err != nil {
log.Println("Error reading database:", err)
slog.Error("Failed to read database", "error", err)
}
log.Printf("Read %d records from database", nr)
slog.Info("Loaded database", "records", nr)
s.expireAndCalculateStatistics()
return s
}
@@ -153,13 +153,13 @@ loop:
for {
select {
case <-t.C:
log.Println("Calculating statistics")
slog.InfoContext(ctx, "Calculating statistics")
s.expireAndCalculateStatistics()
log.Println("Flushing database")
slog.InfoContext(ctx, "Flushing database")
if err := s.write(); err != nil {
log.Println("Error writing database:", err)
slog.ErrorContext(ctx, "Failed to write database", "error", err)
}
log.Println("Finished flushing database")
slog.InfoContext(ctx, "Finished flushing database")
t.Reset(s.flushInterval)
case <-ctx.Done():
@@ -256,7 +256,7 @@ func (s *inMemoryStore) write() (err error) {
if err != nil {
return err
}
bw := bufio.NewWriter(fd)
bw := bufio.NewWriterSize(fd, 1<<20)
var buf []byte
var rangeErr error
@@ -310,18 +310,24 @@ func (s *inMemoryStore) write() (err error) {
return err
}
if info, err := os.Lstat(dbf); err == nil {
slog.Info("Saved database", "name", dbf, "size", info.Size(), "modtime", info.ModTime())
} else {
slog.Warn("Failed to stat database after save", "error", err)
}
// Upload to blob storage
if s.blobs != nil {
fd, err = os.Open(dbf)
if err != nil {
log.Printf("Error uploading database to blob storage: %v", err)
slog.Error("Failed to upload database to blob storage", "error", err)
return nil
}
defer fd.Close()
if err := s.blobs.Upload(context.Background(), s.objKey, fd); err != nil {
log.Printf("Error uploading database to blob storage: %v", err)
slog.Error("Failed to upload database to blob storage", "error", err)
}
log.Println("Finished uploading database")
slog.Info("Finished uploading database")
}
return nil
@@ -360,7 +366,7 @@ func (s *inMemoryStore) read() (int, error) {
key, err = protocol.DeviceIDFromString(string(rec.Key))
}
if err != nil {
log.Println("Bad device ID:", err)
slog.Error("Got bad device ID while reading database", "error", err)
continue
}

View File

@@ -9,9 +9,8 @@ package main
import (
"context"
"crypto/tls"
"log"
"log/slog"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"runtime"
@@ -24,6 +23,7 @@ import (
"github.com/syncthing/syncthing/internal/blob"
"github.com/syncthing/syncthing/internal/blob/azureblob"
"github.com/syncthing/syncthing/internal/blob/s3"
"github.com/syncthing/syncthing/internal/slogutil"
_ "github.com/syncthing/syncthing/lib/automaxprocs"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/protocol"
@@ -32,8 +32,7 @@ import (
)
const (
addressExpiryTime = 2 * time.Hour
databaseStatisticsInterval = 5 * time.Minute
addressExpiryTime = 2 * time.Hour
// Reannounce-After is set to reannounceAfterSeconds +
// random(reannounzeFuzzSeconds), similar for Retry-After
@@ -88,13 +87,16 @@ type CLI struct {
}
func main() {
log.SetOutput(os.Stdout)
var cli CLI
kong.Parse(&cli)
debug = cli.Debug
log.Println(build.LongVersionFor("stdiscosrv"))
level := slog.LevelInfo
if cli.Debug {
level = slog.LevelDebug
}
slogutil.SetDefaultLevel(level)
slog.Info(build.LongVersionFor("stdiscosrv"))
if cli.Version {
return
}
@@ -106,16 +108,18 @@ func main() {
var err error
cert, err = tls.LoadX509KeyPair(cli.Cert, cli.Key)
if os.IsNotExist(err) {
log.Println("Failed to load keypair. Generating one, this might take a while...")
slog.Info("Failed to load keypair. Generating one, this might take a while...")
cert, err = tlsutil.NewCertificate(cli.Cert, cli.Key, "stdiscosrv", 20*365, false)
if err != nil {
log.Fatalln("Failed to generate X509 key pair:", err)
slog.Error("Failed to generate X509 key pair", "error", err)
os.Exit(1)
}
} else if err != nil {
log.Fatalln("Failed to load keypair:", err)
slog.Error("Failed to load keypair", "error", err)
os.Exit(1)
}
devID := protocol.NewDeviceID(cert.Certificate[0])
log.Println("Server device ID is", devID)
slog.Info("Loaded certificate keypair", "deviceID", devID)
}
// Root of the service tree.
@@ -133,7 +137,8 @@ func main() {
blobs, err = azureblob.NewBlobStore(cli.DBAzureBlobAccount, cli.DBAzureBlobKey, cli.DBAzureBlobContainer)
}
if err != nil {
log.Fatalf("Failed to create blob store: %v", err)
slog.Error("Failed to create blob store", "error", err)
os.Exit(1)
}
// Start the database.
@@ -158,7 +163,9 @@ func main() {
go func() {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(cli.MetricsListen, mux))
err := http.ListenAndServe(cli.MetricsListen, mux)
slog.Error("Failed to serve", "error", err)
os.Exit(1)
}()
}
@@ -170,7 +177,7 @@ func main() {
signal.Notify(signalChan, os.Interrupt)
go func() {
sig := <-signalChan
log.Printf("Received signal %s; shutting down", sig)
slog.Info("Received signal; shutting down", "signal", sig)
cancel()
}()

View File

@@ -14,6 +14,7 @@ import (
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"syscall"
@@ -247,10 +248,10 @@ func main() {
query.Set("pingInterval", pingInterval.String())
query.Set("networkTimeout", networkTimeout.String())
if sessionLimitBps > 0 {
query.Set("sessionLimitBps", fmt.Sprint(sessionLimitBps))
query.Set("sessionLimitBps", strconv.Itoa(sessionLimitBps))
}
if globalLimitBps > 0 {
query.Set("globalLimitBps", fmt.Sprint(globalLimitBps))
query.Set("globalLimitBps", strconv.Itoa(globalLimitBps))
}
if statusAddr != "" {
query.Set("statusAddr", statusAddr)

View File

@@ -122,7 +122,7 @@ func (r *rateCalculator) updateRates(interval time.Duration) {
func (r *rateCalculator) rate(periods int) int64 {
var tot int64
for i := 0; i < periods; i++ {
for i := range periods {
tot += r.rates[i]
}
return tot / int64(periods)

View File

@@ -6,6 +6,7 @@ import (
"bufio"
"context"
"crypto/tls"
"errors"
"flag"
"log"
"net"
@@ -133,7 +134,8 @@ func connectToStdio(stdin <-chan string, conn net.Conn) {
conn.SetReadDeadline(time.Now().Add(time.Millisecond))
n, err := conn.Read(buf[0:])
if err != nil {
nerr, ok := err.(net.Error)
var nerr net.Error
ok := errors.As(err, &nerr)
if !ok || !nerr.Timeout() {
log.Println(err)
return

View File

@@ -10,7 +10,7 @@ import (
func setTCPOptions(conn net.Conn) error {
tcpConn, ok := conn.(*net.TCPConn)
if !ok {
return errors.New("Not a TCP connection")
return errors.New("not a TCP connection")
}
if err := tcpConn.SetLinger(0); err != nil {
return err

View File

@@ -32,6 +32,7 @@ type APIClient interface {
type apiClient struct {
http.Client
cfg config.GUIConfiguration
apikey string
}
@@ -91,11 +92,11 @@ func loadGUIConfig() (config.GUIConfiguration, error) {
guiCfg := cfg.GUI()
if guiCfg.Address() == "" {
return config.GUIConfiguration{}, errors.New("Could not find GUI Address")
return config.GUIConfiguration{}, errors.New("could not find GUI Address")
}
if guiCfg.APIKey == "" {
return config.GUIConfiguration{}, errors.New("Could not find GUI API key")
return config.GUIConfiguration{}, errors.New("could not find GUI API key")
}
return guiCfg, nil
@@ -113,7 +114,7 @@ func (c *apiClient) Endpoint() string {
}
func (c *apiClient) Do(req *http.Request) (*http.Response, error) {
req.Header.Set("X-API-Key", c.apikey)
req.Header.Set("X-Api-Key", c.apikey)
resp, err := c.Client.Do(req)
if err != nil {
return nil, err

View File

@@ -10,6 +10,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"github.com/AudriusButkevicius/recli"
@@ -18,6 +19,53 @@ import (
"github.com/urfave/cli"
)
// Try to mimic the kong output format through custom help templates
var customAppHelpTemplate = `Usage: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .Commands}} <command> [flags]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
{{.Description}}{{if .VisibleFlags}}
Flags:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .VisibleCommands}}
Commands:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}
`
var customCommandHelpTemplate = `Usage: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [flags]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
{{.Usage}}{{if .VisibleFlags}}
Flags:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Category}}
Category:
{{.Category}}{{end}}{{if .Description}}
{{.Description}}{{end}}
`
var customSubcommandHelpTemplate = `Usage: {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} <command>{{if .VisibleFlags}} [flags]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
{{.Description}}{{else}}{{if .Usage}}
{{.Usage}}{{end}}{{end}}{{if .VisibleFlags}}
Flags:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .VisibleCommands}}
Commands:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}
`
type configHandler struct {
original, cfg config.Configuration
client APIClient
@@ -28,13 +76,18 @@ type configCommand struct {
Args []string `arg:"" default:"-h"`
}
func (c *configCommand) Run(ctx Context, _ *kong.Context) error {
func (c *configCommand) Run(ctx Context, outerCtx *kong.Context) error {
app := cli.NewApp()
app.Name = "syncthing"
app.Author = "The Syncthing Authors"
app.Name = "syncthing cli config"
app.HelpName = "syncthing cli config"
app.Description = outerCtx.Selected().Help
app.Metadata = map[string]interface{}{
"clientFactory": ctx.clientFactory,
}
app.CustomAppHelpTemplate = customAppHelpTemplate
// Override global templates, as this is out only usage of the package
cli.CommandHelpTemplate = customCommandHelpTemplate
cli.SubcommandHelpTemplate = customSubcommandHelpTemplate
h := new(configHandler)
h.client, h.err = ctx.clientFactory.getClient()
@@ -55,6 +108,8 @@ func (c *configCommand) Run(ctx Context, _ *kong.Context) error {
app.Commands = commands
app.HideHelp = true
// Explicitly re-add help only as flags, not as commands
app.Flags = []cli.Flag{cli.HelpFlag}
app.Before = h.configBefore
app.After = h.configAfter
@@ -86,7 +141,7 @@ func (h *configHandler) configAfter(_ *cli.Context) error {
if err != nil {
return err
}
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
body, err := responseToBArray(resp)
if err != nil {
return err

View File

@@ -9,6 +9,7 @@ package cli
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/alecthomas/kong"
@@ -34,7 +35,7 @@ func (e *errorsPushCommand) Run(ctx Context) error {
if err != nil {
return err
}
if response.StatusCode != 200 {
if response.StatusCode != http.StatusOK {
errStr = fmt.Sprint("Failed to push error\nStatus code: ", response.StatusCode)
bytes, err := responseToBArray(response)
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"bufio"
"errors"
"fmt"
"net/http"
"path/filepath"
"github.com/alecthomas/kong"
@@ -63,7 +64,7 @@ func (f *folderOverrideCommand) Run(ctx Context) error {
if err != nil {
return err
}
if response.StatusCode != 200 {
if response.StatusCode != http.StatusOK {
errStr := fmt.Sprint("Failed to override changes\nStatus code: ", response.StatusCode)
bytes, err := responseToBArray(response)
if err != nil {

View File

@@ -31,7 +31,7 @@ const (
// directory to the crash reporting server as urlBase. Uploads are attempted
// with the newest log first.
//
// This can can block for a long time. The context can set a final deadline
// This can block for a long time. The context can set a final deadline
// for this.
func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
files, err := filepath.Glob(filepath.Join(dir, "panic-*.log"))

View File

@@ -388,8 +388,8 @@ func upgradeViaRest() error {
}
u.Path = path.Join(u.Path, "rest/system/upgrade")
target := u.String()
r, _ := http.NewRequest("POST", target, nil)
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
r, _ := http.NewRequest(http.MethodPost, target, nil)
r.Header.Set("X-Api-Key", cfg.GUI().APIKey)
tr := &http.Transport{
DialContext: dialer.DialContext,
@@ -407,7 +407,7 @@ func upgradeViaRest() error {
defer resp.Body.Close()
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
bs, err := io.ReadAll(resp.Body)
if err != nil {
return err
@@ -529,7 +529,8 @@ func (c *serveCmd) syncthingMain() {
err = upgrade.To(release)
}
if err != nil {
if _, ok := err.(*errNoUpgrade); ok || err == errTooEarlyUpgradeCheck || err == errTooEarlyUpgrade {
var noUpgradeErr *errNoUpgrade
if errors.As(err, &noUpgradeErr) || errors.Is(err, errTooEarlyUpgradeCheck) || errors.Is(err, errTooEarlyUpgrade) {
slog.Debug("Initial automatic upgrade", slogutil.Error(err))
} else {
slog.Info("Initial automatic upgrade", slogutil.Error(err))
@@ -659,13 +660,14 @@ func auditWriter(auditFile string) io.Writer {
var auditDest string
var auditFlags int
if auditFile == "-" {
switch auditFile {
case "-":
fd = os.Stdout
auditDest = "stdout"
} else if auditFile == "--" {
case "--":
fd = os.Stderr
auditDest = "stderr"
} else {
default:
if auditFile == "" {
auditFile = locations.GetTimestamped(locations.AuditLog)
auditFlags = os.O_WRONLY | os.O_CREATE | os.O_EXCL
@@ -720,7 +722,7 @@ func autoUpgrade(cfg config.Wrapper, app *syncthing.App, evLogger events.Logger)
checkInterval := time.Duration(opts.AutoUpgradeIntervalH) * time.Hour
rel, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
if err == upgrade.ErrUpgradeUnsupported {
if errors.Is(err, upgrade.ErrUpgradeUnsupported) {
sub.Unsubscribe()
return
}
@@ -836,7 +838,8 @@ func setPauseState(cfgWrapper config.Wrapper, paused bool) {
}
func exitCodeForUpgrade(err error) int {
if _, ok := err.(*errNoUpgrade); ok {
var noUpgradeErr *errNoUpgrade
if errors.As(err, &noUpgradeErr) {
return svcutil.ExitNoUpgradeAvailable.AsInt()
}
return svcutil.ExitError.AsInt()

View File

@@ -9,6 +9,7 @@ package main
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"log/slog"
@@ -176,7 +177,8 @@ func (c *serveCmd) monitorMain() {
os.Exit(svcutil.ExitSuccess.AsInt())
}
if exiterr, ok := err.(*exec.ExitError); ok {
exiterr := &exec.ExitError{}
if errors.As(err, &exiterr) {
exitCode := exiterr.ExitCode()
if stopped || c.NoRestart {
os.Exit(exitCode)

22
go.mod
View File

@@ -30,7 +30,7 @@ require (
github.com/pierrec/lz4/v4 v4.1.22
github.com/prometheus/client_golang v1.23.0
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/quic-go/quic-go v0.52.0
github.com/quic-go/quic-go v0.56.0
github.com/rabbitmq/amqp091-go v1.10.0
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
github.com/shirou/gopsutil/v4 v4.25.6 // https://github.com/shirou/gopsutil/issues/1898
@@ -42,11 +42,11 @@ require (
github.com/willabides/kongplete v0.4.0
github.com/wlynxg/anet v0.0.5
go.uber.org/automaxprocs v1.6.0
golang.org/x/crypto v0.41.0
golang.org/x/crypto v0.44.0
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.28.0
golang.org/x/net v0.47.0
golang.org/x/sys v0.38.0
golang.org/x/text v0.31.0
golang.org/x/time v0.12.0
google.golang.org/protobuf v1.36.7
modernc.org/sqlite v1.38.2
@@ -68,7 +68,6 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
github.com/google/uuid v1.6.0 // indirect
@@ -77,11 +76,10 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.3 // indirect
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nxadm/tail v1.4.11 // indirect
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -98,11 +96,11 @@ require (
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.2 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect
golang.org/x/tools v0.39.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect

52
go.sum
View File

@@ -73,16 +73,12 @@ github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
@@ -167,8 +163,8 @@ github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPb
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.3 h1:Eaq36EIyJNp7b3qDhjV7jmDVq/yPeW2v4pTqzGbOGB4=
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.3/go.mod h1:6KKUoQBZBW6PDXJtNfqeEjPXMj/ITTk+cWK9t9uS5+E=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 h1:aOeI7xAOVdK+R6xbVsZuU9HmCZYmQVmZgPf9xJUd2Sg=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0/go.mod h1:0hZWbtfeCYUQeAQdPLUzETiBhUSns7O6LDj9vH88xKA=
github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA=
github.com/maxmind/geoipupdate/v6 v6.1.0/go.mod h1:cZYCDzfMzTY4v6dKRdV7KTB6SStxtn3yFkiJ1btTGGc=
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75 h1:cUVxyR+UfmdEAZGJ8IiKld1O0dbGotEnkMolG5hfMSY=
@@ -187,14 +183,12 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
@@ -224,8 +218,8 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY=
github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
@@ -285,18 +279,18 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx50DmMpQ4MQdKKk4yg4=
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -305,13 +299,13 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -334,23 +328,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -0,0 +1,2 @@
{
}

View File

@@ -0,0 +1,2 @@
{
}

View File

@@ -82,6 +82,7 @@
"Custom Range": "Custom Range",
"Danger!": "Danger!",
"Database Location": "Database Location",
"Debug": "Debug",
"Debugging Facilities": "Debugging Facilities",
"Default": "Default",
"Default Configuration": "Default Configuration",
@@ -210,6 +211,7 @@
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
"Incorrect user name or password.": "Incorrect username or password.",
"Info": "Info",
"Internally used paths:": "Internally used paths:",
"Introduced By": "Introduced By",
"Introducer": "Introducer",

View File

@@ -6,15 +6,15 @@
"About": "Acerca de",
"Action": "Acción",
"Actions": "Acciones",
"Active filter rules": "Activadas las reglas del filtro",
"Add": "Agregar",
"Add Device": "Agregar el dispositivo",
"Add Folder": "Agregar Carpeta",
"Add Remote Device": "Añadir un dispositivo remoto",
"Add devices from the introducer to our device list, for mutually shared folders.": "Añadir dispositivos del presentador a nuestra lista de dispositivos para las carpetas compartidas mutuamente.",
"Add filter entry": "Añadir una entrada al filtro",
"Add ignore patterns": "Agregar patrones a ignorar",
"Add new folder?": "¿Agregar una carpeta nueva?",
"Active filter rules": "Reglas habilitadas del filtro",
"Add": "Añadir",
"Add Device": "Añadir dispositivo",
"Add Folder": "Añadir carpeta",
"Add Remote Device": "Añadir dispositivo remoto",
"Add devices from the introducer to our device list, for mutually shared folders.": "Añade dispositivos de la lista del presentador a nuestra lista de dispositivos, para carpetas compartidas simultáneamente.",
"Add filter entry": "Añadir regla",
"Add ignore patterns": "Añadir patrones a ignorar",
"Add new folder?": "¿Añadir 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.": "Además, se aumentará el intervalo de reescaneo completo (60 veces, es decir, nuevo valor predeterminado de 1 hora). También puede configurarlo manualmente para cada carpeta después de elegir No.",
"Address": "Dirección",
"Addresses": "Direcciones",
@@ -22,24 +22,24 @@
"Advanced Configuration": "Configuración Avanzada",
"All Data": "Todos los datos",
"All Time": "Todo el tiempo",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Todas las carpetas compartidas con este dispositivo deben estar protegidas por una contraseña, de forma que todos los datos enviados sean ilegibles sin la contraseña indicada.",
"Allow Anonymous Usage Reporting?": "¿Deseas permitir el envío anónimo de informes de uso?",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Todas las carpetas compartidas con este dispositivo deben estar protegidas por una contraseña, de forma que todos los datos enviados sean ilegibles sin dicha contraseña.",
"Allow Anonymous Usage Reporting?": "¿Permites el informe de uso anónimo?",
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabético",
"Altered by ignoring deletes.": "Alterado ignorando eliminaciones.",
"Always turned on when the folder type is \"{%foldertype%}\".": "Siempre activado cuando el tipo de carpeta es \"{{foldertype}}\".",
"Altered by ignoring deletes.": "Alterado, ignorando eliminaciones.",
"Always turned on when the folder type is \"{%foldertype%}\".": "Siempre habilitado cuando el tipo de carpeta es \"{{foldertype}}\".",
"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 las versiones. Tienes que eliminar el archivo de la carpeta compartida. Si la ruta a la aplicación contiene espacios, ésta debe estar entre comillas.",
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Anonymous Usage Reporting": "Informe de uso anónimo",
"Anonymous usage report format has changed. Would you like to move to the new format?": "El formato del informe de uso anónimo a cambiado. ¿Le gustaría pasar al nuevo formato?",
"Applied to LAN": "Aplicado a la LAN",
"Apply": "Solicitar",
"Are you sure you want to override all remote changes?": "¿Está seguro(a) de que desea sobreescribir todos los cambios remotos?",
"Are you sure you want to permanently delete all these files?": "¿Está seguro de que desea eliminar permanentemente todos estos archivos?",
"Are you sure you want to remove device {%name%}?": "¿Está seguro que desea eliminar el dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "¿Está seguro que desea eliminar la carpeta {{label}}?",
"Are you sure you want to restore {%count%} files?": "¿Está seguro que desea restaurar {{count}} archivos?",
"Are you sure you want to revert all local changes?": "¿Está seguro(a) de que desea revertir todos los cambios locales?",
"Are you sure you want to upgrade?": "¿Está seguro(a) de que desea actualizar?",
"Are you sure you want to override all remote changes?": "¿Estás seguro de que quieres sobreescribir todos los cambios remotos?",
"Are you sure you want to permanently delete all these files?": "¿Estás seguro de que quieres eliminar permanentemente todos estos archivos?",
"Are you sure you want to remove device {%name%}?": "¿Estás seguro de que quieres eliminar el dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "¿Estás seguro de que quieres eliminar la carpeta {{label}}?",
"Are you sure you want to restore {%count%} files?": "¿Estás seguro de que quieres restaurar {{count}} archivos?",
"Are you sure you want to revert all local changes?": "¿Estás seguro de que quieres revertir todos los cambios locales?",
"Are you sure you want to upgrade?": "¿Estás seguro de que quieres actualizar?",
"Authentication Required": "Autenticación requerida",
"Authors": "Autores",
"Auto Accept": "Aceptar automáticamente",
@@ -47,8 +47,8 @@
"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",
"Automatic upgrades are always enabled for candidate releases.": "Las actualizaciones automáticas siempre están activadas para las versiones candidatas.",
"Automatically create or share folders that this device advertises at the default path.": "Crear o compartir automáticamente carpetas que este dispositivo anuncia en la ruta por defecto.",
"Available debug logging facilities:": "Funciones de registro de depuración disponibles:",
"Automatically create or share folders that this device advertises at the default path.": "Crea o comparte automáticamente las carpetas que el dispositivo anuncia en la ruta predeterminada.",
"Available debug logging facilities:": "Servicios de depuración disponibles:",
"Be careful!": "¡Ten cuidado!",
"Body:": "Contenido:",
"Bugs": "Errores",
@@ -56,19 +56,19 @@
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "No se puede habilitar cuando el tipo de carpeta es \"{{foldertype}}\".",
"Changelog": "Registro de cambios",
"Clean out after": "Limpiar tras",
"Cleaning Versions": "Limpiando Versiones",
"Cleanup Interval": "Intervalo de Limpieza",
"Click to see full identification string and QR code.": "Haga clic para ver la cadena de identificación completa y su código QR.",
"Cleaning Versions": "Limpiando versiones",
"Cleanup Interval": "Intervalo de limpieza",
"Click to see full identification string and QR code.": "Haz clic para ver el identificador completo y su código QR.",
"Close": "Cerrar",
"Command": "Dominio",
"Comment, when used at the start of a line": "Comentar, cuando se usa al comienzo de una línea",
"Comment, when used at the start of a line": "Comenta la línea cuando se usa al comienzo (no se tiene en cuenta)",
"Compression": "Compresión",
"Configuration Directory": "Carpeta de la configuración",
"Configuration File": "Archivo de configuración",
"Configured": "Configurado",
"Connected (Unused)": "Conectado (Sin Uso)",
"Connection Error": "Error de conexión",
"Connection Management": "Gestión de las conexiones",
"Connection Management": "Gestión de conexiones",
"Connection Type": "Tipo de conexión",
"Connections": "Conexiones",
"Connections via relays might be rate limited by the relay": "Las conexiones a través de relés pueden estar limitadas por la velocidad del relé",
@@ -77,41 +77,41 @@
"Copied from original": "Copiado del original",
"Copied!": "¡Copiado!",
"Copy": "Copiar",
"Copy failed! Try to select and copy manually.": "¡Copia fallida! Intente seleccionar y copiar manualmente.",
"Currently Shared With Devices": "Actualmente Compartida con los Dispositivos",
"Copy failed! Try to select and copy manually.": "¡Copia fallida! Intenta seleccionar y copiar manualmente.",
"Currently Shared With Devices": "Actualmente compartida con",
"Custom Range": "Rango personalizado",
"Danger!": "¡Peligro!",
"Database Location": "Ubicación de la base de datos",
"Debug": "Depurar",
"Debugging Facilities": "Servicios de depuración",
"Default": "Predeterminado",
"Default Configuration": "Configuración Predeterminada",
"Default Configuration": "Configuración predeterminada",
"Default Device": "Dispositivo Predeterminado",
"Default Folder": "Carpeta Predeterminada",
"Default Ignore Patterns": "Ignorar patrones por defecto",
"Defaults": "Valores Predeterminados",
"Delete": "Borrar",
"Delete Unexpected Items": "Borrar Elementos Inesperados",
"Default Ignore Patterns": "Ignorar patrones predeterminados",
"Defaults": "Valores predeterminados",
"Delete": "Eliminar",
"Delete Unexpected Items": "Eliminar Elementos Inesperados",
"Deleted {%file%}": "Eliminado {{file}}",
"Deselect All": "Deseleccionar Todo",
"Deselect devices to stop sharing this folder with.": "Deseleccionar dispositivos con los cuales dejar de compartir esta carpeta.",
"Deselect folders to stop sharing with this device.": "Deseleccionar carpetas para dejar de compartir con este dispositivo.",
"Deselect All": "Deseleccionar todo",
"Deselect devices to stop sharing this folder with.": "Deselecciona dispositivos para dejar de compartir esta carpeta.",
"Deselect folders to stop sharing with this device.": "Deselecciona carpetas para dejar de compartir con este dispositivo.",
"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 Certificate": "Certificado del dispositivo",
"Device ID": "ID del Dispositivo",
"Device Identification": "Identificación del Dispositivo",
"Device Name": "Nombre del Dispositivo",
"Device ID": "ID del dispositivo",
"Device Identification": "Identificador del Dispositivo",
"Device Name": "Nombre del dispositivo",
"Device Status": "Estado del dispositivo",
"Device is untrusted, enter encryption password": "El dispositivo no es de confianza, introduzca la contraseña de cifrado",
"Device is untrusted, enter encryption password": "El dispositivo no es de confianza, introduce la contraseña de cifrado",
"Device rate limits": "Límites de velocidad del dispositivo",
"Device that last modified the item": "Dispositivo que modificó por última vez el ítem",
"Devices": "Dispositivos",
"Disable Crash Reporting": "Desactivar Informes de Fallos",
"Disable Crash Reporting": "Desactivar informes de fallos",
"Disabled": "Deshabilitado",
"Disabled periodic scanning and disabled watching for changes": "Se desactivó el escaneo periódico y se desactivó el control de cambios",
"Disabled periodic scanning and enabled watching for changes": "Se desactivó el escaneo periódico y se activó el control de cambios",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Se desactivó el escaneo periódico y falló la configuración para detectar cambios, volviendo a intentarlo cada 1 m:",
"Disabled periodic scanning and disabled watching for changes": "Escaneo periódico desactivado y control de cambios desactivado",
"Disabled periodic scanning and enabled watching for changes": "Escaneo periódico desactivado y control de cambios activado",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Escaneo periódico desactivado y fallo en la detección de cambios, reintentando cada 1m:",
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Desactiva la comparación y sincronización de los permisos de los archivos. Útil en sistemas con permisos inexistentes o personalizados (por ejemplo, FAT, exFAT, Synology, Android).",
"Discard": "Descartar",
"Disconnected": "Desconectado",
@@ -119,139 +119,139 @@
"Disconnected (Unused)": "Desconectado (Sin Uso)",
"Discovered": "Descubierto",
"Discovery": "Descubrimiento",
"Discovery Failures": "Fallos de Descubrimiento",
"Discovery Status": "Estado de Descubrimiento",
"Discovery Failures": "Fallos de descubrimiento",
"Discovery Status": "Estado de descubrimiento",
"Dismiss": "Descartar",
"Do not add it to the ignore list, so this notification may recur.": "No agregarlo a la lista de ignorados, de modo que esta notificación sea recurrente.",
"Do not add it to the ignore list, so this notification may recur.": "No añadirlo a la lista de ignorados, de modo que esta notificación sea recurrente.",
"Do not restore": "No restaurar",
"Do not restore all": "No restaurar todos",
"Do you want to enable watching for changes for all your folders?": "¿Deseas activar el control de cambios en todas tus carpetas?",
"Do you want to enable watching for changes for all your folders?": "¿Quieres activar el control de cambios en todas tus carpetas?",
"Documentation": "Documentación",
"Download Rate": "Velocidad de descarga",
"Downloaded": "Descargado",
"Downloading": "Descargando",
"Edit": "Editar",
"Edit Device": "Editar Dispositivo",
"Edit Device Defaults": "Editar Valores Predeterminados del Dispositivo",
"Edit Device Defaults": "Editar valores predeterminados del dispositivo",
"Edit Folder": "Editar Carpeta",
"Edit Folder Defaults": "Editar Valores Predeterminados de las Carpeta",
"Edit Folder Defaults": "Editar valores predeterminados de las carpeta",
"Editing {%path%}.": "Editando {{path}}.",
"Enable Crash Reporting": "Activar Informes de Fallos",
"Enable Crash Reporting": "Activar informes de fallos",
"Enable NAT traversal": "Permitir NAT transversal",
"Enable Relaying": "Habilitar Retransmisión",
"Enable Relaying": "Habilitar retransmisión",
"Enabled": "Activado",
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "Permite enviar atributos ampliados a otros dispositivos y aplicar atributos ampliados entrantes. Puede ser necesario ejecutarlo con privilegios elevados.",
"Enables sending extended attributes to other devices, but not applying incoming extended attributes. This can have a significant performance impact. Always enabled when \"Sync Extended Attributes\" is enabled.": "Permite enviar atributos ampliados a otros dispositivos, pero no aplicar los atributos ampliados entrantes. Esto puede tener un impacto significativo en el rendimiento. Siempre se activa cuando \"Sincronizar atributos extendidos\" está activado.",
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "Hace que la información de propiedad se envíe a otros dispositivos y que se aplique la información de propiedad recibida. Por lo general, requiere ejecutarse con privilegios elevados.",
"Enables sending ownership information to other devices, but not applying incoming ownership information. This can have a significant performance impact. Always enabled when \"Sync Ownership\" is enabled.": "Permite enviar información de propiedad a otros dispositivos, pero no aplicar la información de propiedad entrante. Esto puede tener un impacto significativo en el rendimiento. Siempre se activa cuando \"Sincronizar propiedad\" está activado.",
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "Permite enviar atributos extendidos a otros dispositivos y aplicar atributos extendidos entrantes. Puede ser necesaria la ejecución con privilegios elevados.",
"Enables sending extended attributes to other devices, but not applying incoming extended attributes. This can have a significant performance impact. Always enabled when \"Sync Extended Attributes\" is enabled.": "Permite enviar atributos extendidos a otros dispositivos, pero no aplica los atributos extendidos entrantes. Puede tener impacto en el rendimiento. Siempre se habilita cuando \"Sincronizar atributos extendidos\" está habilitado.",
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "Permite enviar información de propietario a otros dispositivos y que se aplique la información de propietario recibida. Puede ser necesaria la ejecución con privilegios elevados.",
"Enables sending ownership information to other devices, but not applying incoming ownership information. This can have a significant performance impact. Always enabled when \"Sync Ownership\" is enabled.": "Permite enviar información de propietario a otros dispositivos, pero no aplicar la información de propiedad entrante. Puede tener impacto en el rendimiento. Siempre se habilita cuando \"Sincronizar propietario\" está habilitado.",
"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 direcciones separadas por comas (\"tcp://ip:port\", \"tcp://host:port\") o \"dynamic\" para realizar el descubrimiento automático de la dirección.",
"Enter ignore patterns, one per line.": "Introducir patrones a ignorar, uno por línea.",
"Enter up to three octal digits.": "Introduzca hasta tres dígitos octales.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduce direcciones separadas por comas (\"tcp://ip:port\", \"tcp://host:port\") o \"dynamic\" para realizar el descubrimiento automático de la dirección.",
"Enter ignore patterns, one per line.": "Introduce patrones a ignorar, uno por línea.",
"Enter up to three octal digits.": "Introduce hasta tres dígitos octales.",
"Error": "Fallo",
"Extended Attributes": "Atributos ampliados",
"Extended Attributes": "Atributos extendidos",
"Extended Attributes Filter": "Filtro de atributos extendidos",
"External": "Externo",
"External File Versioning": "Versionado externo de fichero",
"External File Versioning": "Externo",
"Failed Items": "Elementos fallidos",
"Failed to load file versions.": "Error al cargar las versiones de los archivos.",
"Failed to load ignore patterns.": "No se pudieron cargar los patrones de ignorar.",
"Failed to load ignore patterns.": "No se pudieron cargar los patrones a ignorar.",
"Failed to set up, retrying": "Fallo en la configuración, 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 Archivos",
"File Versioning": "Versionado de ficheros",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Los ficheros son movidos a la carpeta .stversions cuando son reemplazados o borrados por Syncthing.",
"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 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.": "Los archivos se sincronizan desde el clúster, pero los cambios realizados localmente no se enviarán a otros dispositivos.",
"Filesystem Watcher Errors": "Errores del Vigilante del Sistema de Archivos",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Se espera que la conexión a servidores IPv6 falle si no hay conectividad IPv6.",
"File Pull Order": "Orden de descarga",
"File Versioning": "Versionado de archivos",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Los archivos son movidos al directorio .stversions cuando son reemplazados o eliminados por Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Los archivos son movidos a versiones con fecha en el directorio .stversions cuando son reemplazados o eliminados 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 archivos están protegidos de cambios en los otros dispositivos, pero los cambios en este dispositivo se enviarán al resto.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Los archivos se sincronizan desde los otros dispositivos, pero los cambios locales no se enviarán a estos.",
"Filesystem Watcher Errors": "Errores del watcher del sistema de archivos",
"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 ID": "ID de la carpeta",
"Folder Label": "Etiqueta de la carpeta",
"Folder Path": "Ruta de la carpeta",
"Folder Status": "Estado de la carpeta",
"Folder Type": "Tipo de carpeta",
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "El tipo de carpeta \"{{receiveEncrypted}}\" solo puede ser establecido al agregar una nueva carpeta.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "El tipo de carpeta \"{{receiveEncrypted}}\" no se puede cambiar después de añadir la carpeta. Es necesario eliminar la carpeta, borrar o descifrar los datos en el disco y volver a añadir la carpeta.",
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "El tipo de carpeta \"{{receiveEncrypted}}\" sólo puede ser seleccionado al añadir una nueva carpeta.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "El tipo de carpeta \"{{receiveEncrypted}}\" no se puede cambiar después de añadir la carpeta. Es necesario eliminar la carpeta, eliminar o descifrar los datos en el disco y volver a añadir la 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.": "En las siguientes carpetas se ha producido un error al empezar a buscar cambios. Se volverá a intentar cada minuto, por lo que los errores podrían solucionarse pronto. Si persisten, trata de arreglar el problema subyacente y pide ayuda si no puedes.",
"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.": "Se ha producido un error al comenzar a buscar cambios para las siguientes carpetas. Se hará un reintento cada minuto, por lo que los errores podrían desaparecer pronto. Si persisten trata de arreglar el problema subyacente o pide ayuda.",
"Forever": "Para siempre",
"Full Rescan Interval (s)": "Intervalo de rescaneo completo (s)",
"GUI": "Interfaz gráfica del usuario",
"Full Rescan Interval (s)": "Intervalo reescaneo completo",
"GUI": "Interfaz gráfica",
"GUI / API HTTPS Certificate": "Certificado HTTPS GUI / API",
"GUI Authentication Password": "Contraseña de la Interfaz Gráfica de Usuario (GUI)",
"GUI Authentication User": "Autentificación de usuario de la Interfaz Gráfica de Usuario (GUI)",
"GUI Authentication: Set User and Password": "Autenticación de la GUI: Establezca el Usuario y Contraseña",
"GUI Listen Address": "Dirección de la interfaz de usuario",
"GUI Authentication Password": "Contraseña de la interfaz gráfica",
"GUI Authentication User": "Usuario de la interfaz gráfica",
"GUI Authentication: Set User and Password": "Autenticación GUI: configura usuario y contraseña",
"GUI Listen Address": "Dirección de la interfaz gráfica",
"GUI Override Directory": "Directorio de reemplazo de GUI",
"GUI Theme": "Tema GUI",
"GUI Theme": "Tema de la interfaz gráfica",
"General": "General",
"Generate": "Generar",
"Global Discovery": "Descubrimiento global",
"Global Discovery Servers": "Servidores Globales de Descubrimiento",
"Global Discovery Servers": "Servidores globales de descubrimiento",
"Global State": "Estado global",
"Help": "Ayuda",
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Sugerencia: solo se detectan reglas de denegación mientras que el valor predeterminado es denegar. Considere agregar \"permitir cualquiera\" como última regla.",
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Sugerencia: sólo se detectan reglas para denegar y el valor predeterminado es denegar. Considera añadir \"permitir cualquiera\" como última regla.",
"Home page": "Página de inicio",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Sin embargo, su configuración actual indica que puede no quererla activa. Hemos desactivado los informes automáticos de fallos por usted.",
"Identification": "Identificación",
"If untrusted, enter encryption password": "Si no es de confianza, introduzca la contraseña de cifrado",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Si desea evitar que otros usuarios de esta computadora accedan a Syncthing y, a través de él, a sus archivos, considere establecer la autenticación.",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Sin embargo tu configuración actual indica que puedes no querer habilitarlo. Hemos desactivado los informes automáticos de fallos por .",
"Identification": "Identificador",
"If untrusted, enter encryption password": "Si no es de confianza, introduce la contraseña de cifrado",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Si quieres evitar que otros usuarios de este ordenador accedan a Syncthing y a tus archivos a través de él, considera habilitar la autenticación.",
"Ignore": "Ignorar",
"Ignore Patterns": "Patrones a ignorar",
"Ignore Permissions": "Permisos a ignorar",
"Ignore patterns can only be added after the folder is created. If checked, an input field to enter ignore patterns will be presented after saving.": "Los patrones a ignorar solo se pueden agregar luego de que la carpeta sea creada. Cuando se marca, se presentará un campo de entrada para introducir los patrones a ignorar después de guardar.",
"Ignore Permissions": "Ignorar permisos",
"Ignore patterns can only be added after the folder is created. If checked, an input field to enter ignore patterns will be presented after saving.": "Los patrones a ignorar solo se pueden añadir después de que la carpeta sea creada. Cuando se marca, después de guardar se presentará un campo de entrada para introducir los patrones a ignorar.",
"Ignored Devices": "Dispositivos ignorados",
"Ignored Folders": "Carpetas ignoradas",
"Ignored at": "Ignorados en",
"Included Software": "Programas incluidos",
"Incoming Rate Limit (KiB/s)": "Límite de descarga (KiB/s)",
"Incoming Rate Limit (KiB/s)": "Limite tráfico de entrada (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configuración incorrecta puede corromper el contenido de la carpeta y poner a Syncthing en un estado inoperante.",
"Incorrect user name or password.": "Nombre de usuario o contraseña incorrectos.",
"Info": "Información",
"Internally used paths:": "Rutas de uso interno:",
"Introduced By": "Introducido por",
"Introduced By": "Presentado por",
"Introducer": "Presentador",
"Introduction": "Introducción",
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (por ejemplo, \"no excluir\")",
"Keep Versions": "Mantener versiones",
"Keep Versions": "Versiones a conservar",
"LDAP": "LDAP",
"Largest First": "Más grande primero",
"Largest First": "Mayor tamaño primero",
"Last 30 Days": "Últimos 30 días",
"Last 7 Days": "Últimos 7 días",
"Last Month": "Último mes",
"Last Scan": "Último escaneo",
"Last seen": "Visto por última vez",
"Latest Change": "Último Cambio",
"Latest Change": "Último cambio",
"Learn more": "Saber más",
"Learn more at {%url%}": "Más información en {{url}}",
"Limit": "Límite",
"Limit Bandwidth in LAN": "Limitar el ancho de banda en LAN",
"Listener Failures": "Fallos de Oyente",
"Listener Status": "Estado de Oyente",
"Limit Bandwidth in LAN": "Limitar ancho de banda en LAN",
"Listener Failures": "Fallos del oyente",
"Listener Status": "Estado del oyente",
"Listeners": "Oyentes",
"Loading data...": "Cargando datos...",
"Loading...": "Cargando...",
"Local Additions": "Adiciones Locales",
"Local Additions": "Cambios Locales",
"Local Discovery": "Descubrimiento local",
"Local State": "Estado local",
"Local State (Total)": "Estado Local (Total)",
"Locally Changed Items": "Elementos Cambiados Localmente",
"Local State (Total)": "Estado local (Total)",
"Locally Changed Items": "Cambiados localmente",
"Log": "Registro",
"Log File": "Archivo de registro",
"Log In": "Iniciar sesión",
"Log Out": "Cerrar sesión",
"Log in to see paths information.": "Inicia sesión para ver la información sobre las rutas.",
"Log in to see version information.": "Inicia sesión para ver la información sobre la versión.",
"Log tailing paused. Scroll to the bottom to continue.": "Seguimiento del registro pausado. Desplácese hasta el final para continuar.",
"Log tailing paused. Scroll to the bottom to continue.": "Seguimiento del registro pausado. Desplázate hasta el final para continuar.",
"Login failed, see Syncthing logs for details.": "El inicio de sesión falló, mira los registros de Syncthing para más detalles.",
"Logs": "Registros",
"Major Upgrade": "Actualización importante",
"Mass actions": "Acción masiva",
"Maximum Age": "Edad máxima",
"Maximum Age": "Antigüedad máxima",
"Maximum single entry size": "Tamaño máximo de una entrada",
"Maximum total size": "Tamaño máximo total",
"Metadata Only": "Sólo metadatos",
@@ -264,48 +264,48 @@
"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",
"New Device": "Nuevo Dispositivo",
"New Folder": "Nueva Carpeta",
"Newest First": "El más nuevo primero",
"New Device": "Nuevo dispositivo",
"New Folder": "Nueva carpeta",
"Newest First": "Más reciente primero",
"No": "No",
"No File Versioning": "Sin versionado de fichero",
"No File Versioning": "Sin versionado de archivos",
"No files will be deleted as a result of this operation.": "Ningún archivo será eliminado como resultado de esta operación.",
"No rules set": "No se han fijado normas",
"No upgrades": "Sin actualizaciones",
"Not shared": "No Compartido(a)",
"Not shared": "No compartida",
"Notice": "Aviso",
"Number of Connections": "Número de conexiones",
"OK": "De acuerdo",
"Off": "Desactivado",
"Oldest First": "El más antiguo primero",
"Optional descriptive label for the folder. Can be different on each device.": "Etiqueta descriptiva opcional para la carpeta. Puede ser diferente en cada dispositivo.",
"Oldest First": "Más antiguo primero",
"Optional descriptive label for the folder. Can be different on each device.": "Etiqueta descriptiva opcional de la carpeta. Puede ser diferente en cada dispositivo.",
"Options": "Opciones",
"Out of Sync": "No sincronizado",
"Out of Sync Items": "Elementos no sincronizados",
"Outgoing Rate Limit (KiB/s)": "Límite de subida (KiB/s)",
"Outgoing Rate Limit (KiB/s)": "Limite tráfico de salida (KiB/s)",
"Override": "Sobreescribir",
"Override Changes": "Anular cambios",
"Ownership": "Propiedad",
"Ownership": "Propietario",
"Password": "Contraseña",
"Path": "Ruta",
"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 el dispositivo local. Se creará la carpeta si no existe. El carácter de la tilde (~) se puede utilizar como abreviatura de",
"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 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 de la carpeta en el dispositivo local. Si no existe será creada. El carácter (~) se puede usar como abreviatura de",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Ruta donde almacenar las versiones (deja vacío para usar el directorio predeterminado .stversions en la carpeta compartida).",
"Paths": "Rutas",
"Pause": "Pausar",
"Pause All": "Pausar todo",
"Paused": "Pausado",
"Paused (Unused)": "Pausado(a) (Sin Uso)",
"Paused (Unused)": "Pausado (No usado)",
"Pending changes": "Cambios pendientes",
"Periodic scanning at given interval and disabled watching for changes": "Escaneando periódicamente a un intervalo dado y detección de cambios desactivada",
"Periodic scanning at given interval and enabled watching for changes": "Escaneando periódicamente a un intervalo dado y detección de cambios activada",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Escaneando periódicamente a un intervalo dado y falló la configuración para detectar cambios, volviendo a intentarlo cada 1 m:",
"Permanently add it to the ignore list, suppressing further notifications.": "Agregarlo permanentemente a la lista de ignorados, suprimiendo futuras notificaciones.",
"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.",
"Periodic scanning at given interval and disabled watching for changes": "Escaneo periódico a intervalos, detección de cambios deshabilitada",
"Periodic scanning at given interval and enabled watching for changes": "Escaneo periódico a intervalos, detección de cambios habilitada",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Escaneo periódico a intervalos y fallo en la detección de cambios, reintentando cada 1m:",
"Permanently add it to the ignore list, suppressing further notifications.": "Añadirlo permanentemente a la lista de ignorados, suprimiendo futuras notificaciones.",
"Please consult the release notes before performing a major upgrade.": "Por favor, consulta 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, introduce un usuario y contraseña para la autenticación de la interfaz gráfica en Ajustes.",
"Please wait": "Por favor, espere",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefijo que indica que el archivo puede ser eliminado si se impide el borrado del directorio",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefijo que indica que el archivo puede ser eliminado si se impide eliminar el directorio",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefijo que indica que el patrón debe coincidir sin distinguir mayúsculas de minúsculas",
"Preparing to Sync": "Preparándose para Sincronizar",
"Preparing to Sync": "Preparando Sincronización",
"Preview": "Vista previa",
"Preview Usage Report": "Previsualizar el Informe de Uso",
"QR code": "Código QR",
@@ -314,22 +314,22 @@
"Quick guide to supported patterns": "Guía rápida de patrones soportados",
"Random": "Aleatorio",
"Receive Encrypted": "Recibir Encriptado",
"Receive Only": "Solo Recibir",
"Receive Only": "Sólo Recibir",
"Received data is already encrypted": "Los datos recibidos ya están cifrados",
"Recent Changes": "Cambios recientes",
"Reduced by ignore patterns": "Reducido por patrones de ignorar",
"Reduced by ignore patterns": "Reducido por patrones a ignorar",
"Relay LAN": "Relé LAN",
"Relay WAN": "Relé WAN",
"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",
"Remote GUI": "GUI Remota",
"Remote Devices": "Dispositivos Remotos",
"Remote GUI": "GUI remota",
"Remove": "Eliminar",
"Remove Device": "Eliminar dispositivo",
"Remove Folder": "Remover 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",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador requerido de la carpeta. Debe ser el mismo en todos los dispositivos del clúster.",
"Rescan": "Reescanear",
"Rescan All": "Reescanear todo",
"Rescans": "Reescaneos",
"Restart": "Reiniciar",
"Restart Needed": "Reinicio necesario",
@@ -340,150 +340,150 @@
"Resume All": "Continuar todo",
"Reused": "Reutilizado",
"Revert": "Revertir",
"Revert Local Changes": "Revertir Cambios Locales",
"Revert Local Changes": "Revertir cambios locales",
"Save": "Guardar",
"Saving changes": "Guardar los cambios",
"Scan Time Remaining": "Tiempo Restante de Escaneo",
"Scanning": "Analizando",
"See external versioning help for supported templated command line parameters.": "Vea la ayuda del gestor de versiones externo para los parámetros de linea de comandos que usan una plantilla.",
"Select All": "Seleccionar Todo",
"Scan Time Remaining": "Tiempo restante de escaneo",
"Scanning": "Escaneando",
"See external versioning help for supported templated command line parameters.": "Ve a la ayuda del gestor de versiones externo para las parámetros de linea de comandos compatibles.",
"Select All": "Seleccionar todo",
"Select a version": "Seleccione una versión",
"Select additional devices to share this folder with.": "Seleccionar dispositivos adicionales con los cuales compartir esta carpeta.",
"Select additional folders to share with this device.": "Seleccionar carpetas adicionales para compartir con este dispositivo.",
"Select latest version": "Seleccione la última versión",
"Select oldest version": "Seleccione la versión más antigua",
"Select additional devices to share this folder with.": "Selecciona dispositivos adicionales para compartir esta carpeta.",
"Select additional folders to share with this device.": "Selecciona carpetas adicionales para compartir con este dispositivo.",
"Select latest version": "Selecciona la última versión",
"Select oldest version": "Selecciona la versión más antigua",
"Send & Receive": "Enviar y Recibir",
"Send Extended Attributes": "Enviar atributos extendidos",
"Send Only": "Solo Enviar",
"Send Ownership": "Enviar Titularidad",
"Set Ignores on Added Folder": "Establecer Ignorados en Carpeta Agregada",
"Send Only": "Sólo Enviar",
"Send Ownership": "Enviar propietario",
"Set Ignores on Added Folder": "Establecer ignorados en carpeta añadida",
"Settings": "Ajustes",
"Share": "Compartir",
"Share Folder": "Compartir carpeta",
"Share by Email": "Compartir por correo electrónico",
"Share by Email": "Compartir por email",
"Share by SMS": "Compartir por SMS",
"Share this folder?": "¿Deseas compartir esta carpeta?",
"Shared Folders": "Carpetas Compartidas",
"Shared With": "Compartir con",
"Sharing": "Compartiendo",
"Share this folder?": "¿Quieres compartir esta carpeta?",
"Shared Folders": "Carpetas compartidas",
"Shared With": "Compartida con",
"Sharing": "Compartir",
"Show ID": "Mostrar ID",
"Show QR": "Mostrar QR",
"Show detailed discovery status": "Mostrar estado de descubrimiento detallado",
"Show detailed listener status": "Mostrar estado de oyente detallado",
"Show diff with previous version": "Mostrar la diferencia con la versión anterior",
"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.",
"Show diff with previous version": "Mostrar diferencias con la versión anterior",
"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 predeterminado.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Sustituye al ID del dispositivo en el estado del grupo (cluster). Si se deja vacío se usará el nombre que anuncia el dispositivo.",
"Shut Down": "Apagar",
"Shutdown Complete": "Apagar completamente",
"Simple": "Sencillo",
"Simple File Versioning": "Versionado simple de fichero",
"Simple": "Simple",
"Simple File Versioning": "Simple",
"Single level wildcard (matches within a directory only)": "Comodín de nivel único (coincide solamente dentro de un directorio)",
"Size": "Tamaño",
"Smallest First": "El más pequeño primero",
"Smallest First": "Menor tamaño primero",
"Some discovery methods could not be established for finding other devices or announcing this device:": "No se han podido establecer algunos métodos de descubrimiento para encontrar otros dispositivos o para anunciar este dispositivo:",
"Some items could not be restored:": "Algunos ítemes no pudieron ser restaurados:",
"Some listening addresses could not be enabled to accept connections:": "Algunas direcciones de escucha no pudieron ser activadas para aceptar conexiones:",
"Some items could not be restored:": "Algunos elementos no pudieron ser restaurados:",
"Some listening addresses could not be enabled to accept connections:": "Algunas direcciones de escucha no pudieron activarse para aceptar conexiones:",
"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.",
"Stable releases only": "Solo versiones estables",
"Staggered": "Gradual",
"Staggered File Versioning": "Versionado escalonado de fichero",
"Start Browser": "Iniciar el navegador",
"Staggered File Versioning": "Escalonado",
"Start Browser": "Iniciar en navegador",
"Statistics": "Estadísticas",
"Stay logged in": "Permanecer conectado",
"Stopped": "Detenido",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Almacena y sincroniza sólo los datos cifrados. Las carpetas de todos los dispositivos conectados deben estar configuradas con la misma contraseña o ser también del tipo \"{{receiveEncrypted}}\".",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Sólo almacena y sincroniza datos cifrados. Las carpetas de todos los dispositivos conectados deben estar configuradas con la misma contraseña o ser del tipo \"{{receiveEncrypted}}\".",
"Subject:": "Asunto:",
"Support": "Forum",
"Support Bundle": "Paquete de Soporte",
"Sync Extended Attributes": "Atributos ampliados de sincronización",
"Sync Ownership": "Sincronizar la información de la propiedad",
"Support": "Soporte",
"Support Bundle": "Paquete de soporte",
"Sync Extended Attributes": "Sincronizar atributos extendidos",
"Sync Ownership": "Sincronizar propietario",
"Sync Protocol Listen Addresses": "Direcciones de escucha del protocolo de sincronización",
"Sync Status": "Estado de la sincronización",
"Syncing": "Sincronizando",
"Syncthing device ID for \"{%devicename%}\"": "ID del dispositivo de sincronización para \"{{devicename}}\"",
"Syncthing has been shut down.": "Syncthing se ha detenido.",
"Syncthing includes the following software or portions thereof:": "Syncthing incluye el siguiente software o partes de él:",
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing es Software Libre y de Código Abierto con licencia MPL v2.0.",
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing es software libre de código abierto con licencia MPL v2.0.",
"Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet.": "Syncthing es una aplicación para la sincronización continua de archivos. Sincroniza archivos entre dos o más computadoras en tiempo real, con protección contra miradas indiscretas. Tus datos son solo tuyos y mereces elegir dónde se almacenan, si se comparten con terceros y cómo se transmiten a través de Internet.",
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing está a la escucha en las siguientes direcciones de red en busca de intentos de conexión de otros dispositivos:",
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing no está a la escucha de intentos de conexión de otros dispositivos en ninguna dirección. Solo pueden funcionar las conexiones salientes de este dispositivo.",
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing está a la escucha en las siguientes direcciones de red en busca de intentos de conexión desde otros dispositivos:",
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing no está a la escucha de intentos de conexión desde otros dispositivos en ninguna dirección. Solo pueden funcionar las conexiones salientes de este dispositivo.",
"Syncthing is restarting.": "Syncthing se está reiniciando.",
"Syncthing is saving changes.": "La sincronización guarda los cambios.",
"Syncthing is upgrading.": "Syncthing se está actualizando.",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing ahora soporta el reportar automáticamente las fallas a los desarrolladores. Esta característica está habilitada por defecto.",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing soporta ahora el reporte automático de fallos a los desarrolladores. Esta función está habilitada por defecto.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Parece que la sincronización no funciona o hay un problema con la conexión a Internet. Intentando lo otra vez…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing tiene problemas para procesar tu solicitud. Por favor, actualiza la página o reinicia Syncthing si el problema persiste.",
"TCP LAN": "TCP LAN",
"TCP WAN": "TCP WAN",
"Take me back": "Llévame de vuelta",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "La dirección de la Interfaz Gráfica de Ususario (GUI) está sobreescrita por las opciones de inicio. Los cambios aquí no tendrán efecto mientras la sobreescritura esté activa.",
"The Syncthing Authors": "Los Autores de Syncthing",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "La dirección de la interfaz gráfica está anulada por las opciones de inicio. Los cambios aquí no tendrán efecto mientras continue la anulación.",
"The Syncthing Authors": "Los autores de Syncthing",
"The Syncthing admin interface is configured to allow remote access without a password.": "El panel de administración de Syncthing está configurado para permitir el acceso remoto sin contraseña.",
"The aggregated statistics are publicly available at the URL below.": "Las estadísticas agragadas están disponibles públicamente en la URL de abajo.",
"The cleanup interval cannot be blank.": "El intervalo de limpieza no puede ser nulo.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuración ha sido grabada pero no activada. Syncthing debe reiniciarse para activar la nueva configuración.",
"The device ID cannot be blank.": "La ID del dispositivo no puede estar vacía.",
"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).": "El ID del dispositivo que hay que introducir aquí se puede encontrar en el diálogo \"Acciones > Mostrar ID\" en el otro dispositivo. Los espacios y las barras son opcionales (ignorados).",
"The cleanup interval cannot be blank.": "El intervalo de limpieza no puede estar vacío.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configuración ha sido guardada pero no activada. Syncthing debe reiniciarse para activar la nueva configuración.",
"The device ID cannot be blank.": "El ID del dispositivo no puede estar vacío.",
"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).": "El ID del dispositivo a introducir aquí se puede encontrar en el diálogo \"Acciones > Mostrar ID\" del otro dispositivo. Los espacios y barras son opcionales (ignorados).",
"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.": "El informe encriptado de uso se envía diariamente. Se usa para rastrear plataformas comunes, tamaños de carpetas y versiones de la aplicación. Si el conjunto de datos enviados en el informes se cambia, se le pedirá a usted autorización de nuevo.",
"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.": "La ID del dispositivo introducida no parece válida. Debe ser una cadena de 52 ó 56 caracteres formada por letras y números, con espacios y guiones opcionales.",
"The folder ID cannot be blank.": "La ID de la carpeta no puede estar vacía.",
"The folder ID must be unique.": "La ID de la carpeta debe ser única.",
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "El contenido de las carpetas de otros dispositivos será sobreescritos para que sea idéntico al de este dispositivo. Archivos no presentes aquí serán eliminados de otros dispositivos.",
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "El contenido de las carpetas en este dispositivo será sobreescrito para ser idéntico al de otros dispositivos. Los archivos que se agreguen aquí se eliminarán.",
"The folder path cannot be blank.": "La ruta de la carpeta no puede estar en blanco.",
"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.": "Se utilizan los siguientes intervalos: para la primera hora se mantiene una versión cada 30 segundos, para el primer día se mantiene una versión cada hora, para los primeros 30 días se mantiene una versión diaria hasta la edad máxima de una semana.",
"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.": "El ID del dispositivo introducido no parece válido. Debe ser una cadena de 52 ó 56 caracteres formada por letras y números, con espacios y guiones opcionales.",
"The folder ID cannot be blank.": "El ID de la carpeta no puede estar vacío.",
"The folder ID must be unique.": "El ID de la carpeta debe ser único.",
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "El contenido de la carpeta en el resto de dispositivos será sobreescritos para que sea idéntico al de este dispositivo. Los archivos no presentes aquí serán eliminados de los otros dispositivos.",
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "El contenido de la carpeta en este dispositivo será sobreescrito para que sea idéntico al resto de dispositivos. Los archivos añadidos localmente serán eliminados.",
"The folder path cannot be blank.": "La ruta de la carpeta no puede estar vacía.",
"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.": "Se usan los siguientes intervalos: durante la primera hora se conserva una versión cada 30 segundos, durante el primer día se conserva una versión cada hora, durante los primeros 30 días se conserva una versión cada día, y hasta alcanzar la antigüedad máxima se conserva una versión por semana.",
"The following items could not be synchronized.": "Los siguientes elementos no pueden ser sincronizados.",
"The following items were changed locally.": "Los siguientes elementos fueron cambiados localmente.",
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Los siguientes métodos son usados para descubrir otros dispositivos en la red y anunciar este dispositivo para que sea encontrado por otros:",
"The following text will automatically be inserted into a new message.": "El siguiente texto se insertará automáticamente en un nuevo mensaje.",
"The following unexpected items were found.": "Los siguientes elementos inesperados fueron encontrados.",
"The interval must be a positive number of seconds.": "El intervalo debe ser un número de segundos positivo.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "El intervalo, en segundos, para ejecutar la limpieza del directorio de versiones. Cero para desactivar la limpieza periódica.",
"The maximum age must be a number and cannot be blank.": "La edad máxima debe ser un número y no puede estar vacía.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "El tiempo máximo para mantener una versión en días (introducir 0 para mantener las versiones indefinidamente).",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Intervalo en segundos para ejecutar la limpieza del directorio de versiones. Cero desactiva la limpieza periódica.",
"The maximum age must be a number and cannot be blank.": "La antigüedad máxima debe ser un número y no puede estar vacía.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Tiempo máximo en días para conservar una versión. Cero significa indefinidamente.",
"The number of connections must be a non-negative number.": "El número de las conexiones debe ser un número que no sea negativo.",
"The number of days must be a number and cannot be blank.": "El número de días debe ser un número y no puede estar en blanco.",
"The number of days to keep files in the trash can. Zero means forever.": "El número de días para mantener los archivos en la papelera. Cero significa \"para siempre\".",
"The number of old versions to keep, per file.": "El número de versiones a antiguas a mantener para cada fichero.",
"The number of days must be a number and cannot be blank.": "El número de días debe ser un número y no puede estar vacío.",
"The number of days to keep files in the trash can. Zero means forever.": "Número de días para conservar los archivos en la papelera. Cero significa indefinidamente.",
"The number of old versions to keep, per file.": "Número de versiones antiguas a conservar por cada archivo.",
"The number of versions must be a number and cannot be blank.": "El número de versiones debe ser un número y no puede estar vacío.",
"The path cannot be blank.": "La ruta no puede estar vacía.",
"The rate limit is applied to the accumulated traffic of all connections to this device.": "El límite de velocidad se aplica al tráfico acumulado de todas las conexiones a este dispositivo.",
"The rate limit must be a non-negative number (0: no limit)": "El límite de velocidad debe ser un número no negativo (0: sin límite)",
"The remote device has not accepted sharing this folder.": "El dispositivo remoto no ha aceptado compartir esta carpeta.",
"The remote device has paused this folder.": "El dispositivo remoto ha puesto en pausa esta carpeta.",
"The rescan interval must be a non-negative number of seconds.": "El intervalo de actualización debe ser un número positivo de segundos.",
"There are no devices to share this folder with.": "No hay dispositivos con los cuales compartir esta carpeta.",
"The rescan interval must be a non-negative number of seconds.": "El intervalo de reescaneo debe ser un número positivo de segundos.",
"There are no devices to share this folder with.": "No hay dispositivos con los que compartir esta carpeta.",
"There are no file versions to restore.": "No hay versiones de archivos para restaurar.",
"There are no folders to share with this device.": "No hay carpetas para compartir con este dispositivo.",
"They are retried automatically and will be synced when the error is resolved.": "Se reintentarán de forma automática y se sincronizarán cuando se resuelva el error.",
"This Device": "Este Dispositivo",
"This Month": "Este mes",
"This can easily give hackers access to read and change any files on your computer.": "Esto podría permitir fácilmente el acceso a hackers para leer y modificar cualquier fichero de tu equipo.",
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Este dispositivo no puede descubrir automáticamente a otros dispositivos o anunciar su propia dirección para que sea encontrado con otros. Solo dispositivos con direcciones configuradas como estáticas pueden conectarse.",
"This can easily give hackers access to read and change any files on your computer.": "Esto podría permitir fácilmente el acceso a hackers para leer y modificar cualquier archivo de tu equipo.",
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Este dispositivo no puede descubrir automáticamente a otros dispositivos o anunciar su propia dirección para que sea descubierto por otros. Solo dispositivos con direcciones estáticas configuradas pueden conectarse.",
"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).",
"This setting controls the free space required on the home (i.e., index database) disk.": "Determina el espacio libre necesario en el disco principal (con la. base de datos de índices).",
"Time": "Hora",
"Time the item was last modified": "Hora en que el ítem fue modificado por última vez",
"Time the item was last modified": "Hora de última modificación del elemento",
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "Para conectarse con el dispositivo Syncthing llamado \"{{devicename}}\", añada un nuevo dispositivo remoto en su extremo con este ID:",
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Para permitir una regla, marque la casilla. Para denegar una regla, déjela sin marcar.",
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Para permitir una regla, marca la casilla. Para denegar una regla, déjala sin marcar.",
"Today": "Hoy",
"Trash Can": "Papelera",
"Trash Can File Versioning": "Versionado de archivos de la papelera",
"Trash Can File Versioning": "Papelera",
"Type": "Tipo",
"UNIX Permissions": "Permisos de UNIX",
"Unavailable": "No disponible",
"Unavailable/Disabled by administrator or maintainer": "No disponible/Deshabilitado por el administrador o mantenedor",
"Undecided (will prompt)": "No decidido (se preguntará)",
"Unexpected Items": "Elementos Inesperados",
"Unexpected items have been found in this folder.": "Se han encontrado Elementos Inesperados en esta carpeta.",
"Unexpected Items": "Elementos inesperados",
"Unexpected items have been found in this folder.": "Se han encontrado elementos inesperados en esta carpeta.",
"Unignore": "Dejar de ignorar",
"Unknown": "Desconocido",
"Unshared": "No compartido",
"Unshared Devices": "Dispositivos no Enlazados",
"Unshared Folders": "Carpetas no Compartidas",
"Untrusted": "No Confiable",
"Unshared Devices": "No compartida con",
"Unshared Folders": "Carpetas no compartidas",
"Untrusted": "No confiable",
"Up to Date": "Actualizado",
"Updated {%file%}": "Actualizado {{file}}",
"Upgrade": "Actualizar",
@@ -492,11 +492,11 @@
"Upload Rate": "Velocidad de subida",
"Uptime": "Tiempo de funcionamiento",
"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)",
"Use notifications from the filesystem to detect changed items.": "Usar notificaciones del sistema de archivos para detectar elementos cambiados.",
"Use HTTPS for GUI": "Usar HTTPS para la interfaz gráfica",
"Use notifications from the filesystem to detect changed items.": "Usa las notificaciones del sistema de archivos para detectar elementos cambiados.",
"User": "Usuario",
"User Home": "Carpeta de inicio del usuario",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "No se ha configurado el nombre de usuario/la contraseña para la autenticación de la GUI. Por favor, considere configurarlos.",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "No se ha configurado el nombre de usuario/la contraseña para la autenticación de la GUI. Por favor, considera configurarlos.",
"Using a QUIC connection over LAN": "Usando una conexión QUIC a través de una LAN",
"Using a QUIC connection over WAN": "Usando una conexión QUIC a través de una WAN",
"Using a direct TCP connection over LAN": "Utilizar una conexión TCP directa a través de LAN",
@@ -504,44 +504,44 @@
"Version": "Versión",
"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.",
"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 eliminan automáticamente si son más antiguas que la antigüedad máxima o se excede el número de archivos permitidos en un intervalo.",
"Waiting to Clean": "Esperando para Limpiar",
"Waiting to Scan": "Esperando para Escanear",
"Waiting to Sync": "Esperando para Sincronizar",
"Warning": "Advertencia",
"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%}).": "Advertencia, esta ruta es una carpeta principal de una carpeta 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.": "Advertencia: Si estás utilizando un observador externo como {{syncthingInotify}}, debes asegurarte de que está desactivado.",
"Watch for Changes": "Monitorear los cambios",
"Watching for Changes": "Vigilando los cambios",
"Watching for changes discovers most changes without periodic scanning.": "El control de cambios descubre la mayoría de cambios sin el escaneo periódico.",
"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.",
"When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.": "Cuando se configura en más de uno o en ambos dispositivos, Syncthing intentará establecer múltiples conexiones simultáneamente. Si los valores difieren, se utilizará el más alto. Pon cero para que Syncthing decida por ti.",
"Waiting to Scan": "Esperando para escanear",
"Waiting to Sync": "Esperando para sincronizar",
"Warning": "Atención",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Atención, 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%}).": "Atención, esta ruta es una carpeta principal de una carpeta existente \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Atención, esta ruta es un subdirectorio de una carpeta ya existente llamada \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Atención, 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.": "Atención: si estás utilizando un observador externo como {{syncthingInotify}}, debes asegurarte de que está desactivado.",
"Watch for Changes": "Detectar cambios",
"Watching for Changes": "Detectando cambios",
"Watching for changes discovers most changes without periodic scanning.": "La detección de cambios descubre la mayoría de cambios sin el escaneo periódico.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Cuando añadas un nuevo dispositivo, ten 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ñadas una nueva carpeta, ten 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.",
"When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.": "Cuando se configura a más de una en ambos dispositivos, Syncthing intentará establecer múltiples conexiones simultáneamente. Si los valores difieren se usará el más alto. Deja a cero para que Syncthing decida.",
"Yes": "Si",
"Yesterday": "Ayer",
"You can also copy and paste the text into a new message manually.": "También puedes copiar y pegar manualmente el texto en un nuevo mensaje.",
"You can also select one of these nearby devices:": "También puede seleccionar 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 have no ignored devices.": "No tienes dispositivos ignorados.",
"You have no ignored folders.": "No tienes carpetas ignoradas.",
"You have unsaved changes. Do you really want to discard them?": "Tienes cambios sin guardar. ¿Quieres descartarlos realmente?",
"You must keep at least one version.": "Debes mantener al menos una versión.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Nunca debe agregar o cambiar nada localmente en una carpeta \"{{receiveEncrypted}}\".",
"You can read more about the two release channels at the link below.": "Puedes leer más sobre los dos canales de publicación de versiones en el siguiente enlace.",
"You have no ignored devices.": "No hay dispositivos ignorados.",
"You have no ignored folders.": "No hay carpetas ignoradas.",
"You have unsaved changes. Do you really want to discard them?": "Hay cambios sin guardar. ¿Estás seguro de que quieres descartarlos?",
"You must keep at least one version.": "Debes conservar al menos una versión.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Nunca debes añadir o cambiar nada localmente en una carpeta \"{{receiveEncrypted}}\".",
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Tu aplicación de SMS debería abrirse para permitirte elegir el destinatario y enviarlo desde tu propio número.",
"Your email app should open to let you choose the recipient and send it from your own address.": "Tu aplicación de correo electrónico debería abrirse para permitirte elegir el destinatario y enviarlo desde tu propia dirección.",
"Your email app should open to let you choose the recipient and send it from your own address.": "Tu aplicación de email debería abrirse para permitirte elegir el destinatario y enviarlo desde tu propia dirección.",
"days": "días",
"deleted": "eliminado",
"deny": "denegar",
"directories": "directorios",
"file": "fichero",
"file": "archivo",
"files": "archivos",
"folder": "carpeta",
"full documentation": "Documentación completa",
"items": "Elementos",
"items": "elementos",
"modified": "modificado",
"permit": "permiso",
"seconds": "segundos",
@@ -556,5 +556,5 @@
"unknown device": "dispositivo desconocido",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quiere compartir la carpeta \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} puede reintroducir este dispositivo."
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} puede presentar de nuevo este dispositivo."
}

View File

@@ -6,25 +6,53 @@
"About": "Rakenduse teave",
"Action": "Tegevus",
"Actions": "Tegevused",
"Active filter rules": "Aktiivsed filtrireeglid",
"Add": "Lisa",
"Add Device": "Lisa seade",
"Add Folder": "Lisa kaust",
"Add Remote Device": "Lisa kaugseade",
"Add filter entry": "Lisa filtrikirje",
"Add ignore patterns": "Lisa eiramismustreid",
"Add new folder?": "Kas lisad uue kausta?",
"Address": "Aadress",
"Addresses": "Aadressid",
"Advanced": "Täiendavad seadistused",
"Advanced Configuration": "Täiendavad seadistused",
"All Data": "Kõik andmed",
"All Time": "Kõik ajad",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Kõik selle seadmega jagatud kaustad peavad olema kaitstud salasõnaga, mis tähendab, et edastatud andmed on ilma salasõnata loetamatud.",
"Allow Anonymous Usage Reporting?": "Kas lubad anonüümset statistikakogumist rakenduse kasutamise kohta?",
"Allowed Networks": "Lubatud võrgud",
"Alphabetic": "Tähestikuline",
"Always turned on when the folder type is \"{%foldertype%}\".": "Kui kausta tüüp on „{{foldertype}}“, siis on alati lülitatud sisse.",
"Anonymous Usage Reporting": "Anonüümne aruandlus kasutuse kohta",
"Applied to LAN": "Kehtib kohtvõrgu puhul",
"Apply": "Rakenda",
"Are you sure you want to override all remote changes?": "Kas sa soovid sürjutada kõik eemalt tehtud muudatused?",
"Are you sure you want to permanently delete all these files?": "Kas sa oled kindel, et soovid jäädavalt kõik need failid kustutada?",
"Are you sure you want to remove device {%name%}?": "Kas sa oled kindel, et soovid {{name}} seadme eemaldada?",
"Are you sure you want to remove folder {%label%}?": "Kas sa oled kindel, et soovid {{label}} kausta eemaldada?",
"Are you sure you want to restore {%count%} files?": "Kas sa oled kindel, et soovid {{count}} faili taastada?",
"Are you sure you want to revert all local changes?": "Kas sa oled kindel, et soovid kõik kohalikud muudatused tagasi pöörata?",
"Are you sure you want to upgrade?": "Kas sa oled kindel, et soovid uuendamise ette võtta?",
"Authentication Required": "Autentimine on vajalik",
"Authors": "Autorid",
"Auto Accept": "Nõustu automaatselt",
"Automatic Crash Reporting": "Automaatne teavitus rakenduste kokkujooksmise kohta",
"Automatic upgrades": "Automaatsed uuendused",
"Automatic upgrades are always enabled for candidate releases.": "Automaatsed uuendused on kandidaatversioonide puhul alati lubatud.",
"Be careful!": "Ettevaatust!",
"Body:": "Kirja sisu:",
"Bugs": "Vead",
"Cancel": "Katkesta",
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Kui kausta tüüp on „{{foldertype}}“, siis seda ei saa kasutada.",
"Changelog": "Muudatuste ajalugu",
"Clean out after": "Puhasta peale järgneva aja möödumist",
"Cleaning Versions": "Kustutan versioone",
"Cleanup Interval": "Kustutamise välp",
"Click to see full identification string and QR code.": "Täispikka tunnust ja QR-koodi näed, kui klõpsad siin.",
"Close": "Sulge",
"Command": "Käsk",
"Compression": "Pakkimine",
"Configuration Directory": "Seadistuste kaust",
"Configuration File": "Seadistuste fail",
@@ -37,50 +65,113 @@
"Copied from elsewhere": "Kopeeritud mujalt",
"Copied from original": "Kopeeritud algallikast",
"Copied!": "Kopeeritud!",
"Copy": "Kopeeri",
"Copy failed! Try to select and copy manually.": "Kopeerimine ei õnnestunud! Proovi valida ja kopeerida käsitsi.",
"Currently Shared With Devices": "Hetkel jagatud seadmetega",
"Custom Range": "Sinu valitud vahemik",
"Danger!": "Ohtlik!",
"Database Location": "Andmebaasi asukoht",
"Debug": "Veaotsing",
"Debugging Facilities": "Teadete tüübid veaotsingul",
"Default": "Vaikimisi",
"Default Configuration": "Vaikimisi seadistus",
"Default Device": "Vaikimisi seade",
"Default Folder": "Vaikimisi kaust",
"Default Ignore Patterns": "Vaikimisi eiramismustrid",
"Defaults": "Vaikimisi väärtused",
"Delete": "Kustuta",
"Deleted {%file%}": "Kustutasin {{file}} faili",
"Deselect All": "Eemalda kogu valik",
"Deselect devices to stop sharing this folder with.": "Eemalda seadmed, millega sa enam ei taha seda kausta jagada.",
"Deselect folders to stop sharing with this device.": "Eemalda kaustad, mida sa enam ei taha selle seadmega jagada.",
"Device": "Seade",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Seade \"{{name}}\" ({{device}} aadressil {{address}}) soovib luua ühendust. Kas lisada uus seade?",
"Device Certificate": "Seadme sertifikaat",
"Device ID": "Seadme tunnus",
"Device Identification": "Seadme tuvastamine",
"Device Name": "Seadme nimi",
"Device Status": "Seadme olek",
"Device is untrusted, enter encryption password": "Seadme pole usaldusväärne, palun sisesta krüptimise salasõna",
"Device rate limits": "Seadme kiiruspiirangud",
"Device that last modified the item": "Sead, kus objekt oli viimati muudetud",
"Devices": "Seadmed",
"Disable Crash Reporting": "Lülita rakenduse kokkujooksmisest teavitamine välja",
"Disabled": "Pole kasutusel",
"Discard": "Loobu",
"Disconnected": "Ühendus puudub",
"Disconnected (Inactive)": "Ühendus on katkestatud (pole aktiivne)",
"Disconnected (Unused)": "Ühendus on katkestatud (pole kasutatud)",
"Discovered": "Tuvastatud",
"Discovery": "Avastamine",
"Discovery Failures": "Vead avastamisel",
"Discovery Status": "Avastamise olek",
"Dismiss": "Loobu",
"Do not restore": "Ära taasta",
"Do not restore all": "Ära taasta kõiki",
"Do you want to enable watching for changes for all your folders?": "Kas sa tahad lülitada sisse kõikide oma kaustade muudatuste jälgimise?",
"Documentation": "Dokumentatsioon",
"Download Rate": "Allalaadimise Kiirus",
"Downloaded": "Alla laetud",
"Download Rate": "Allalaadimise kiirus",
"Downloaded": "Allalaaditud",
"Downloading": "Allalaadimine",
"Edit": "Muuda",
"Edit Device": "Muuda Seadet",
"Edit Device": "Muuda seadet",
"Edit Device Defaults": "Muuda seadme vaikeseadistusi",
"Edit Folder": "Muuda Kausta",
"Edit Folder Defaults": "Muuda kaustade vaikimisi seadistusi",
"Editing {%path%}.": "{{path}} muutmine.",
"Enable Crash Reporting": "Võta kasutusele rakenduse kokkujooksmistest teatamine",
"Enable NAT traversal": "Luba NAT traversal",
"Enable Relaying": "Luba edastussõlmede kasutamine",
"Enabled": "Kasutusel",
"Error": "Viga",
"Extended Attributes": "Täiendavad atribuudid",
"External": "Väline",
"External File Versioning": "Väline failide versioonihaldus",
"Failed to load file versions.": "Failiversioonide laadimine ei õnnestunud.",
"Failed to load ignore patterns.": "Eiramismustrite laadimine ei õnnestunud.",
"Failed to set up, retrying": "Seadistamine ei õnnestunud, proovin uuesti",
"File Pull Order": "Failide Tirimise Järjekord",
"File Versioning": "Failide versioonihaldus",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Failid liigutatakse asendamisel või kustutamisel .stversions kataloogi.",
"Filter by date": "Filtreeri kuupäeva alusel",
"Filter by name": "Filtreeri nime alusel",
"Folder": "Kaust",
"Folder ID": "Kausta ID",
"Folder Label": "Kausta Silt",
"Folder Type": "Kausta Tüüp",
"Folder Path": "Kausta asukoht",
"Folder Status": "Kausta olek",
"Folder Type": "Kausta tüüp",
"Folders": "Kaustad",
"Forever": "Igavesti",
"Full Rescan Interval (s)": "Täiemahulise kordusskaneerimise välp (sek)",
"GUI": "Kasutajaliides",
"GUI Authentication Password": "GUI Autentimise Salasõna",
"GUI Authentication Password": "Salasõna kasutajaliidese autentimiseks",
"GUI Authentication User": "GUI Autentimise Kasutajatunnus",
"GUI Authentication: Set User and Password": "Kasutajaliidese autentimine: sisesta kasutajanimi ja salasõna",
"GUI Listen Address": "Aadress, mida kasutajaliides kuulab",
"GUI Override Directory": "Kasutajaliidese sürjutamise kaust",
"GUI Theme": "GUI Teema",
"Generate": "Genereeri",
"Global Discovery": "Üldine avastamine",
"Global State": "Üldine olek",
"Help": "Abiteave",
"Home page": "Avaleht",
"Identification": "Tunnus",
"If untrusted, enter encryption password": "Kui usaldusväärsus on veel kinnitamata, siis sisesta krüptosalasõna",
"Ignore": "Ignoreeri",
"Ignore Patterns": "Ignoreeri Mustreid",
"Ignore Patterns": "Eira mustreid",
"Ignore Permissions": "Ignoreeri Õigusi",
"Ignored Devices": "Eiratud seadmed",
"Ignored Folders": "Eiratud kaustad",
"Ignored at": "Eiratud seadmes",
"Included Software": "Kaasa arvatud tarkvara",
"Incoming Rate Limit (KiB/s)": "Siseneva liikluse kiiruspiirang (KiB/s)",
"Incorrect user name or password.": "Vigane kasutajanimi või salasõna.",
"Info": "Teave",
"Introduction": "Sissejuhatus",
"Keep Versions": "Säilita Versioone",
"LDAP": "LDAP",
"Largest First": "Suurim Enne",
"Largest First": "Esmalt suuremad",
"Last 30 Days": "Viimased 30 päeva",
"Last 7 Days": "Viimased 7 päeva",
"Last Month": "Viimane kuu",
@@ -89,74 +180,236 @@
"Latest Change": "Viimane Muudatus",
"Learn more": "Veel infot",
"Learn more at {%url%}": "Lisateavet leiad siit: {{url}}",
"Local State": "Kohalik Olek",
"Local State (Total)": "Kohalik Olek (Summaarne)",
"Limit": "Piirang",
"Limit Bandwidth in LAN": "Piira ribalaiust kohtvõrgus",
"Loading data...": "Laadin andmeid…",
"Loading...": "Laadin...",
"Local Additions": "Kohalikud lisandused",
"Local Discovery": "Kohalik avastamine",
"Local State": "Kohalik olek",
"Local State (Total)": "Kohalik olek (summaarne)",
"Locally Changed Items": "Kohalikus seadmes muudetud objektid",
"Log": "Logi",
"Log File": "Logifail",
"Log In": "Logi sisse",
"Log Out": "Logi välja",
"Log in to see paths information.": "Asukohtade teavet näed peale sisselogimist.",
"Log in to see version information.": "Versiooniteavet näed peale sisselogimist.",
"Log tailing paused. Scroll to the bottom to continue.": "Logi lõpu täiendamine on peatunud. Jätkamiseks keri väljund lõpuni.",
"Login failed, see Syncthing logs for details.": "Sisselogimine ei õnnestunud. Üksikasjalikku teavet leiad Syncthingi logidest.",
"Logs": "Logid",
"Major Upgrade": "Suurem versiooniuuendus",
"Mass actions": "Pakktöötlused",
"Maximum Age": "Maksimaalne vanus",
"Maximum single entry size": "Maksimaalne ühe objekti maht",
"Maximum total size": "Maksimaalne kogumaht",
"Metadata Only": "Ainult metaandmed",
"Minimum Free Disk Space": "Minimaalne Vaba Kettaruum",
"Mod. Device": "Muudetud seadmes",
"Mod. Time": "Muutmise aeg",
"More than a month ago": "Enam, kui kuu tagasi",
"More than a week ago": "Enam, kui nädal tagasi",
"More than a year ago": "Enam, kui aasta tagasi",
"Move to top of queue": "Liiguta järjekorra algusesse",
"Multi level wildcard (matches multiple directory levels)": "Mitmetasandiline metamärk (vastab mitmele kaustapuu tasandile)",
"Never": "Eikunagi",
"New Device": "Uus Seade",
"New Folder": "Uus Kaust",
"Newest First": "Uusimad Ennem",
"Newest First": "Esmalt uuemad",
"No": "Ei",
"No File Versioning": "Failide versioonihaldus puudub",
"No files will be deleted as a result of this operation.": "Selle tegevuse tulemusel ei kustutata ühtegi faili.",
"No rules set": "Ühtegi reeglit pole määratud",
"No upgrades": "Uuendusi pole",
"Not shared": "Pole jagatud",
"Notice": "Märkus",
"Number of Connections": "Ühenduste arv",
"OK": "Sobib",
"Oldest First": "Vanimad Ennem",
"Off": "Pole kasutusel",
"Oldest First": "Esmalt vanemad",
"Optional descriptive label for the folder. Can be different on each device.": "Kausta kirjeldav silt, kui vajad seda. Võib olla igas seadmes erinev.",
"Options": "Valikud",
"Out of Sync": "Pole sünkroonis",
"Out of Sync Items": "Sünkroonimata objektid",
"Outgoing Rate Limit (KiB/s)": "Väljuva kiiruse piirang (KiB/s)",
"Override Changes": "Kirjuta Muudatused Üle",
"Override": "rjutamine",
"Override Changes": "Sürjuta muudatused",
"Ownership": "Omand",
"Password": "Salasõna",
"Path": "Asukoht",
"Paths": "Asukohad",
"Pause": "Peata",
"Pause All": "Peata Kõik",
"Paused": "Peatatud",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Palun seadista GUI Autentimise Kasutajatunnus ning Salasõna Seadistuste dialoogist.",
"Paused (Unused)": "Peatatud (pole kasutatud)",
"Pending changes": "Ootel muudatused",
"Please consult the release notes before performing a major upgrade.": "Enne suurema versiooniuuenduse tegemist palun loe muudatuste logi ja uue versiooni teavet.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Kasutajaliidese autentimiseks sisesta seadistuste vaatest kasutajanimi ja salasõna.",
"Please wait": "Palun oota",
"Preparing to Sync": "Valmistun sünkroonima",
"Preview": "Eelvaade",
"Preview Usage Report": "Kasutusaruande eelvaade",
"QR code": "QR-kood",
"QUIC LAN": "QUIC LAN",
"QUIC WAN": "QUIC WAN",
"Random": "Juhuslik",
"Receive Encrypted": "Vastuvõtmine krüptituna",
"Receive Only": "Ainult vastuvõtmine",
"Received data is already encrypted": "Vastuvõetud andmed on juba krüptitud",
"Recent Changes": "Hiljutised muudatused",
"Reduced by ignore patterns": "Hiljutised eiramismustrid",
"Release Notes": "Muudatuste logi",
"Remote Devices": "Kaugseadmed",
"Remote GUI": "Kaugseadme graafiline kasutajaliides",
"Remove": "Eemalda",
"Remove Device": "Kaugseade",
"Remove Folder": "Kaugkaust",
"Required identifier for the folder. Must be the same on all cluster devices.": "Kohustuslik kausta identifikaator. Peab olema sama kõigil klastri seadmetel.",
"Rescan": "Skaneeri uuesti",
"Rescan All": "Skaneeri kõik uuesti",
"Rescans": "Uuesti skaneerimised",
"Restart": "Taaskäivita",
"Restart Needed": "Taaskäivitamine Vajalik",
"Restarting": "Taaskäivitamine",
"Restore": "Taasta",
"Restore Versions": "Taasta versioone",
"Resume": "Jätka",
"Resume All": "Jätka Kõik",
"Reused": "Uuesti kasutatud",
"Revert": "Pööra muudatus tagasi",
"Revert Local Changes": "Pööra kohalikud muudatused tagasi",
"Save": "Salvesta",
"Saving changes": "Salvestan muudatusi",
"Scan Time Remaining": "Järelejäänud skaneerimisaeg",
"Scanning": "Skaneerin",
"Select All": "Vali kõik",
"Select a version": "Vali versioon",
"Select additional devices to share this folder with.": "Vali täiendavad seadmed, millega tahad seda kausta jagada.",
"Select additional folders to share with this device.": "Vali täiendavad kaustad, mida tahad selle seadmega jagada.",
"Select latest version": "Vali viimane versioon",
"Select oldest version": "Vali vanim versioon",
"Send & Receive": "Saatmine ja vastuvõtmine",
"Send Only": "Ainult saatmine",
"Send Ownership": "Edasta omand",
"Settings": "Seadistused",
"Share": "Jaga",
"Share Folder": "Jaga Kausta",
"Share this folder?": "Kas jagada seda kausta?",
"Share by Email": "Jaga e-kirjaga",
"Share by SMS": "Jaga SMS-iga",
"Share this folder?": "Kas tahad jagada seda kausta?",
"Shared Folders": "Jagatud kaustad",
"Shared With": "Jagatud järgnevalt",
"Sharing": "Jagamine",
"Show ID": "Kuva ID",
"Show QR": "Kuva QR",
"Shut Down": "Lülita välja",
"Shutdown Complete": "Väljalülitamine on lõppenud",
"Simple": "Lihtne",
"Simple File Versioning": "Lihtne Faili Versioonindus",
"Size": "Suurus",
"Smallest First": "Esmalt väiksemad",
"Some items could not be restored:": "Mõne objekti taastamine polnud võimalik:",
"Source Code": "Lähtekood",
"Stable releases only": "Ainult stabiilsed väljalasked",
"Staggered": "Järkjärguline",
"Start Browser": "Käivita Brauser",
"Statistics": "Statistika",
"Stay logged in": "Jää sisselogituks",
"Stopped": "Peatatud",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Salvestab ja sünkroonib vaid krüptitud andmeid. Kaustad kõikides teistes seadmetes peavad olema seadistatud sama salasõnaga ning nende tüüp peab olema ka „{{receiveEncrypted}}“.",
"Subject:": "Teema:",
"Support": "Abi",
"Support Bundle": "Veaotsingu tugipakett",
"Sync Ownership": "Sünkrooni omand",
"Sync Status": "Sünkroonimise olek",
"Syncing": "Sünkroniseerimine",
"Syncthing is restarting.": "Syncthing taaskäivitub.",
"Syncthing is saving changes.": "Syncthing salvestab muudatusi.",
"Syncthing is upgrading.": "Syncthing uueneb.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ei suutnud sinu päringut töödelda. Probleemi püsimisel värskenda lehte või taaskäivita Syncthing.",
"The device ID cannot be blank.": "Seadme ID ei tohi olla tühi.",
"TCP LAN": "Kohtvõrgu TCP",
"TCP WAN": "Laivõrgu TCP",
"The Syncthing Authors": "Syncthingi autorid",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthingi haldusliides on seadistatud lubamaks ligipääsu ilma salasõnata.",
"The aggregated statistics are publicly available at the URL below.": "Koondstatistika on avalikult saadaval järgneval lehel.",
"The device ID cannot be blank.": "Seadme tunnus ei tohi olla tühi.",
"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.": "Krüptitud kasutusaruanne saadetakse kord päevas. Sellega peetakse arvet kasutatavate platvormide, rakenduste versioonide ja kaustade suuruste üle. Kui varemteatatud andmekogu muutub, siis sa näed seda vaadet uuesti.",
"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.": "Seadme tunnus ei tundu olema korrektne. Ta peaks olema 52 või 56 märki pikk, kus leidub vaid numbreid ja tähemärke ning võib olla ka tühikuid ja kriipse.",
"The folder ID cannot be blank.": "Kausta ID ei tohi olla tühi.",
"The folder ID must be unique.": "Kausta ID peab olema unikaalne.",
"The folder path cannot be blank.": "Kausta asukoht ei tohi olla tühi.",
"The following items could not be synchronized.": "Järgnevaid üksusi ei õnnestunud sünkroniseerida.",
"The maximum age must be a number and cannot be blank.": "Maksimaalne vanus peab olema arv ning ei tohi olla tühi.",
"The number of connections must be a non-negative number.": "Ühenduste arv peab olema nullist suurem number.",
"The number of days must be a number and cannot be blank.": "Päevade arv peab olema number ega tohi jääda tühjaks.",
"The number of days to keep files in the trash can. Zero means forever.": "Päevade arv failide hoidmiseks prügikastis. Null (0) tähistab piiranguteta hoidmist.",
"The number of old versions to keep, per file.": "Alleshoitavate faili vanade versioonide arv.",
"The number of versions must be a number and cannot be blank.": "Versioonide arv peab olema number ega tohi jääda tühjaks.",
"The path cannot be blank.": "Asukoht ei saa jääda tühjaks.",
"There are no devices to share this folder with.": "Pole ühtegi seadet, millega saaks seda kausta jagada.",
"There are no file versions to restore.": "Pole ühtegi taastatavat failiversiooni.",
"There are no folders to share with this device.": "Pole ühtegi kausta, mida selle seadmega jagada.",
"This Device": "See seade",
"This Month": "Sel kuul",
"This is a major version upgrade.": "Tegemist on suurema versiooniuuendusega.",
"Time": "Aeg",
"Time the item was last modified": "Aeg, millal objekti viimati muudeti",
"Today": "Täna",
"Trash Can": "Prügikast",
"Type": "Tüüp",
"UNIX Permissions": "UNIX-i õigused",
"Unavailable": "Pole saadaval",
"Unexpected Items": "Ootamatud ja plaanivälised objektid",
"Unignore": "Lõpeta eiramine",
"Unknown": "Teadmata",
"Upload Rate": "Üleslaadimise Kiirus",
"Unshared": "Pole jagatud",
"Unshared Devices": "Mittejagatud seadmed",
"Unshared Folders": "Mittejagatud kaustad",
"Untrusted": "Pole usaldusväärne",
"Up to Date": "Uuendatud ja sünkroonis",
"Updated {%file%}": "{{file}} fail on uuendatud",
"Upgrade": "Uuenda",
"Upgrade To {%version%}": "Uuenda versioonini {{version}}",
"Upgrading": "Uuendan",
"Upload Rate": "Üleslaadimise kiirus",
"Uptime": "Kasulik tööaeg",
"Use HTTPS for GUI": "Kasuta HTTPS'i GUI jaoks",
"User": "Kasutaja",
"User Home": "Kasutaja kodukaust",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Kasutajaliidese autentimiseks pole lisatud kasutajanime ja salasõna. Me soovitame, et kindlasti teed seda.",
"Version": "Versioon",
"Versions": "Versioonid",
"Versions Path": "Versioonide asukoht",
"Warning": "Hoiatus",
"Watch for Changes": "Jälgi muudatusi",
"Watching for Changes": "Jälgin muudatusi",
"Yes": "Jah",
"Yesterday": "Eile",
"You have no ignored devices.": "Sul pole ühtegi eiratud seadet.",
"You have no ignored folders.": "Sul pole ühtegi eiratud kausta.",
"You have unsaved changes. Do you really want to discard them?": "Sul on salvestamata muudatusi. Kas sa kindlasti tahad neist loobuda?",
"You must keep at least one version.": "Sa pead alles hoidma vähemalt ühe versiooni.",
"days": "päeva",
"deleted": "kustutatud",
"deny": "keela",
"directories": "kaustad",
"file": "fail",
"files": "failid",
"folder": "kaust",
"full documentation": "täisdokumentatsioon",
"items": "objektid",
"modified": "muudetud",
"permit": "luba",
"seconds": "sekundit",
"theme": {
"name": {
"black": "Süsimust kujundus",
"dark": "Tume kujundus",
"default": "Vaikimisi kujundus",
"light": "Hele kujundus"
}
},
"unknown device": "tundmatu seade",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} soovib jagada kausta \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} soovib jagada kausta \"{{folderlabel}}\" ({{folder}})."
}

View File

@@ -11,18 +11,18 @@
"Add Device": "Engadir dispositivo",
"Add Folder": "Engadir cartafol",
"Add Remote Device": "Engadir dispositivo remoto",
"Add devices from the introducer to our device list, for mutually shared folders.": "Engadir dispositivos desde o enviador ao noso dispositivo, para cartafois mutuamente compartidos.",
"Add devices from the introducer to our device list, for mutually shared folders.": "Engadir dispositivos desde o enviador ao noso dispositivo, para cartafoles mutuamente compartidos.",
"Add filter entry": "Engadir unha entrada ao filtro",
"Add ignore patterns": "Engadir patróns a ignorar",
"Add new folder?": "Engadir novo cartafol?",
"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.": "Ademais, aumentarase o lapso de reescaneo completo (60 veces, é dicir, novo por defecto dunha hora). Tamén pode configuralo de xeito manual para cada cartafol logo de escoller No.",
"Address": "Enderezo",
"Addresses": "Enderezos",
"Advanced": "Avanzado",
"Advanced": "Avanzada",
"Advanced Configuration": "Configuración avanzada",
"All Data": "Todos os datos",
"All Time": "Todo o tempo",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Todos os cartafois compartidos con este dispositivo teñen que estar protexidos por un contrasinal, de modo que os datos enviados sexan ilexibles sen o constrasinal indicado.",
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Todos os cartafoles compartidos con este dispositivo teñen que estar protexidos por un contrasinal, de xeito que os datos enviados non se poidan ler sen o constrasinal.",
"Allow Anonymous Usage Reporting?": "Permitir o informe de uso anónimo?",
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabética",
@@ -47,7 +47,7 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Agora a actualización automática permite escoller entre versións estables e versións candidatas.",
"Automatic upgrades": "Actualizacións automáticas",
"Automatic upgrades are always enabled for candidate releases.": "As actualizacións automáticas sempre están activadas para versións candidatas.",
"Automatically create or share folders that this device advertises at the default path.": "Crear ou compartir automaticamente os cartafoles na ruta predeterminada que este dispositivo anuncia.",
"Automatically create or share folders that this device advertises at the default path.": "Crear ou compartir automaticamente na ruta predeterminada os cartafoles que este dispositivo anuncia.",
"Available debug logging facilities:": "Ferramentas de depuración dispoñibles:",
"Be careful!": "Teña coidado!",
"Body:": "Corpo:",
@@ -95,7 +95,7 @@
"Deleted {%file%}": "Eliminado {{file}}",
"Deselect All": "Deseleccionar Todo",
"Deselect devices to stop sharing this folder with.": "Deleccionar os dispositivos cos que deixar de compartir este cartafol.",
"Deselect folders to stop sharing with this device.": "Deseleccionar os cartafois que deixar de compartir con este dispositivo.",
"Deselect folders to stop sharing with this device.": "Deseleccionar os cartafoles que deixen de compartirse con este dispositivo.",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "O dispositivo \"{{name}}\" ({{device}} en {{address}}) quere conectarse. Engadir o dispositivo?",
"Device Certificate": "Certificado do Dispositivo",
@@ -125,7 +125,7 @@
"Do not add it to the ignore list, so this notification may recur.": "Non engadilo á lista de ignorados, para que esta notificación poida repetirse.",
"Do not restore": "Non restaurar",
"Do not restore all": "Non restablecer todo",
"Do you want to enable watching for changes for all your folders?": "Quere habilitar o control de cambios para todos os seus cartafois?",
"Do you want to enable watching for changes for all your folders?": "Queres activar o control de cambios para todos os teus cartafoles?",
"Documentation": "Documentación",
"Download Rate": "Velocidade de Descarga",
"Downloaded": "Descargado",
@@ -134,7 +134,7 @@
"Edit Device": "Editar o Dispositivo",
"Edit Device Defaults": "Editar predeterminados do dispositivo",
"Edit Folder": "Editar o Cartafol",
"Edit Folder Defaults": "Editar predeterminados dos cartafoles",
"Edit Folder Defaults": "Editar as predeterminacións dos Cartafoles",
"Editing {%path%}.": "Editando {{path}}.",
"Enable Crash Reporting": "Activar informar dos fallos",
"Enable NAT traversal": "Habilitar o NAT traversal",
@@ -146,14 +146,14 @@
"Enables sending ownership information to other devices, but not applying incoming ownership information. This can have a significant performance impact. Always enabled when \"Sync Ownership\" is enabled.": "Activa o envío a outros dispositivos de información sobre a propiedade, pero non aplica información entrante sobre a propiedade. Isto pode afectar en gran medida ao rendemento. Está sempre activado cando \"Sincronización da propiedade\" está activada.",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introduza un número non negativo (por exemplo, \"2.35\") e seleccione unha unidade. As porcentaxes son como partes totais do tamaño do disco.",
"Enter a non-privileged port number (1024 - 65535).": "Introduza un número de porto non privilexiado (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza direccións separadas por comas (\"tcp://ip:porto\", \"tcp://host:porto\") ou \"dynamic\" para realizar o descubrimento automático da dirección.",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza enderezos separados por comas (\"tcp://ip:porto\", \"tcp://host:porto\") ou \"dynamic\" para realizar o descubrimento automático do enderezo.",
"Enter ignore patterns, one per line.": "Introduza patróns a ignorar, un por liña.",
"Enter up to three octal digits.": "Introduza ata tres díxitos octais.",
"Error": "Erro",
"Extended Attributes": "Atributos Estendidos",
"Extended Attributes Filter": "Filtro de Atributos Estendidos",
"External": "Externo",
"External File Versioning": "Versionado de Fichiro Externo",
"External File Versioning": "Versionado de Ficheiro Externo",
"Failed Items": "Elmentos fallados",
"Failed to load file versions.": "Fallou a carga das versións dos ficheiros.",
"Failed to load ignore patterns.": "Fallou a carga de patróns ignorados.",
@@ -162,6 +162,10 @@
"File Pull Order": "Orde de Obtención de Arquivos",
"File Versioning": "Versionado de Ficheiros",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Os ficheiros móvense ao directorio .stversions cando se substitúen ou eleminan con Syncthing.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Os ficheiros móvense a unha versión marcada coa data no directorio .stversions cando Syncthing os substitúe ou elimina.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "O ficheiros están protexidos dos cambios realizados por outros dispositivos, pero os cambios neste dispositivo serán sincronizados co resto do grupo.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Os ficheiros están sincronizados desde o grupo, pero os cambios realizados localmente non se envían aos outros dispositivos.",
"Filesystem Watcher Errors": "Vixiante de erros no sistema de ficheiros",
"Filter by date": "FIltrar por data",
"Filter by name": "Filtrar por nome",
"Folder": "Cartafol",
@@ -171,16 +175,18 @@
"Folder Status": "Estado do Cartafol",
"Folder Type": "Tipo do Cartafol",
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "O tipo de cartafol \"{{receiveEncrypted}}\" so pode ser establecido ao engadir un cartafol novo.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "O tipo de cartafol \"{{receiveEncrypted}}\" non se pode cambiar despois de engadir o cartafol. Ten que eliminar o cartafol, eliminar ou desencriptar os datos do disco e volver a engadirlo.",
"Folders": "Cartafois",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "O tipo de cartafol \"{{receiveEncrypted}}\" non se pode cambiar despois de engadir o cartafol. Ten que eliminar o cartafol, eliminar ou descifrar os datos do disco e volver a engadirlo.",
"Folders": "Cartafoles",
"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.": "Houbo un erro nos seguintes cartafoles ao comezar a comprobar os cambios. Vaise reintentar cada minutos, polo que os erros pronto poderían desaparecer. Se persisten, intente solucionar o problema de base e pida axuda se non pode arranxalo.",
"Forever": "Para sempre",
"Full Rescan Interval (s)": "Intervalo de Escaneamento Completo (s)",
"GUI": "GUI",
"GUI": "Interface",
"GUI / API HTTPS Certificate": "Certificado HTTPS GUI/API",
"GUI Authentication Password": "Contrasinal de Autenticación da GUI",
"GUI Authentication User": "Usuario de Autenticación da GUI",
"GUI Authentication: Set User and Password": "Autenticación da GUI: Establecer o Usuario e o Contrasinal",
"GUI Listen Address": "Dirección de Escoita da GUI",
"GUI Listen Address": "Enderezo de Escoita da GUI",
"GUI Override Directory": "Interface do directorio de sobrescritura",
"GUI Theme": "Tema da GUI",
"General": "Xeral",
"Generate": "Xerar",
@@ -189,10 +195,26 @@
"Global State": "Estado Global",
"Help": "Axuda",
"Home page": "Páxina de inicio",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Emporiso, os axustes actuais indican que igual non quere activalo. Desactivamos para vostede o aviso automático de fallo.",
"Identification": "Identificación",
"If untrusted, enter encryption password": "Se non é de confianza, escriba un contrasinal",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se queres evitar que outras persoas usuarias desta computadora accedan a Syncthing e a través del e aos teus ficheiros, considera establecer a autenticación.",
"Ignore": "Ignorar",
"Ignore Patterns": "Ignorar patróns",
"Ignore Permissions": "Ignorar permisos",
"Ignore patterns can only be added after the folder is created. If checked, an input field to enter ignore patterns will be presented after saving.": "Só se poden engadir os patróns a ignorar despois de crear o cartafol. Se está marcado, vaise mostrar un campo de texto para escribir patróns a ignorar despois de gardar.",
"Ignored Devices": "Dispositivos Ignorados",
"Ignored Folders": "Cartafoles ignorados",
"Ignored at": "Ignorado o",
"Included Software": "Software incluído",
"Incoming Rate Limit (KiB/s)": "Límite de Descaga (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Unha configuración incorrecta pode danar os contidos do teu cartafol e deixar Syncthing inutilizable.",
"Incorrect user name or password.": "Nome de usuario ou contrasinal incorrecto.",
"Info": "Info",
"Internally used paths:": "Rutas internas utilizadas:",
"Introduced By": "Presentado por",
"Introducer": "Presentador",
"Introduction": "Presentación",
"Inversion of the given condition (i.e. do not exclude)": "Inversión da condición dada (por exemplo, non excluír)",
"Keep Versions": "Manter Versións",
"LDAP": "LDAP",
@@ -203,7 +225,13 @@
"Last Scan": "Último escaneamento",
"Last seen": "Visto por última vez",
"Latest Change": "Último cambio",
"Learn more": "Saber máis",
"Learn more at {%url%}": "Aprenda máis en {{url}}",
"Limit": "Límite",
"Limit Bandwidth in LAN": "Limitar ancho de banda na LAN",
"Listener Failures": "Fallos na escoita",
"Listener Status": "Estado da escoita",
"Listeners": "Instancias á escoita",
"Loading data...": "Cargando datos...",
"Loading...": "Cargando...",
"Local Additions": "Adicións\tlocais",
@@ -217,18 +245,23 @@
"Log Out": "Pechar Sesión",
"Log in to see paths information.": "Inicia sesión para ver información das rutas.",
"Log in to see version information.": "Inicia sesión para ver información da versión.",
"Log tailing paused. Scroll to the bottom to continue.": "Rexistro continuo en pausa. Desprácese abaixo para continuar.",
"Login failed, see Syncthing logs for details.": "Fallou o inicio de sesión, vexa os rexistros de Syngthing para máis detalles.",
"Logs": "Rexistros",
"Major Upgrade": "Actualización Maior",
"Mass actions": "Accións en masa",
"Maximum Age": "Idade Máxima",
"Maximum single entry size": "Tamaño máximo dunha única entrada",
"Maximum total size": "Tamaño máximo total",
"Metadata Only": "Só Metadatos",
"Minimum Free Disk Space": "Espacio Mínimo Libre no Disco",
"Mod. Device": "Dispositivo Mod.",
"Mod. Time": "Hora da Mod.",
"More than a month ago": "Fai máis dun mes",
"More than a week ago": "Fai máis dunha semana",
"More than a year ago": "Fai máis dun ano",
"Move to top of queue": "Mover a enriba da cola",
"Multi level wildcard (matches multiple directory levels)": "Comodín multi nivel (concorda con varios niveis de directorios)",
"Never": "Nunca",
"New Device": "Dispositivo Novo",
"New Folder": "Cartafol Novo",
@@ -239,10 +272,15 @@
"No rules set": "Sen regras",
"No upgrades": "Sen actualizacións",
"Not shared": "Non compartido",
"Notice": "Aviso",
"Number of Connections": "Número de Conexións",
"OK": "OK",
"Off": "Non activo",
"Oldest First": "Máis Vellos Primeiro",
"Optional descriptive label for the folder. Can be different on each device.": "Etiqueta descritiva opcional para o cartafol. Pode ser distinta en cada dispositivo",
"Optional descriptive label for the folder. Can be different on each device.": "Etiqueta descritiva optativa para o cartafol. Pode ser distinta en cada dispositivo.",
"Options": "Opcións",
"Out of Sync": "Sen sincronizar",
"Out of Sync Items": "Elementos sen sincronizar",
"Outgoing Rate Limit (KiB/s)": "Límite de Saída (KiB/s)",
"Override": "Sobrescribir",
"Override Changes": "Sobrescribir os Cambios",
@@ -257,10 +295,14 @@
"Paused": "Parada",
"Paused (Unused)": "Parada (Sen uso)",
"Pending changes": "Cambios pendentes",
"Periodic scanning at given interval and disabled watching for changes": "Escaneo periódico a intervalos dados e desactivada a busca de cambios",
"Periodic scanning at given interval and enabled watching for changes": "Escaneo periódico a intervalos dados e activa a busca de cambios",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Escaneo periódico a intervalos dados e fallou o axuste de buscar cambios, reintentando cada minuto:",
"Permanently add it to the ignore list, suppressing further notifications.": "Engadilo permanentemente á lista de ignorados, suprimindo notificacións futuras.",
"Please consult the release notes before performing a major upgrade.": "Por favor consulte as notas de lanzamento antes de realizar unha actualización maior.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor configure un Usuario e Contrasinal de Autenticación para a GUI no diálogo de Configuración.",
"Please wait": "Por favor espere",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefixo que indica que o ficheiro se pode eliminar se evita a eliminación do directorio",
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefixo que indica que o patrón debe coincidir sen distinguir maiúsculas e minúsculas",
"Preparing to Sync": "Preparandose para Sincronizar",
"Preview": "Vista previa",
@@ -270,8 +312,9 @@
"QUIC WAN": "QUIC WAN",
"Quick guide to supported patterns": "Guía rápida dos patróns soportados",
"Random": "Aleatorio",
"Receive Encrypted": "Recibir datos cifrados",
"Receive Only": "Só Recibir",
"Received data is already encrypted": "Os datos recibidos xa están encriptados",
"Received data is already encrypted": "Os datos recibidos xa están cifrados",
"Recent Changes": "Cambios Recentes",
"Reduced by ignore patterns": "Reducido por patróns de ignorar",
"Relay LAN": "Relevo LAN",
@@ -301,23 +344,25 @@
"Saving changes": "Gardando os cambios",
"Scan Time Remaining": "Tempo Restante de Reescaneo",
"Scanning": "Escaneando",
"See external versioning help for supported templated command line parameters.": "Ver axuda externa sobre versións para os modelos de parámetros na liña de ordes.",
"Select All": "Seleccionar Todo",
"Select a version": "Seleccionar unha versión",
"Select additional devices to share this folder with.": "Seleccione dispositivos adicionais cos que compartir este cartafol.",
"Select additional folders to share with this device.": "Seleccione cartafois adicionais para compatir con este dispositivo.",
"Select additional folders to share with this device.": "Seleccione cartafoles adicionais para compatir con este dispositivo.",
"Select latest version": "Seleccionar a última versión",
"Select oldest version": "Seleccionar a versión máis vella",
"Send & Receive": "Enviar e Recibir",
"Send Extended Attributes": "Enviar Atributos Extensos",
"Send Extended Attributes": "Enviar Atributos ampliados",
"Send Only": "Só Enviar",
"Send Ownership": "Enviar Propiedade",
"Set Ignores on Added Folder": "Establecer ignorados no Cartafol engadido",
"Settings": "Configuración",
"Share": "Compartir",
"Share Folder": "Compartir Cartafol",
"Share by Email": "Compartir por Correo Electrónico",
"Share by SMS": "Compartir por SMS",
"Share this folder?": "Compartir este cartafol?",
"Shared Folders": "Cartafois Compartidos",
"Shared Folders": "Cartafoles Compartidos",
"Shared With": "Compartido Con",
"Sharing": "Compartindo",
"Show ID": "Mostrar ID",
@@ -336,20 +381,24 @@
"Smallest First": "Os máis pequenos primeiro",
"Some discovery methods could not be established for finding other devices or announcing this device:": "Algúns métodos de descubrimento non se puideron establecer para descubrir outros dispositivo ou anunciarse:",
"Some items could not be restored:": "Non se puideron recuperar algúns ítems:",
"Some listening addresses could not be enabled to accept connections:": "Algunhas direccións de escoita non se puideron habilitar para aceptar conexións:",
"Some listening addresses could not be enabled to accept connections:": "Algúns enderezos de escoita non se puideron activar para aceptar conexións:",
"Source Code": "Código Fonte",
"Stable releases and release candidates": "Versións estables e candidatos de lanzamento",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "As versións estables retrásanse ao redor de dúas semanas. Durante este tempo próbanse como candidatas de lanzamento.",
"Stable releases only": "Só versións estables",
"Staggered": "Gradual",
"Staggered File Versioning": "Versionado de Ficheiros Gradual",
"Start Browser": "Iniciar o Buscador",
"Statistics": "Estadísticas",
"Statistics": "Estatísticas",
"Stay logged in": "Manter a sesión",
"Stopped": "Parado",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Só almacena e sincroniza datos encriptados. Os cartafois en todos os dispositivos conectados necesitan configuarse co mesmo constrasinal ou ser do tipo \"{{receiveEncrypted}}\" tamén.",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Só almacena e sincroniza datos cifrados. Os cartafoles en todos os dispositivos conectados necesitan configuarse co mesmo constrasinal ou ser do tipo \"{{receiveEncrypted}}\" tamén.",
"Subject:": "Asunto:",
"Sync Extended Attributes": "Sincronizar os Atributos Extendidos",
"Support": "Axuda",
"Support Bundle": "Paquete de axuda",
"Sync Extended Attributes": "Sincronizar os Atributos ampliados",
"Sync Ownership": "Sincronizar Propiedade",
"Sync Protocol Listen Addresses": "Direccións de Escoita do Protocolo de Sincronización",
"Sync Protocol Listen Addresses": "Enderezos de Escoita do Protocolo de Sincronización",
"Sync Status": "Estado da Sincronización",
"Syncing": "Sincronizando",
"Syncthing device ID for \"{%devicename%}\"": "Sincronizando o ID de dispositivo para \"{{devicename}}\"",
@@ -357,8 +406,8 @@
"Syncthing includes the following software or portions thereof:": "Syncthing inclúe todo o seguinte software ou porcións dos mesmos:",
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing e Software Libre licenciado baixo a MPL v2.0.",
"Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet.": "Syncthing e un programa de sincronización de ficheiros continua. Sincroniza ficheiros entre dous ou máis computadores en tempo real, de maneira segura protexida de miradas indiscretas. Os teus datos son só teus e mereces elixir onde se gardan, se é cunha terceira parte, e como se transmiten pola rede.",
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing está escoitando nas seguintes direccións de rede intentos de conexión doutros dispositivos:",
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing non está escoitando intentos de conexión doutros dispositivos en ningunha dirección. Só funcionarán as conexións saíntes deste dispositivo.",
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing está á escoita de intentos de conexión doutros dispositivos nos seguintes enderezos de rede:",
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing non está agardando intentos de conexión doutros dispositivos en ningún enderezo. Só funcionarán as conexións saíntes deste dispositivo.",
"Syncthing is restarting.": "Syncthing estase a reiniciar.",
"Syncthing is saving changes.": "Syncthing esta a gardar os cambios.",
"Syncthing is upgrading.": "Syncthing estase a actualizar.",
@@ -368,20 +417,22 @@
"TCP LAN": "LAN TCP",
"TCP WAN": "WAN TCP",
"Take me back": "Léveme de volta",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "A dirección da GUI sobrescríbese polas opcións de arranque. Os cambios aquí non terán efecto mentres que a invalidación estea activa.",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "O enderezo da GUI sobrescríbese polas opcións de arranque. Os cambios feitos aquí non terán efecto mentres que a sobrescritura estea activa.",
"The Syncthing Authors": "Os Autores de Syncthing",
"The Syncthing admin interface is configured to allow remote access without a password.": "A inteface de aministración de Syncthing está configurada para permitir o acceso remoto sen ningún contrasinal.",
"The aggregated statistics are publicly available at the URL below.": "As estatísticas agregadas son públicas na URL de debaixo.",
"The cleanup interval cannot be blank.": "O intervalo de limpeza non pode estar en branco.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Gardouse a configuración pero non se activou. Ten que reiniciar Syncthing para activar a configuración nova.",
"The device ID cannot be blank.": "O ID de dispositivo non pode estar en branco.",
"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.": "O informe de uso encriptado envíase diariamente. Úsase para seguir plataformas comús, tamaños de cartafois e versións da aplicación. Se os datos que se envían cambian, preguntaráselle con este diálogo outra vez.",
"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).": "O ID de dispositivo a escribir aquí pódese atopar no cadro \"Accións > Mostrar ID\" no outro dispositivo. Os espazos e guións son optativos (ignóranse).",
"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.": "O informe de uso cifrado envíase diariamente. Úsase para seguir plataformas comúns, tamaños de cartafoles e versións da aplicación. Se os datos que se envían cambian, preguntaráselle con este diálogo outra vez.",
"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.": "O ID de dispositivo introducido non parece válido. Ten que ser unha cadea de 52 ou 56 caracteres consistente de letras e números, sendo opcionais os espacios e guións.",
"The folder ID cannot be blank.": "O ID de cartafol non pode estar en branco.",
"The folder ID must be unique.": "O ID de cartafol ten que ser único.",
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "O contenido do cartafol en outros dispositivos será sobrescrito para volverse identico con este dispositivo. Os ficheiros que non estean aquí eliminaranse nos outros dispositivos.",
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "O contenido do cartafol neste dispositivo será sobrescrito para volverse idéntico aos outros dispositivos. Os ficheiros novos serán eliminados.",
"The folder path cannot be blank.": "A ruta do cartafol non pode estar en branco.",
"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.": "Úsanse os seguintes intervalos: na primeira hora gárdase unha versión cada 30 segundos, durante o primeiro día cada hora, nos seguintes 30 días gárdase unha versión diaria, ata o tempo máximo establecido que se garda unha versión semanal.",
"The following items could not be synchronized.": "Non se puido sincronizar os seguintes ítems.",
"The following items were changed locally.": "Cambiáronse localmente os seguintes ítems.",
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Os seguintes métodos úsanse para descubrir outros dispositivos na rede e anunciar este dispositivo para que o encontren outros:",
@@ -404,11 +455,12 @@
"The rescan interval must be a non-negative number of seconds.": "O intervalo de reescaneo ten que ser un número non negativo de segundos.",
"There are no devices to share this folder with.": "Non hai dispositivos cos que compartir este cartafol.",
"There are no file versions to restore.": "Non hai versións de ficheriso para restaurar.",
"There are no folders to share with this device.": "Non hai cartafois que compartir con este dispositivo.",
"There are no folders to share with this device.": "Non hai cartafoles que compartir con este dispositivo.",
"They are retried automatically and will be synced when the error is resolved.": "Reinténtase automáticamente e sincronízanse cando se resolve o erro.",
"This Device": "Este Dispositivo",
"This Month": "Este Mes",
"This can easily give hackers access to read and change any files on your computer.": "Esto pode dar acceso fácil a hackers para ler e cambiar ficheiros no teu compturador.",
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Este dispositivo non pode descubrir outros dispositivos automaticamente ou anunciar a súa dirección a outros. So se poden contectar dispositivos con direccións estáticas configuradas.",
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Este dispositivo non pode descubrir outros dispositivos automaticamente ou anunciar o seu enderezo a outros. So se poden contectar dispositivos con enderezos estáticos configurados.",
"This is a major version upgrade.": "Esta é unha actualización maior.",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este axuste controla o espacio en disco dispoñible necesario no disco principal (p.ej. índice da base de datos).",
"Time": "Hora",
@@ -425,11 +477,11 @@
"Undecided (will prompt)": "Sen decidir (preguntará)",
"Unexpected Items": "Ítems non espeardos",
"Unexpected items have been found in this folder.": "Atopáronse ítems non esperados neste cartafol.",
"Unignore": "Des-ignorar",
"Unignore": "Non ignorar",
"Unknown": "Descoñecido",
"Unshared": "Des-compartido",
"Unshared Devices": "Dispositivos des-compartidos",
"Unshared Folders": "Cartafois des-compartidos",
"Unshared": "Non compartido",
"Unshared Devices": "Dispositivos non compartidos",
"Unshared Folders": "Cartafoles non compartidos",
"Untrusted": "Sen confiar",
"Up to Date": "Ao día",
"Updated {%file%}": "Actualizouse {{file}}",
@@ -460,8 +512,13 @@
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Aviso, esta ruta e un directorio pai dun cartafol existente \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Aviso, esta ruta é un subdirectorio dun cartafol existente \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Aviso, esta ruta é un subdirectorio de un cartafol existente \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Aviso: se utiliza un vixiante externo como {{syncthingInotify}}, debería comprobar que está desactivado.",
"Watch for Changes": "Buscar cambios",
"Watching for Changes": "Buscando cambios",
"Watching for changes discovers most changes without periodic scanning.": "Ao buscar cambios atópanse a maioiría dos cambios sen escaneo periódico.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Ao engadir un dispositivo novo, teña en mente que tamén debe engadir o dispositivo do outro 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.": "Ao engadir un cartafol novo, teña en mete que o ID de Cartafol úsase para enlazar os cartafois entre dispositivos. Son sensíbles a maiúsculas e teñen que coincidir exactamente entre dispositivos.",
"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.": "Ao engadir un cartafol novo, teña en mete que o ID de Cartafol úsase para enlazar os cartafoles entre dispositivos. Son sensibles a maiúsculas e teñen que coincidir exactamente entre dispositivos.",
"When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.": "Cando se establece máis de un en ambos dispositivos, Syncthing intentará establecer múltiples conexións concurrentes. Se os valores difiren, o máis alto será o que se use. Estableza cero para que Syncthing decida.",
"Yes": "Sí",
"Yesterday": "Onte",
"You can also copy and paste the text into a new message manually.": "Tamén pode copiar e pegar o texto nunha mensaxe de xeito manual.",
@@ -469,12 +526,12 @@
"You can change your choice at any time in the Settings dialog.": "Tamén pode cambiar a súa decisión en calqueira momento no diálogo de Opcións.",
"You can read more about the two release channels at the link below.": "Pode ler máis sobre as dúas canles de lanzamentos na ligazón de debaixo.",
"You have no ignored devices.": "Non ten dispositivos ignorados.",
"You have no ignored folders.": "Non ten cartafois ignorados.",
"You have no ignored folders.": "Non ten cartafoles ignorados.",
"You have unsaved changes. Do you really want to discard them?": "Ten cambios sen gardar. Realmente quere descartalos?",
"You must keep at least one version.": "Ten que manter ao menos unha versión.",
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Nunca debe engadir nen cambiar nada localmente nun cartafol \"{{receiveEncrypted}}\".",
"Your SMS app should open to let you choose the recipient and send it from your own number.": "A súa aplicación SMS debe abrirse para deixarlle escoller un destinatario e envialo desde o seu propio número.",
"Your email app should open to let you choose the recipient and send it from your own address.": "A súa amplicación de correo debe abrirse para deixarlle escoller un destinatario e envialo desde a súa propia dirección.",
"Your SMS app should open to let you choose the recipient and send it from your own number.": "A túa aplicación SMS debe abrirse para deixarche elixir un correspondente e envialo desde o teu propio número.",
"Your email app should open to let you choose the recipient and send it from your own address.": "A túa aplicación de correo ten que abrirse para deixarche escoller a persoa correspondente e envialo desde o teu propio enderezo.",
"days": "días",
"deleted": "eliminado",
"deny": "denegar",
@@ -496,6 +553,7 @@
}
},
"unknown device": "dispositivo descoñecido",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quere compartir o cartafol \"{{cartafol}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quere compartir o cartafol \"{{folderlabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quere compartir o cartafol \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} quere compartir o cartafol \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} quere volver a presentar este dispositivo."
}

View File

@@ -1,16 +1,18 @@
{
"A device with that ID is already added.": "Már van hozzáadott eszköz ezzel az azonosítóval.",
"A negative number of days doesn't make sense.": "Negatív számú napnak nincs értelme.",
"A new major version may not be compatible with previous versions.": "Az új főverzió nem feltétlenül kompatibilis az előző verziókkal.",
"A new major version may not be compatible with previous versions.": "Az új főverzió nem biztos, hogy kompatibilis az előző verziókkal.",
"API Key": "API-kulcs",
"About": "Névjegy",
"About": "Rólunk",
"Action": "Művelet",
"Actions": "Műveletek",
"Active filter rules": "Aktív szűrők",
"Add": "Hozzáadás",
"Add Device": "Eszköz hozzáadása",
"Add Folder": "Mappa hozzáadása",
"Add Remote Device": "Távoli eszköz hozzáadása",
"Add devices from the introducer to our device list, for mutually shared folders.": "Eszközök hozzáadása a bevezetőről az eszköz listához, a közösen megosztott mappákhoz.",
"Add filter entry": "Szűrő hozzáadása",
"Add ignore patterns": "Mellőzési minták hozzáadása",
"Add new folder?": "Hozzáadható az új mappa?",
"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.": "Ezzel együtt a teljes átnézési intervallum jóval meg fog nőni (60-szoros értékre, vagyis 1 óra az új alapértelmezett érték). A „Nem” kiválasztásával később kézzel is módosítható ez az érték minden egyes mappára külön-külön.",
@@ -25,9 +27,11 @@
"Allowed Networks": "Engedélyezett hálózatok",
"Alphabetic": "ABC sorrendben",
"Altered by ignoring deletes.": "Módosítva a törlések figyelmen kívül hagyásával.",
"Always turned on when the folder type is \"{%foldertype%}\".": "Mindig aktív, ha a mappa típusa \"{{foldertype}}\".",
"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.": "Külső program kezeli a fájlverzió-követést. Az távolítja el a fájlt a megosztott mappából. Ha az alkalmazás útvonala szóközöket tartalmaz, zárójelezni szükséges az útvonalat.",
"Anonymous Usage Reporting": "Névtelen felhasználási adatok küldése",
"Anonymous usage report format has changed. Would you like to move to the new format?": "A névtelen használati jelentés formátuma megváltozott. Szeretnél áttérni az új formátumra?",
"Applied to LAN": "LAN-ra alkalmazva",
"Apply": "Alkalmazás",
"Are you sure you want to override all remote changes?": "Biztos, hogy felülírható minden távoli módosítás?",
"Are you sure you want to permanently delete all these files?": "Biztos, hogy véglegesen törölhetőek mindezek a fájlok?",
@@ -36,6 +40,7 @@
"Are you sure you want to restore {%count%} files?": "Biztos, hogy vissza akarod állítani a(z) {{count}} fájlt?",
"Are you sure you want to revert all local changes?": "Biztos, hogy visszavonható minden helyi módosítás?",
"Are you sure you want to upgrade?": "Biztos, hogy frissíteni akarod?",
"Authentication Required": "Hitelesítés szükséges",
"Authors": "Szerzők",
"Auto Accept": "Automatikus elfogadás",
"Automatic Crash Reporting": "Automatikus összeomlás-jelentés",
@@ -48,6 +53,7 @@
"Body:": "Szövegmező:",
"Bugs": "Hibák",
"Cancel": "Mégsem",
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Nem elérhető, ha a mappa típusa \"{{foldertype}}\".",
"Changelog": "Változások",
"Clean out after": "Takarítás",
"Cleaning Versions": "Tisztító verziók",
@@ -62,6 +68,7 @@
"Configured": "Beállított",
"Connected (Unused)": "Kapcsolódva (használaton kívül)",
"Connection Error": "Kapcsolódási hiba",
"Connection Management": "Kapcsolatkezelő",
"Connection Type": "Kapcsolattípus",
"Connections": "Kapcsolatok",
"Connections via relays might be rate limited by the relay": "A közvetítőkön keresztüli csatlakozások sebességét a közvetítő korlátozhatja",
@@ -75,7 +82,9 @@
"Custom Range": "Egyedi intervallum",
"Danger!": "Veszély!",
"Database Location": "Az adatbázis helye",
"Debug": "Hibakeresés",
"Debugging Facilities": "Hibakeresési képességek",
"Default": "Alapértelmezett",
"Default Configuration": "Alapértelmezett beállítások",
"Default Device": "Alapértelmezett eszköz",
"Default Folder": "Alapértelmezett mappa",
@@ -93,6 +102,7 @@
"Device ID": "Eszközazonosító",
"Device Identification": "Eszközazonosító",
"Device Name": "Eszköz neve",
"Device Status": "Eszközállapot",
"Device is untrusted, enter encryption password": "Az eszköz nem megbízható, adjon meg titkosítási jelszót",
"Device rate limits": "Eszköz sávszélessége",
"Device that last modified the item": "Az eszköz, amely utoljára módosította az elemet",
@@ -141,6 +151,7 @@
"Enter up to three octal digits.": "Adjon meg legfeljebb három oktális számjegyet.",
"Error": "Hiba",
"Extended Attributes": "Kiterjesztett attribútumok",
"Extended Attributes Filter": "Kiterjesztett attribútum-szűrő",
"External": "Külső",
"External File Versioning": "Külső fájlverzió-követés",
"Failed Items": "Hibás elemek",
@@ -161,6 +172,7 @@
"Folder ID": "Mappaazonosító",
"Folder Label": "Mappacímke",
"Folder Path": "Mappa elérési útvonala",
"Folder Status": "Mappaállapot",
"Folder Type": "Mappatípus",
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "A(z) „{{receiveEncrypted}}” mappatípus csak új mappa hozzáadásakor adható meg.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "A „{{receiveEncrypted}}” mappa típusa nem változtatható meg a létrehozása után. El kell távolítani a mappát, az adatokat törölni kell vagy visszafejteni a lemezen és újra hozzáadni a mappát.",
@@ -182,6 +194,7 @@
"Global Discovery Servers": "Globális felfedező kiszolgálók",
"Global State": "Globális állapot",
"Help": "Súgó",
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Tipp: csak tiltó szabályok találhatók, miközben az alapértelmezés is tiltás. Fontolja meg egy „permit any” (mindent engedélyező) szabály hozzáadását utolsóként.",
"Home page": "Főoldal",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "A jelenlegi beállítások azonban azt jelzik, hogy nem kívánja engedélyezni. Az automatikus összeomlás-jelentés ezért letiltásra került.",
"Identification": "Azonosítás",
@@ -197,9 +210,12 @@
"Included Software": "Felhasznált szoftver",
"Incoming Rate Limit (KiB/s)": "Bejövő sebességkorlát (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Helytelen konfiguráció esetén károsodhat a mappák tartalma és működésképtelenné válhat a Syncthing.",
"Incorrect user name or password.": "Hibás felhasználónév vagy jelszó.",
"Info": "Infó",
"Internally used paths:": "Belsőleg használt útvonal:",
"Introduced By": "Bevezette",
"Introducer": "Bevezető",
"Introduction": "Bemutatkozás",
"Inversion of the given condition (i.e. do not exclude)": "A feltétel ellentéte (pl. ki nem hagyás)",
"Keep Versions": "Megtartott verziók",
"LDAP": "LDAP",
@@ -213,6 +229,7 @@
"Learn more": "Tudj meg többet",
"Learn more at {%url%}": "További információ itt: {{url}}",
"Limit": "Sebességkorlát",
"Limit Bandwidth in LAN": "LAN sávszélesség korlátozása",
"Listener Failures": "Figyelő hibák",
"Listener Status": "Figyelő állapot",
"Listeners": "Figyelők",
@@ -225,11 +242,18 @@
"Locally Changed Items": "Helyileg változott elemek",
"Log": "Napló",
"Log File": "Naplófájl",
"Log In": "Bejelentkezés",
"Log Out": "Kijelentkezés",
"Log in to see paths information.": "Jelentkezzen be az útvonalinformációk megtekintéséhez.",
"Log in to see version information.": "Jelentkezzen be a verzióinformációk megtekintéséhez.",
"Log tailing paused. Scroll to the bottom to continue.": "Napló utolsó sorainak figyelése szüneteltetve. Alulra görgetve lehet folytatni.",
"Login failed, see Syncthing logs for details.": "Sikertelen bejelentkezés, részletek a Syncthing naplófájlokban.",
"Logs": "Naplófájlok",
"Major Upgrade": "Főverzió-frissítés",
"Mass actions": "Tömeges műveletek",
"Maximum Age": "Maximális kor",
"Maximum single entry size": "Maximális egyedi méret",
"Maximum total size": "Maximális összesített méret",
"Metadata Only": "Csak metaadatok",
"Minimum Free Disk Space": "Minimális szabad lemezterület",
"Mod. Device": "Módosító eszköz",
@@ -246,9 +270,11 @@
"No": "Nem",
"No File Versioning": "Nincs fájlverzió-követés",
"No files will be deleted as a result of this operation.": "A művelet eredményeként egyetlen fájl sem lesz törölve.",
"No rules set": "Nincsenek szabályok",
"No upgrades": "Nincsenek frissítések",
"Not shared": "Nem megosztott",
"Notice": "Megjegyzés",
"Number of Connections": "Kapcsolatok száma",
"OK": "Rendben",
"Off": "Kikapcsolva",
"Oldest First": "Régebbi először",
@@ -260,8 +286,9 @@
"Override": "Felülírás",
"Override Changes": "Változtatások felülbírálása",
"Ownership": "Tulajdonjog",
"Password": "Jelszó",
"Path": "Útvonal",
"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": "A mappa elérési útvonala az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható:",
"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": "A mappa elérési útvonala az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "A verziók tárolására szolgáló elérési út (hagyd üresen a megosztott mappa alapértelmezett .stversions könyvtárának használatához).",
"Paths": "Elérési utak",
"Pause": "Szüneteltetés",
@@ -315,6 +342,7 @@
"Revert": "Visszaállítás",
"Revert Local Changes": "Helyi módosítások visszavonása",
"Save": "Mentés",
"Saving changes": "Változtatások mentése",
"Scan Time Remaining": "Fennmaradó átnézési idő",
"Scanning": "Átnézés",
"See external versioning help for supported templated command line parameters.": "A támogatott parancssori paraméter sablonokat a külső verziókezelő súgójában találod.",
@@ -336,7 +364,7 @@
"Share by SMS": "Megosztás SMS-ben",
"Share this folder?": "Megosztható ez a mappa?",
"Shared Folders": "Megosztott mappák",
"Shared With": "Megosztva ezekkel:",
"Shared With": "Megosztva ezekkel",
"Sharing": "Megosztás",
"Show ID": "Azonosító megjelenítése",
"Show QR": "QR-kód megjelenítése",
@@ -363,6 +391,7 @@
"Staggered File Versioning": "Többszintű fájlverzió-követés",
"Start Browser": "Böngésző indítása",
"Statistics": "Statisztika",
"Stay logged in": "Maradjon bejelentkezve",
"Stopped": "Leállítva",
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Csak titkosított adatokat tárol és szinkronizál. Minden kapcsolatban lévő eszközön a mappákat ugyanazzal a jelszóval kell védeni vagy „{{receiveEncrypted}}” típusúnak kell lenniük.",
"Subject:": "Tárgy:",
@@ -381,6 +410,7 @@
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "A Syncthing a következő hálózati címeken figyel más eszközök csatlakozási kísérleteire:",
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "A Syncthing semmilyen címen nem figyel más eszközök csatlakozási kísérleteire. Csak kimenő kapcsolatok működhetnek erről az eszközről.",
"Syncthing is restarting.": "Syncthing újraindul.",
"Syncthing is saving changes.": "A Syncthing éppen ment.",
"Syncthing is upgrading.": "Syncthing frissül.",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "A Syncthing már támogatja az automatikus összeomlás-jelentések küldését a fejlesztők felé. Ez a funkció alapértelmezetten be van kapcsolva.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Úgy tűnik, hogy a Syncthing nem működik, vagy valami probléma van a hálózati kapcsolattal. Újra próbálom…",
@@ -413,11 +443,13 @@
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "A verziók mappáin futó tisztítási folyamat intervalluma másodpercekben kifejezve. A nullával letiltható az időszakos tisztítás.",
"The maximum age must be a number and cannot be blank.": "A maximális kornak számnak kell lenni és nem lehet üres.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "A verziók megtartásának maximális ideje (napokban, 0-t megadva örökre megmaradnak).",
"The number of connections must be a non-negative number.": "A kapcsolatok száma nem lehet negatív.",
"The number of days must be a number and cannot be blank.": "A napok száma szám kell legyen és nem lehet üres.",
"The number of days to keep files in the trash can. Zero means forever.": "A napok száma, ameddig a fájlok meg lesznek tartva a szemetesben. A 0 azt jelenti, hogy örökre.",
"The number of old versions to keep, per file.": "A megtartott régi verziók száma fájlonként.",
"The number of versions must be a number and cannot be blank.": "A megtartott verziók száma nem lehet üres.",
"The path cannot be blank.": "Az elérési útvonal nem lehet üres.",
"The rate limit is applied to the accumulated traffic of all connections to this device.": "A sebességkorlátozás az eszközhöz tartozó összes kapcsolat összforgalmára vonatkozik.",
"The rate limit must be a non-negative number (0: no limit)": "A sebességlimitnek pozitív számnak kell lennie (0: nincs limit)",
"The remote device has not accepted sharing this folder.": "A távoli eszköz nem fogadta el ennek a mappának a megosztását.",
"The remote device has paused this folder.": "A távoli eszköz szünetelteti ezt a mappát.",
@@ -435,6 +467,7 @@
"Time": "Idő",
"Time the item was last modified": "Az idő, amikor utoljára módosítva lett az elem",
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "A \"{{devicename}}\" nevű Syncthing eszközzel való kapcsolódáshoz adjon hozzá egy új távoli eszközt a saját oldalán ezzel az azonosítóval:",
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Egy szabály engedélyezéséhez jelölje be a jelölőnégyzetet. Elutasításához hagyja üresen.",
"Today": "Ma",
"Trash Can": "Szemetes",
"Trash Can File Versioning": "Szemetes fájlverzió-követés",
@@ -461,8 +494,11 @@
"Usage reporting is always enabled for candidate releases.": "Az előzetes kiadásokban a használati jelentés mindig engedélyezett.",
"Use HTTPS for GUI": "HTTPS használata a grafikus felülethez",
"Use notifications from the filesystem to detect changed items.": "A fájlrendszer által szolgáltatott értesítések alkalmazása a megváltozott elemek keresésére.",
"User": "Felhasználó",
"User Home": "Felhasználói kezdőlap",
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Még nincs felhasználó és jelszó beállítva a grafikus felülethez. Érdemes megfontolni a beállítását.",
"Using a QUIC connection over LAN": "QUIC-kapcsolat használata LAN-on keresztül",
"Using a QUIC connection over WAN": "QUIC-kapcsolat használata WAN-on keresztül",
"Using a direct TCP connection over LAN": "Közvetlen TCP-kapcsolat használata LAN-on keresztül",
"Using a direct TCP connection over WAN": "Közvetlen TCP-kapcsolat használata WAN-on keresztül",
"Version": "Verzió",
@@ -483,6 +519,7 @@
"Watching for changes discovers most changes without periodic scanning.": "A változások keresése felfedezi a legtöbb változást periodikus átnézés nélkül.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Új eszköz hozzáadásakor nem szabad elfeledkezni arról, hogy a másik oldalon ezt az eszközt is hozzá kell adni.",
"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.": "Új eszköz hozzáadásakor észben kell tartani, hogy a mappaazonosító arra való, hogy összekösse a mappákat az eszközökön. Az azonosító kisbetű-nagybetű érzékeny és pontosan egyeznie kell az eszközökön.",
"When set to more than one on both devices, Syncthing will attempt to establish multiple concurrent connections. If the values differ, the highest will be used. Set to zero to let Syncthing decide.": "Ha mindkét eszközön egynél többre van beállítva, a Syncthing több párhuzamos kapcsolat létrehozását fogja megkísérelni. Eltérő értékeknél, a legmagasabb érték lesz használva. Állítsa nullára, hogy a Syncthing döntsön.",
"Yes": "Igen",
"Yesterday": "Tegnap",
"You can also copy and paste the text into a new message manually.": "A szöveget kézzel is bemásolhatja és beillesztheti egy új üzenetbe.",
@@ -498,6 +535,7 @@
"Your email app should open to let you choose the recipient and send it from your own address.": "Az e-mail alkalmazásnak meg kell nyílnia, hogy kiválaszthassa a címzettet, és elküldhesse a saját címéről.",
"days": "nap",
"deleted": "törölve",
"deny": "megtagad",
"directories": "mappa",
"file": "fájl",
"files": "fájl",
@@ -505,6 +543,7 @@
"full documentation": "teljes dokumentáció",
"items": "elem",
"modified": "módosított",
"permit": "engedélyez",
"seconds": "másodperc",
"theme": {
"name": {
@@ -514,6 +553,7 @@
"light": "Világos"
}
},
"unknown device": "ismeretlen eszköz",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} szeretné megosztani a mappát: „{{folder}}”.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} szeretné megosztani a mappát: „{{folderlabel}}” ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} újra bevezetheti ezt az eszközt."

View File

@@ -82,6 +82,7 @@
"Custom Range": "Intervallo personalizzato",
"Danger!": "Pericolo!",
"Database Location": "Posizione del database",
"Debug": "Debug",
"Debugging Facilities": "Servizi di Debug",
"Default": "Default",
"Default Configuration": "Configurazione predefinita",
@@ -210,6 +211,7 @@
"Incoming Rate Limit (KiB/s)": "Limite Velocità in Ingresso (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Una configurazione non corretta potrebbe danneggiare il contenuto delle cartelle e rendere Syncthing inoperativo.",
"Incorrect user name or password.": "Nome utente o password errati.",
"Info": "Info",
"Internally used paths:": "Percorsi interni utilizzati:",
"Introduced By": "Introdotto da",
"Introducer": "Introduttore",
@@ -227,6 +229,7 @@
"Learn more": "Per saperne di più",
"Learn more at {%url%}": "Scopri di più su {{url}}",
"Limit": "Limite",
"Limit Bandwidth in LAN": "Limitare la larghezza di banda nella LAN",
"Listener Failures": "Fallimenti dell'Ascoltatore",
"Listener Status": "Stato dell'Ascoltatore",
"Listeners": "In Ascolto",

View File

@@ -259,7 +259,7 @@
"Mod. Device": "Enhet som utförde ändring",
"Mod. Time": "Tid för ändring",
"More than a month ago": "Mer än en månad sedan",
"More than a week ago": "Mer än en vecka sedan",
"More than a week ago": "För mer än en vecka sedan",
"More than a year ago": "Mer än ett år sedan",
"Move to top of queue": "Flytta till överst i kön",
"Multi level wildcard (matches multiple directory levels)": "Flernivå jokertecken (matchar flera mappnivåer)",

View File

@@ -1 +1 @@
var langPrettyprint = {"ar":"Arabic","bg":"Bulgarian","ca":"Catalan","ca@valencia":"Valencian","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","eu":"Basque","fil":"Filipino","fr":"French","fy":"Frisian","ga":"Irish","he-IL":"Hebrew (Israel)","hi":"Hindi","hr":"Croatian","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean","lt":"Lithuanian","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian","ru":"Russian","sk":"Slovak","sl":"Slovenian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (Simplified Han script)","zh-HK":"Chinese (Traditional Han script, Hong Kong)","zh-TW":"Chinese (Traditional Han script)"}
var langPrettyprint = {"ar":"Arabic","bg":"Bulgarian","ca":"Catalan","ca@valencia":"Valencian","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","eu":"Basque","fil":"Filipino","fr":"French","fy":"Frisian","ga":"Irish","gl":"Galician","he-IL":"Hebrew (Israel)","hi":"Hindi","hr":"Croatian","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean","lt":"Lithuanian","nl":"Dutch","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ro-RO":"Romanian","ru":"Russian","sk":"Slovak","sl":"Slovenian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","zh-CN":"Chinese (Simplified Han script)","zh-HK":"Chinese (Traditional Han script, Hong Kong)","zh-TW":"Chinese (Traditional Han script)"}

View File

@@ -1 +1 @@
var validLangs = ["ar","bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","eu","fil","fr","fy","ga","he-IL","hi","hr","hu","id","it","ja","ko-KR","lt","nl","pl","pt-BR","pt-PT","ro-RO","ru","sk","sl","sv","tr","uk","zh-CN","zh-HK","zh-TW"]
var validLangs = ["ar","bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","eu","fil","fr","fy","ga","gl","he-IL","hi","hr","hu","id","it","ja","ko-KR","lt","nl","pl","pt-BR","pt-PT","ro-RO","ru","sk","sl","sv","tr","uk","zh-CN","zh-HK","zh-TW"]

View File

@@ -30,7 +30,7 @@
<h4 class="text-center" translate>The Syncthing Authors</h4>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Tommy van der Vorst, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Daniil Gentili, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus B Spencer, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Michael Jephcote, Michael Rienstra, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, pullmerge, Quentin Hibon, Rahmi Pruitt, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Marcus B Spencer, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Tommy van der Vorst, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Daniil Gentili, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Michael Jephcote, Michael Rienstra, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, pullmerge, Quentin Hibon, Rahmi Pruitt, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
</div>
</div>
</div>

View File

@@ -10,6 +10,7 @@ import "github.com/jmoiron/sqlx"
type txPreparedStmts struct {
*sqlx.Tx
stmts map[string]*sqlx.Stmt
}

View File

@@ -14,9 +14,10 @@ import (
)
type folderDB struct {
folderID string
*baseDB
folderID string
localDeviceIdx int64
deleteRetention time.Duration
}

View File

@@ -455,7 +455,7 @@ func (s *service) Serve(ctx context.Context) error {
// due to a config change through the API, let that finish successfully.
timeout, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
defer cancel()
if err := srv.Shutdown(timeout); err == timeout.Err() {
if err := srv.Shutdown(timeout); errors.Is(err, timeout.Err()) {
srv.Close()
}
@@ -546,7 +546,7 @@ func corsMiddleware(next http.Handler, allowFrameLoading bool) http.Handler {
// See https://www.w3.org/TR/cors/ for details.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Process OPTIONS requests
if r.Method == "OPTIONS" {
if r.Method == http.MethodOptions {
// Add a generous access-control-allow-origin header for CORS requests
w.Header().Add("Access-Control-Allow-Origin", "*")
// Only GET/POST/OPTIONS Methods are supported
@@ -557,7 +557,7 @@ func corsMiddleware(next http.Handler, allowFrameLoading bool) http.Handler {
w.Header().Set("Access-Control-Max-Age", "600")
// Indicate that no content will be returned
w.WriteHeader(204)
w.WriteHeader(http.StatusNoContent)
return
}

View File

@@ -337,7 +337,7 @@ func formatOptionalPercentS(template string, username string) string {
if nReps < 0 {
nReps = 0
}
for i := 0; i < nReps; i++ {
for range nReps {
replacements = append(replacements, username)
}
return fmt.Sprintf(template, replacements...)

View File

@@ -22,6 +22,7 @@ import (
type configMuxBuilder struct {
*httprouter.Router
id protocol.DeviceID
cfg config.Wrapper
}

View File

@@ -168,8 +168,8 @@ func (m *tokenCookieManager) createSession(username string, persistent bool, w h
// either directly to us, or as used by the client towards a reverse
// proxy who sends us headers.
connectionIsHTTPS := r.TLS != nil ||
strings.ToLower(r.Header.Get("x-forwarded-proto")) == "https" ||
strings.Contains(strings.ToLower(r.Header.Get("forwarded")), "proto=https")
strings.ToLower(r.Header.Get("X-Forwarded-Proto")) == "https" ||
strings.Contains(strings.ToLower(r.Header.Get("Forwarded")), "proto=https")
// If the connection is HTTPS, or *should* be HTTPS, set the Secure
// bit in cookies.
useSecureCookie := connectionIsHTTPS || m.guiCfg.UseTLS()

View File

@@ -32,6 +32,7 @@ type Interface interface {
type cast struct {
*suture.Supervisor
name string
reader svcutil.ServiceWithError
writer svcutil.ServiceWithError

View File

@@ -8,6 +8,7 @@ package beacon
import (
"context"
"errors"
"log/slog"
"net"
"time"
@@ -74,6 +75,7 @@ func writeBroadcasts(ctx context.Context, inbox <-chan []byte, port int) error {
if iaddr, ok := addr.(*net.IPNet); ok && len(iaddr.IP) >= 4 && iaddr.IP.IsGlobalUnicast() && iaddr.IP.To4() != nil {
baddr := bcast(iaddr)
dsts = append(dsts, baddr.IP)
slog.Debug("Added broadcast address", slogutil.Address(baddr), "intf", intf.Name, slog.String("intf_flags", intf.Flags.String()))
}
}
}
@@ -83,8 +85,6 @@ func writeBroadcasts(ctx context.Context, inbox <-chan []byte, port int) error {
dsts = append(dsts, net.IP{0xff, 0xff, 0xff, 0xff})
}
l.Debugln("addresses:", dsts)
success := 0
for _, ip := range dsts {
dst := &net.UDPAddr{IP: ip, Port: port}
@@ -93,7 +93,8 @@ func writeBroadcasts(ctx context.Context, inbox <-chan []byte, port int) error {
_, err = conn.WriteTo(bs, dst)
conn.SetWriteDeadline(time.Time{})
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
var nerr net.Error
if errors.As(err, &nerr) && nerr.Timeout() {
// Write timeouts should not happen. We treat it as a fatal
// error on the socket.
l.Debugln(err)

View File

@@ -129,6 +129,11 @@ func readMulticasts(ctx context.Context, outbox chan<- recv, addr string) error
pconn := ipv6.NewPacketConn(conn)
joined := 0
for _, intf := range intfs {
if intf.Flags&net.FlagMulticast == 0 {
slog.DebugContext(ctx, "Not joining multicast group on non-multicast interface", "name", intf.Name, slog.String("flags", intf.Flags.String()))
continue
}
err := pconn.JoinGroup(&intf, &net.UDPAddr{IP: gaddr.IP})
if err != nil {
l.Debugln("IPv6 join", intf.Name, "failed:", err)

View File

@@ -165,6 +165,7 @@ func (cfg *Configuration) ProbeFreePorts() error {
type xmlConfiguration struct {
Configuration
XMLName xml.Name `xml:"configuration"`
}
@@ -684,7 +685,7 @@ func copyMatchingTag(from interface{}, to interface{}, tag string, shouldCopy fu
panic(fmt.Sprintf("non equal types: %s != %s", fromType, toType))
}
for i := 0; i < toStruct.NumField(); i++ {
for i := range toStruct.NumField() {
fromField := fromStruct.Field(i)
toField := toStruct.Field(i)

View File

@@ -153,7 +153,7 @@ func (f FolderConfiguration) ModTimeWindow() time.Duration {
}
func (f *FolderConfiguration) CreateMarker() error {
if err := f.CheckPath(); err != ErrMarkerMissing {
if err := f.CheckPath(); !errors.Is(err, ErrMarkerMissing) {
return err
}
if f.MarkerName != DefaultMarkerName {

View File

@@ -428,11 +428,12 @@ func migrateToConfigV12(cfg *Configuration) {
var newDiscoServers []string
var useDefault bool
for _, addr := range cfg.Options.RawGlobalAnnServers {
if addr == "udp4://announce.syncthing.net:22026" {
switch addr {
case "udp4://announce.syncthing.net:22026":
useDefault = true
} else if addr == "udp6://announce-v6.syncthing.net:22026" {
case "udp6://announce-v6.syncthing.net:22026":
useDefault = true
} else {
default:
newDiscoServers = append(newDiscoServers, addr)
}
}

View File

@@ -11,6 +11,7 @@ import "github.com/syncthing/syncthing/lib/config"
// invalidListener is never valid
type invalidListener struct {
listenerFactory
err error
}
@@ -25,6 +26,7 @@ func (i invalidListener) Valid(_ config.Configuration) error {
// invalidDialer is never valid
type invalidDialer struct {
dialerFactory
err error
}

View File

@@ -225,8 +225,9 @@ func getRateLimiter(m map[protocol.DeviceID]*rate.Limiter, deviceID protocol.Dev
// limitedReader is a rate limited io.Reader
type limitedReader struct {
reader io.Reader
waiterHolder
reader io.Reader
}
func (r *limitedReader) Read(buf []byte) (int, error) {
@@ -239,8 +240,9 @@ func (r *limitedReader) Read(buf []byte) (int, error) {
// limitedWriter is a rate limited io.Writer
type limitedWriter struct {
writer io.Writer
waiterHolder
writer io.Writer
}
func (w *limitedWriter) Write(buf []byte) (int, error) {

View File

@@ -39,6 +39,7 @@ func init() {
type quicDialer struct {
commonDialer
registry *registry.Registry
}

View File

@@ -39,10 +39,10 @@ func init() {
type quicListener struct {
svcutil.ServiceWithError
nat atomic.Uint64 // Holds a stun.NATType.
onAddressesChangedNotifier
nat atomic.Uint64 // Holds a stun.NATType.
uri *url.URL
cfg config.Wrapper
tlsCfg *tls.Config
@@ -102,14 +102,10 @@ func (t *quicListener) serve(ctx context.Context) error {
}
defer udpConn.Close()
tracer := &writeTrackingTracer{}
quicTransport := &quic.Transport{
Conn: udpConn,
Tracer: tracer.loggingTracer(),
}
quicTransport := &quic.Transport{Conn: udpConn}
defer quicTransport.Close()
svc := stun.New(t.cfg, t, &transportPacketConn{tran: quicTransport}, tracer)
svc := stun.New(t.cfg, t, &transportPacketConn{tran: quicTransport})
stunCtx, cancel := context.WithCancel(ctx)
defer cancel()
go svc.Serve(stunCtx)

View File

@@ -18,7 +18,6 @@ import (
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/logging"
"github.com/syncthing/syncthing/lib/osutil"
)
@@ -40,15 +39,16 @@ func quicNetwork(uri *url.URL) string {
}
type quicTlsConn struct {
quic.Connection
quic.Stream
*quic.Conn
*quic.Stream
// If we created this connection, we should be the ones closing it.
createdConn net.PacketConn
}
func (q *quicTlsConn) Close() error {
sterr := q.Stream.Close()
seerr := q.Connection.CloseWithError(0, "closing")
seerr := q.Conn.CloseWithError(0, "closing")
var pcerr error
if q.createdConn != nil {
pcerr = q.createdConn.Close()
@@ -63,7 +63,7 @@ func (q *quicTlsConn) Close() error {
}
func (q *quicTlsConn) ConnectionState() tls.ConnectionState {
return q.Connection.ConnectionState().TLS
return q.Conn.ConnectionState().TLS
}
func transportConnUnspecified(conn any) bool {
@@ -76,25 +76,6 @@ func transportConnUnspecified(conn any) bool {
return err == nil && ip.IsUnspecified()
}
type writeTrackingTracer struct {
lastWrite atomic.Int64 // unix nanos
}
func (t *writeTrackingTracer) loggingTracer() *logging.Tracer {
return &logging.Tracer{
SentPacket: func(net.Addr, *logging.Header, logging.ByteCount, []logging.Frame) {
t.lastWrite.Store(time.Now().UnixNano())
},
SentVersionNegotiationPacket: func(net.Addr, logging.ArbitraryLenConnectionID, logging.ArbitraryLenConnectionID, []logging.Version) {
t.lastWrite.Store(time.Now().UnixNano())
},
}
}
func (t *writeTrackingTracer) LastWrite() time.Time {
return time.Unix(0, t.lastWrite.Load())
}
// A transportPacketConn is a net.PacketConn that uses a quic.Transport.
type transportPacketConn struct {
tran *quic.Transport

View File

@@ -1340,7 +1340,7 @@ func (c *deviceConnectionTracker) accountAddedConnection(conn protocol.Connectio
// how many total connections they want
d := conn.DeviceID()
c.connections[d] = append(c.connections[d], conn)
c.wantConnections[d] = int(h.NumConnections)
c.wantConnections[d] = h.NumConnections
l.Debugf("Added connection for %s (now %d), they want %d connections", d.Short(), len(c.connections[d]), h.NumConnections)
// Update active connections metric

View File

@@ -39,6 +39,7 @@ type tlsConn interface {
// came from (type, priority).
type internalConn struct {
tlsConn
connType connType
isLocal bool
priority int

View File

@@ -27,6 +27,7 @@ func init() {
type tcpDialer struct {
commonDialer
registry *registry.Registry
}

View File

@@ -81,11 +81,12 @@ func (t *tcpListener) serve(ctx context.Context) error {
defer slog.InfoContext(ctx, "TCP listener shutting down", slogutil.Address(tcaddr))
var ipVersion nat.IPVersion
if t.uri.Scheme == "tcp4" {
switch t.uri.Scheme {
case "tcp4":
ipVersion = nat.IPv4Only
} else if t.uri.Scheme == "tcp6" {
case "tcp6":
ipVersion = nat.IPv6Only
} else {
default:
ipVersion = nat.IPvAny
}
mapping := t.natService.NewMapping(nat.TCP, ipVersion, tcaddr.IP, tcaddr.Port)

View File

@@ -7,6 +7,7 @@
package connections
import (
"errors"
"net"
"net/url"
"strconv"
@@ -20,7 +21,8 @@ func fixupPort(uri *url.URL, defaultPort int) *url.URL {
copyURI := *uri
host, port, err := net.SplitHostPort(uri.Host)
if e, ok := err.(*net.AddrError); ok && strings.Contains(e.Err, "missing port") {
e := &net.AddrError{}
if errors.As(err, &e) && strings.Contains(e.Err, "missing port") {
// addr is of the form "1.2.3.4" or "[fe80::1]"
host = uri.Host
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {

View File

@@ -64,6 +64,7 @@ func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error)
// existing connection" shenanigans.
type dialerConn struct {
net.Conn
addr net.Addr
}

View File

@@ -17,6 +17,7 @@ import (
// A cachedFinder is a Finder with associated cache timeouts.
type cachedFinder struct {
Finder
cacheTime time.Duration
negCacheTime time.Duration
cache *cache

View File

@@ -32,6 +32,8 @@ import (
)
type globalClient struct {
errorHolder
server string
addrList AddressLister
announceClient httpClient
@@ -39,7 +41,6 @@ type globalClient struct {
noAnnounce bool
noLookup bool
evLogger events.Logger
errorHolder
}
type httpClient interface {
@@ -373,6 +374,7 @@ func queryBool(q url.Values, key string) bool {
type idCheckingHTTPClient struct {
httpClient
id protocol.DeviceID
}
@@ -472,7 +474,7 @@ func ipv4Identity(port int) string {
}
func ipv6Identity(addr string) string {
return fmt.Sprintf("IPv6 local multicast discovery on address %s", addr)
return "IPv6 local multicast discovery on address " + addr
}
func http2EnabledTransport(t *http.Transport) *http.Transport {

View File

@@ -34,6 +34,8 @@ import (
type localClient struct {
*suture.Supervisor
*cache
myID protocol.DeviceID
addrList AddressLister
name string
@@ -43,8 +45,6 @@ type localClient struct {
localBcastStart time.Time
localBcastTick <-chan time.Time
forcedBcastTick chan time.Time
*cache
}
const (

View File

@@ -41,6 +41,7 @@ type Manager interface {
type manager struct {
*suture.Supervisor
myID protocol.DeviceID
cfg config.Wrapper
cert tls.Certificate

View File

@@ -524,7 +524,7 @@ func (s *bufferedSubscription) Since(id int, into []Event, timeout time.Duration
into = append(into, s.buf[i])
}
}
for i := 0; i < s.next; i++ {
for i := range s.next {
if s.buf[i].SubscriptionID > id {
into = append(into, s.buf[i])
}

View File

@@ -340,6 +340,7 @@ func (*BasicFilesystem) underlying() (Filesystem, bool) {
// basicFile implements the fs.File interface on top of an os.File
type basicFile struct {
*os.File
name string
}

View File

@@ -397,10 +397,13 @@ func (f *caseFilesystem) checkCaseExisting(name string) error {
type defaultRealCaser struct {
cache *caseCache
fs Filesystem
mut sync.Mutex
}
type caseCache = lru.TwoQueueCache[string, *caseNode]
type caseCache struct {
*lru.TwoQueueCache[string, *caseNode]
mut sync.Mutex
}
func newCaseCache() *caseCache {
cache, err := lru.New2Q[string, *caseNode](caseCacheItemLimit)
@@ -408,7 +411,9 @@ func newCaseCache() *caseCache {
if err != nil {
panic(err)
}
return cache
return &caseCache{
TwoQueueCache: cache,
}
}
func (r *defaultRealCaser) realCase(name string) (string, error) {
@@ -418,7 +423,7 @@ func (r *defaultRealCaser) realCase(name string) (string, error) {
}
for _, comp := range PathComponents(name) {
node := r.getExpireAdd(realName)
node := r.cache.getExpireAdd(realName, r.fs)
if node.err != nil {
return "", node.err
@@ -444,18 +449,18 @@ func (r *defaultRealCaser) dropCache() {
// getExpireAdd gets an entry for the given key. If no entry exists, or it is
// expired a new one is created and added to the cache.
func (r *defaultRealCaser) getExpireAdd(key string) *caseNode {
r.mut.Lock()
defer r.mut.Unlock()
node, ok := r.cache.Get(key)
func (c *caseCache) getExpireAdd(key string, fs Filesystem) *caseNode {
c.mut.Lock()
defer c.mut.Unlock()
node, ok := c.Get(key)
if !ok {
node := newCaseNode(key, r.fs)
r.cache.Add(key, node)
node := newCaseNode(key, fs)
c.Add(key, node)
return node
}
if node.expires.Before(time.Now()) {
node = newCaseNode(key, r.fs)
r.cache.Add(key, node)
node = newCaseNode(key, fs)
c.Add(key, node)
}
return node
}

View File

@@ -156,7 +156,7 @@ func testCaseFSStat(t *testing.T, fsys Filesystem) {
func BenchmarkWalkCaseFakeFS100k(b *testing.B) {
const entries = 100_000
fsys, paths, err := fakefsForBenchmark(entries, 0)
fsys, paths, err := fakefsForTest(entries, 0)
if err != nil {
b.Fatal(err)
}
@@ -260,7 +260,7 @@ func TestStressCaseFS(t *testing.T) {
t.Skip("long test")
}
fsys, paths, err := fakefsForBenchmark(10_000, 0)
fsys, paths, err := fakefsForTest(10_000, 0, &OptionDetectCaseConflicts{})
if err != nil {
t.Fatal(err)
}
@@ -326,8 +326,8 @@ func doubleWalkFSWithOtherOps(fsys Filesystem, paths []string, otherOpEvery int,
return nil
}
func fakefsForBenchmark(nfiles int, latency time.Duration) (Filesystem, []string, error) {
fsys := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("fakefsForBenchmark?files=%d&insens=true&latency=%s", nfiles, latency))
func fakefsForTest(nfiles int, latency time.Duration, opts ...Option) (Filesystem, []string, error) {
fsys := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("fakefsForBenchmark?files=%d&insens=true&latency=%s", nfiles, latency), opts...)
var paths []string
if err := fsys.Walk("/", func(path string, info FileInfo, err error) error {

View File

@@ -749,6 +749,7 @@ func (fs *fakeFS) reportMetricsPer(b *testing.B, divisor float64, unit string) {
// opened for reading or writing, it's all good.
type fakeFile struct {
*fakeEntry
mut *sync.Mutex
rng io.Reader
seed int64

View File

@@ -140,12 +140,12 @@ func (evType EventType) Merge(other EventType) EventType {
}
func (evType EventType) String() string {
switch {
case evType == NonRemove:
switch evType {
case NonRemove:
return "non-remove"
case evType == Remove:
case Remove:
return "remove"
case evType == Mixed:
case Mixed:
return "mixed"
default:
panic("bug: Unknown event type")

View File

@@ -16,6 +16,7 @@ import (
type logFilesystem struct {
Filesystem
// Number of filesystem layers on top of logFilesystem to skip when looking
// for the true caller of the filesystem
layers int

View File

@@ -19,6 +19,7 @@ type database interface {
type mtimeFS struct {
Filesystem
chtimes func(string, time.Time, time.Time) error
db database
folderID string
@@ -170,6 +171,7 @@ func (f *mtimeFS) load(name string) (ondisk, virtual time.Time) {
type mtimeFileInfo struct {
FileInfo
mtime time.Time
}
@@ -179,6 +181,7 @@ func (m mtimeFileInfo) ModTime() time.Time {
type mtimeFile struct {
File
fs *mtimeFS
}

View File

@@ -63,6 +63,7 @@ type WalkFunc func(path string, info FileInfo, err error) error
type walkFilesystem struct {
Filesystem
checkInfiniteRecursion bool
}

View File

@@ -12,6 +12,7 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
@@ -50,13 +51,13 @@ func (resp *recordedResponse) ServeHTTP(w http.ResponseWriter, r *http.Request)
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Content-Length", fmt.Sprint(len(resp.gzip)))
w.Header().Set("Content-Length", strconv.Itoa(len(resp.gzip)))
w.WriteHeader(resp.status)
_, _ = w.Write(resp.gzip)
return
}
w.Header().Set("Content-Length", fmt.Sprint(len(resp.data)))
w.Header().Set("Content-Length", strconv.Itoa(len(resp.data)))
w.WriteHeader(resp.status)
_, _ = w.Write(resp.data)
}

View File

@@ -10,6 +10,7 @@ import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
@@ -353,7 +354,7 @@ func hashPatterns(patterns []Pattern) string {
h.Write([]byte(pat.String()))
h.Write([]byte("\n"))
}
return fmt.Sprintf("%x", h.Sum(nil))
return hex.EncodeToString(h.Sum(nil))
}
func loadIgnoreFile(fs fs.Filesystem, file string) (fs.File, fs.FileInfo, error) {

View File

@@ -44,6 +44,7 @@ type folder struct {
stateTracker
config.FolderConfiguration
*stats.FolderStatisticsReference
ioLimiter *semaphore.Semaphore
localFlags protocol.FlagLocal
@@ -54,8 +55,7 @@ type folder struct {
ignores *ignore.Matcher
mtimefs fs.Filesystem
modTimeWindow time.Duration
ctx context.Context //nolint:containedctx // used internally, only accessible on serve lifetime
done chan struct{} // used externally, accessible regardless of serve
done chan struct{} // used externally, accessible regardless of serve
sl *slog.Logger
scanInterval time.Duration
@@ -93,12 +93,12 @@ type folder struct {
}
type syncRequest struct {
fn func() error
fn func(context.Context) error
err chan error
}
type puller interface {
pull() (bool, error) // true when successful and should not be retried
pull(ctx context.Context) (bool, error) // true when successful and should not be retried
}
func newFolder(model *model, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *semaphore.Semaphore, ver versioner.Versioner) *folder {
@@ -150,10 +150,8 @@ func (f *folder) Serve(ctx context.Context) error {
f.model.foldersRunning.Add(1)
defer f.model.foldersRunning.Add(-1)
f.ctx = ctx
l.Debugln(f, "starting")
defer l.Debugln(f, "exiting")
f.sl.DebugContext(ctx, "Folder starting")
defer f.sl.DebugContext(ctx, "Folder exiting")
defer func() {
f.scanTimer.Stop()
@@ -162,7 +160,7 @@ func (f *folder) Serve(ctx context.Context) error {
}()
if f.FSWatcherEnabled && f.getHealthErrorAndLoadIgnores() == nil {
f.startWatch()
f.startWatch(ctx)
}
// If we're configured to not do version cleanup, or we don't have a
@@ -181,7 +179,7 @@ func (f *folder) Serve(ctx context.Context) error {
var err error
select {
case <-f.ctx.Done():
case <-ctx.Done():
close(f.done)
return nil
@@ -196,16 +194,16 @@ func (f *folder) Serve(ctx context.Context) error {
}
pullTimer.Reset(time.Duration(float64(time.Second) * f.PullerDelayS))
} else {
_, err = f.pull()
_, err = f.pull(ctx)
}
case <-pullTimer.C:
f.setState(FolderIdle)
_, err = f.pull()
_, err = f.pull(ctx)
case <-f.pullFailTimer.C:
var success bool
success, err = f.pull()
success, err = f.pull(ctx)
if (err != nil || !success) && f.pullPause < 60*f.pullBasePause() {
// Back off from retrying to pull
f.pullPause *= 2
@@ -214,46 +212,46 @@ func (f *folder) Serve(ctx context.Context) error {
case <-initialCompleted:
// Initial scan has completed, we should do a pull
initialCompleted = nil // never hit this case again
_, err = f.pull()
_, err = f.pull(ctx)
case <-f.forcedRescanRequested:
err = f.handleForcedRescans()
err = f.handleForcedRescans(ctx)
case <-f.scanTimer.C:
l.Debugln(f, "Scanning due to timer")
err = f.scanTimerFired()
f.sl.DebugContext(ctx, "Scanning due to timer")
err = f.scanTimerFired(ctx)
case req := <-f.doInSyncChan:
l.Debugln(f, "Running something due to request")
err = req.fn()
f.sl.DebugContext(ctx, "Running something due to request")
err = req.fn(ctx)
req.err <- err
case next := <-f.scanDelay:
l.Debugln(f, "Delaying scan")
f.sl.DebugContext(ctx, "Delaying scan")
f.scanTimer.Reset(next)
case <-f.scanScheduled:
l.Debugln(f, "Scan was scheduled")
f.sl.DebugContext(ctx, "Scan was scheduled")
f.scanTimer.Reset(0)
case fsEvents := <-f.watchChan:
l.Debugln(f, "Scan due to watcher")
err = f.scanSubdirs(fsEvents)
f.sl.DebugContext(ctx, "Scan due to watcher")
err = f.scanSubdirs(ctx, fsEvents)
case <-f.restartWatchChan:
l.Debugln(f, "Restart watcher")
err = f.restartWatch()
f.sl.DebugContext(ctx, "Restart watcher")
err = f.restartWatch(ctx)
case <-f.versionCleanupTimer.C:
l.Debugln(f, "Doing version cleanup")
f.versionCleanupTimerFired()
f.sl.DebugContext(ctx, "Doing version cleanup")
f.versionCleanupTimerFired(ctx)
}
if err != nil {
if svcutil.IsFatal(err) {
return err
}
f.setError(err)
f.setError(ctx, err)
}
}
}
@@ -302,12 +300,14 @@ func (*folder) Jobs(_, _ int) ([]string, []string, int) {
func (f *folder) Scan(subdirs []string) error {
<-f.initialScanFinished
return f.doInSync(func() error { return f.scanSubdirs(subdirs) })
return f.doInSync(func(ctx context.Context) error {
return f.scanSubdirs(ctx, subdirs)
})
}
// doInSync allows to run functions synchronously in folder.serve from exported,
// asynchronously called methods.
func (f *folder) doInSync(fn func() error) error {
func (f *folder) doInSync(fn func(context.Context) error) error {
req := syncRequest{
fn: fn,
err: make(chan error, 1),
@@ -328,7 +328,7 @@ func (f *folder) Reschedule() {
// Sleep a random time between 3/4 and 5/4 of the configured interval.
sleepNanos := (f.scanInterval.Nanoseconds()*3 + rand.Int63n(2*f.scanInterval.Nanoseconds())) / 4 //nolint:gosec
interval := time.Duration(sleepNanos) * time.Nanosecond
l.Debugln(f, "next rescan in", interval)
f.sl.Debug("Next rescan scheduled", slog.Duration("interval", interval))
f.scanTimer.Reset(interval)
}
@@ -364,7 +364,7 @@ func (f *folder) getHealthErrorWithoutIgnores() error {
return nil
}
func (f *folder) pull() (success bool, err error) {
func (f *folder) pull(ctx context.Context) (success bool, err error) {
f.pullFailTimer.Stop()
select {
case <-f.pullFailTimer.C:
@@ -401,7 +401,7 @@ func (f *folder) pull() (success bool, err error) {
// Abort early (before acquiring a token) if there's a folder error
err = f.getHealthErrorWithoutIgnores()
if err != nil {
l.Debugln("Skipping pull of", f.Description(), "due to folder error:", err)
f.sl.DebugContext(ctx, "Skipping pull due to folder error", slogutil.Error(err))
return false, err
}
@@ -410,7 +410,7 @@ func (f *folder) pull() (success bool, err error) {
if f.Type != config.FolderTypeSendOnly {
f.setState(FolderSyncWaiting)
if err := f.ioLimiter.TakeWithContext(f.ctx, 1); err != nil {
if err := f.ioLimiter.TakeWithContext(ctx, 1); err != nil {
return true, err
}
defer f.ioLimiter.Give(1)
@@ -427,12 +427,12 @@ func (f *folder) pull() (success bool, err error) {
}()
err = f.getHealthErrorAndLoadIgnores()
if err != nil {
l.Debugln("Skipping pull of", f.Description(), "due to folder error:", err)
f.sl.DebugContext(ctx, "Skipping pull due to folder error", slogutil.Error(err))
return false, err
}
f.setError(nil)
f.setError(ctx, nil)
success, err = f.puller.pull()
success, err = f.puller.pull(ctx)
if success && err == nil {
return true, nil
@@ -440,14 +440,14 @@ func (f *folder) pull() (success bool, err error) {
// Pulling failed, try again later.
delay := f.pullPause + time.Since(startTime)
f.sl.Info("Folder failed to sync, will be retried", slog.String("wait", stringutil.NiceDurationString(delay)))
f.sl.InfoContext(ctx, "Folder failed to sync, will be retried", slog.String("wait", stringutil.NiceDurationString(delay)))
f.pullFailTimer.Reset(delay)
return false, err
}
func (f *folder) scanSubdirs(subDirs []string) error {
l.Debugf("%v scanning", f)
func (f *folder) scanSubdirs(ctx context.Context, subDirs []string) error {
f.sl.DebugContext(ctx, "Scanning")
oldHash := f.ignores.Hash()
@@ -455,14 +455,14 @@ func (f *folder) scanSubdirs(subDirs []string) error {
if err != nil {
return err
}
f.setError(nil)
f.setError(ctx, nil)
// Check on the way out if the ignore patterns changed as part of scanning
// this folder. If they did we should schedule a pull of the folder so that
// we request things we might have suddenly become unignored and so on.
defer func() {
if f.ignores.Hash() != oldHash {
l.Debugln("Folder", f.Description(), "ignore patterns change detected while scanning; triggering puller")
f.sl.DebugContext(ctx, "Ignore patterns change detected while scanning; triggering puller")
f.ignoresUpdated()
f.SchedulePull()
}
@@ -471,15 +471,15 @@ func (f *folder) scanSubdirs(subDirs []string) error {
f.setState(FolderScanWaiting)
defer f.setState(FolderIdle)
if err := f.ioLimiter.TakeWithContext(f.ctx, 1); err != nil {
if err := f.ioLimiter.TakeWithContext(ctx, 1); err != nil {
return err
}
defer f.ioLimiter.Give(1)
metricFolderScans.WithLabelValues(f.ID).Inc()
ctx, cancel := context.WithCancel(f.ctx)
scanCtx, cancel := context.WithCancel(ctx)
defer cancel()
go addTimeUntilCancelled(ctx, metricFolderScanSeconds.WithLabelValues(f.ID))
go addTimeUntilCancelled(scanCtx, metricFolderScanSeconds.WithLabelValues(f.ID))
for i := range subDirs {
sub := osutil.NativeFilename(subDirs[i])
@@ -511,13 +511,13 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// changes.
changes := 0
defer func() {
l.Debugf("%v finished scanning, detected %v changes", f, changes)
f.sl.DebugContext(ctx, "Finished scanning", slog.Int("changes", changes))
if changes > 0 {
f.SchedulePull()
}
}()
changesHere, err := f.scanSubdirsChangedAndNew(subDirs, batch)
changesHere, err := f.scanSubdirsChangedAndNew(ctx, subDirs, batch)
changes += changesHere
if err != nil {
return err
@@ -536,7 +536,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// Do a scan of the database for each prefix, to check for deleted and
// ignored files.
changesHere, err = f.scanSubdirsDeletedAndIgnored(subDirs, batch)
changesHere, err = f.scanSubdirsDeletedAndIgnored(ctx, subDirs, batch)
changes += changesHere
if err != nil {
return err
@@ -565,7 +565,7 @@ func (f *folder) newScanBatch() *scanBatch {
}
b.updateBatch = NewFileInfoBatch(func(fs []protocol.FileInfo) error {
if err := b.f.getHealthErrorWithoutIgnores(); err != nil {
l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err)
b.f.sl.Debug("Stopping scan due to folder error", slogutil.Error(err))
return err
}
b.f.updateLocalsFromScanning(fs)
@@ -626,7 +626,7 @@ func (b *scanBatch) Update(fi protocol.FileInfo) (bool, error) {
// Our item is deleted and the global item is our own receive only
// file. No point in keeping track of that.
b.Remove(fi.Name)
l.Debugf("%v scanning: deleting deleted receive-only local-changed file: %v", b.f, fi)
b.f.sl.Debug("Deleting deleted receive-only local-changed file", slogutil.FilePath(fi.Name))
return true, nil
}
case (b.f.Type == config.FolderTypeReceiveOnly || b.f.Type == config.FolderTypeReceiveEncrypted) &&
@@ -639,19 +639,19 @@ func (b *scanBatch) Update(fi protocol.FileInfo) (bool, error) {
IgnoreXattrs: !b.f.SyncXattrs && !b.f.SendXattrs,
}):
// What we have locally is equivalent to the global file.
l.Debugf("%v scanning: Merging identical locally changed item with global: %v", b.f, fi)
b.f.sl.Debug("Merging identical locally changed item with global", slogutil.FilePath(fi.Name))
fi = gf
}
b.updateBatch.Append(fi)
return true, nil
}
func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (int, error) {
func (f *folder) scanSubdirsChangedAndNew(ctx context.Context, subDirs []string, batch *scanBatch) (int, error) {
changes := 0
// If we return early e.g. due to a folder health error, the scan needs
// to be cancelled.
scanCtx, scanCancel := context.WithCancel(f.ctx)
scanCtx, scanCancel := context.WithCancel(ctx)
defer scanCancel()
scanConfig := scanner.Config{
@@ -705,7 +705,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
switch f.Type {
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
default:
if nf, ok := f.findRename(res.File, alreadyUsedOrExisting); ok {
if nf, ok := f.findRename(ctx, res.File, alreadyUsedOrExisting); ok {
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
@@ -718,7 +718,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
return changes, nil
}
func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch) (int, error) {
func (f *folder) scanSubdirsDeletedAndIgnored(ctx context.Context, subDirs []string, batch *scanBatch) (int, error) {
var toIgnore []protocol.FileInfo
ignoredParent := ""
changes := 0
@@ -731,7 +731,7 @@ outer:
}
select {
case <-f.ctx.Done():
case <-ctx.Done():
break outer
default:
}
@@ -742,7 +742,7 @@ outer:
if ignoredParent != "" && !fs.IsParent(fi.Name, ignoredParent) {
for _, file := range toIgnore {
l.Debugln("marking file as ignored", file)
f.sl.DebugContext(ctx, "Marking file as ignored", slogutil.FilePath(file.Name))
nf := file
nf.SetIgnored()
if ok, err := batch.Update(nf); err != nil {
@@ -774,7 +774,7 @@ outer:
continue
}
l.Debugln("marking file as ignored", fi)
f.sl.DebugContext(ctx, "Marking file as ignored", slogutil.FilePath(fi.Name))
nf := fi
nf.SetIgnored()
if ok, err := batch.Update(nf); err != nil {
@@ -809,7 +809,7 @@ outer:
// sure the file gets in sync on the following pull.
nf.Version = protocol.Vector{}
}
l.Debugln("marking file as deleted", nf)
f.sl.DebugContext(ctx, "Marking file as deleted", slogutil.FilePath(nf.Name))
if ok, err := batch.Update(nf); err != nil {
return 0, err
} else if ok {
@@ -823,13 +823,13 @@ outer:
return 0, err
case !ok:
case gf.IsReceiveOnlyChanged():
l.Debugln("removing deleted, receive-only item that is globally receive-only from db", fi)
f.sl.DebugContext(ctx, "Removing deleted receive-only item that is globally receive-only from db", slogutil.FilePath(fi.Name))
batch.Remove(fi.Name)
changes++
case gf.IsDeleted():
// Our item is deleted and the global item is deleted too. We just
// pretend it is a normal deleted file (nobody cares about that).
l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, fi.Name)
f.sl.DebugContext(ctx, "Marking globally deleted item as not locally changed", slogutil.FilePath(fi.Name))
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
if ok, err := batch.Update(fi); err != nil {
return 0, err
@@ -841,7 +841,7 @@ outer:
// No need to bump the version for a file that was and is
// deleted and just the folder type/local flags changed.
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
l.Debugln("removing receive-only flag on deleted item", fi)
f.sl.DebugContext(ctx, "Removing receive-only flag on deleted item", slogutil.FilePath(fi.Name))
if ok, err := batch.Update(fi); err != nil {
return 0, err
} else if ok {
@@ -852,14 +852,14 @@ outer:
}
select {
case <-f.ctx.Done():
return changes, f.ctx.Err()
case <-ctx.Done():
return changes, ctx.Err()
default:
}
if len(toIgnore) > 0 {
for _, file := range toIgnore {
l.Debugln("marking file as ignored", file)
f.sl.DebugContext(ctx, "Marking file as ignored", slogutil.FilePath(file.Name))
nf := file
nf.SetIgnored()
if ok, err := batch.Update(nf); err != nil {
@@ -878,7 +878,7 @@ outer:
return changes, nil
}
func (f *folder) findRename(file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) {
func (f *folder) findRename(ctx context.Context, file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) {
if len(file.Blocks) == 0 || file.Size == 0 {
return protocol.FileInfo{}, false
}
@@ -893,7 +893,7 @@ loop:
}
select {
case <-f.ctx.Done():
case <-ctx.Done():
break loop
default:
}
@@ -942,16 +942,16 @@ loop:
return nf, found
}
func (f *folder) scanTimerFired() error {
err := f.scanSubdirs(nil)
func (f *folder) scanTimerFired(ctx context.Context) error {
err := f.scanSubdirs(ctx, nil)
select {
case <-f.initialScanFinished:
default:
if err != nil {
f.sl.Error("Failed initial scan", slogutil.Error(err))
f.sl.ErrorContext(ctx, "Failed initial scan", slogutil.Error(err))
} else {
f.sl.Info("Completed initial scan")
f.sl.InfoContext(ctx, "Completed initial scan")
}
close(f.initialScanFinished)
}
@@ -961,19 +961,19 @@ func (f *folder) scanTimerFired() error {
return err
}
func (f *folder) versionCleanupTimerFired() {
func (f *folder) versionCleanupTimerFired(ctx context.Context) {
f.setState(FolderCleanWaiting)
defer f.setState(FolderIdle)
if err := f.ioLimiter.TakeWithContext(f.ctx, 1); err != nil {
if err := f.ioLimiter.TakeWithContext(ctx, 1); err != nil {
return
}
defer f.ioLimiter.Give(1)
f.setState(FolderCleaning)
if err := f.versioner.Clean(f.ctx); err != nil {
f.sl.Warn("Failed to clean versions", slogutil.Error(err))
if err := f.versioner.Clean(ctx); err != nil {
f.sl.WarnContext(ctx, "Failed to clean versions", slogutil.Error(err))
}
f.versionCleanupTimer.Reset(f.versionCleanupInterval)
@@ -1007,21 +1007,21 @@ func (f *folder) scheduleWatchRestart() {
// restartWatch should only ever be called synchronously. If you want to use
// this asynchronously, you should probably use scheduleWatchRestart instead.
func (f *folder) restartWatch() error {
func (f *folder) restartWatch(ctx context.Context) error {
f.stopWatch()
f.startWatch()
return f.scanSubdirs(nil)
f.startWatch(ctx)
return f.scanSubdirs(ctx, nil)
}
// startWatch should only ever be called synchronously. If you want to use
// this asynchronously, you should probably use scheduleWatchRestart instead.
func (f *folder) startWatch() {
ctx, cancel := context.WithCancel(f.ctx)
func (f *folder) startWatch(ctx context.Context) {
watchCtx, cancel := context.WithCancel(ctx)
f.watchMut.Lock()
f.watchChan = make(chan []string)
f.watchCancel = cancel
f.watchMut.Unlock()
go f.monitorWatch(ctx)
go f.monitorWatch(watchCtx)
}
// monitorWatch starts the filesystem watching and retries every minute on failure.
@@ -1065,7 +1065,7 @@ func (f *folder) monitorWatch(ctx context.Context) {
}
lastWatch = time.Now()
watchaggregator.Aggregate(aggrCtx, eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, f.evLogger)
l.Debugln("Started filesystem watcher for folder", f.Description())
f.sl.DebugContext(ctx, "Started filesystem watcher")
case err = <-errChan:
var next time.Duration
if dur := time.Since(lastWatch); dur > pause {
@@ -1147,9 +1147,9 @@ func (f *folder) scanOnWatchErr() {
}
}
func (f *folder) setError(err error) {
func (f *folder) setError(ctx context.Context, err error) {
select {
case <-f.ctx.Done():
case <-ctx.Done():
return
default:
}
@@ -1161,12 +1161,12 @@ func (f *folder) setError(err error) {
if err != nil {
if oldErr == nil {
f.sl.Warn("Error on folder", slogutil.Error(err))
f.sl.WarnContext(ctx, "Error on folder", slogutil.Error(err))
} else {
f.sl.Info("Folder error changed", slogutil.Error(err), slog.Any("previously", oldErr))
f.sl.InfoContext(ctx, "Folder error changed", slogutil.Error(err), slog.Any("previously", oldErr))
}
} else {
f.sl.Info("Folder error cleared")
f.sl.InfoContext(ctx, "Folder error cleared")
f.SchedulePull()
}
@@ -1323,7 +1323,7 @@ func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events
}
}
func (f *folder) handleForcedRescans() error {
func (f *folder) handleForcedRescans(ctx context.Context) error {
f.forcedRescanPathsMut.Lock()
paths := make([]string, 0, len(f.forcedRescanPaths))
for path := range f.forcedRescanPaths {
@@ -1359,7 +1359,7 @@ func (f *folder) handleForcedRescans() error {
return err
}
return f.scanSubdirs(paths)
return f.scanSubdirs(ctx, paths)
}
// The exists function is expected to return true for all known paths
@@ -1376,7 +1376,7 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string {
for i := 0; i < len(dirs); {
dir, err := fs.Canonicalize(dirs[i])
if err != nil {
l.Debugf("Skipping %v for scan: %s", dirs[i], err)
slog.Debug("Skipping directory for scan", slog.String("dir", dirs[i]), slogutil.Error(err))
dirs = append(dirs[:i], dirs[i+1:]...)
continue
}

View File

@@ -7,6 +7,7 @@
package model
import (
"context"
"fmt"
"slices"
"strings"
@@ -39,8 +40,8 @@ func (f *receiveEncryptedFolder) Revert() {
f.doInSync(f.revert)
}
func (f *receiveEncryptedFolder) revert() error {
f.sl.Info("Reverting unexpected items")
func (f *receiveEncryptedFolder) revert(ctx context.Context) error {
f.sl.InfoContext(ctx, "Reverting unexpected items")
f.setState(FolderScanning)
defer f.setState(FolderIdle)
@@ -84,7 +85,7 @@ func (f *receiveEncryptedFolder) revert() error {
batch.Append(fi)
}
f.revertHandleDirs(dirs)
f.revertHandleDirs(ctx, dirs)
if err := batch.Flush(); err != nil {
return err
@@ -96,13 +97,13 @@ func (f *receiveEncryptedFolder) revert() error {
return nil
}
func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string) {
func (f *receiveEncryptedFolder) revertHandleDirs(ctx context.Context, dirs []string) {
if len(dirs) == 0 {
return
}
scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
go f.pullScannerRoutine(ctx, scanChan)
defer close(scanChan)
slices.SortFunc(dirs, func(a, b string) int {

View File

@@ -7,6 +7,7 @@
package model
import (
"context"
"slices"
"strings"
"time"
@@ -69,14 +70,14 @@ func (f *receiveOnlyFolder) Revert() {
f.doInSync(f.revert)
}
func (f *receiveOnlyFolder) revert() error {
f.sl.Info("Reverting folder")
func (f *receiveOnlyFolder) revert(ctx context.Context) error {
f.sl.InfoContext(ctx, "Reverting folder")
f.setState(FolderScanning)
defer f.setState(FolderIdle)
scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
go f.pullScannerRoutine(ctx, scanChan)
defer close(scanChan)
delQueue := &deleteQueue{
@@ -155,7 +156,7 @@ func (f *receiveOnlyFolder) revert() error {
// Handle any queued directories
deleted, err := delQueue.flush()
if err != nil {
f.sl.Warn("Failed to revert directories", slogutil.Error(err))
f.sl.WarnContext(ctx, "Failed to revert directories", slogutil.Error(err))
}
now := time.Now()
for _, dir := range deleted {

View File

@@ -407,8 +407,8 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: "ro", Files: files}))
// Ensure the pull to resolve conflicts (content identical) happened
must(t, f.doInSync(func() error {
f.pull()
must(t, f.doInSync(func(ctx context.Context) error {
f.pull(ctx)
return nil
}))
@@ -456,7 +456,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) {
// Monitor the outcome
sub := f.evLogger.Subscribe(events.LocalIndexUpdated)
defer sub.Unsubscribe()
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
go func() {
defer cancel()
@@ -567,7 +567,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi
if err != nil {
t.Fatal(err)
}
blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil)
blocks, _ := scanner.Blocks(t.Context(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil)
knownFiles := []protocol.FileInfo{
{
Name: "knownDir",

View File

@@ -7,6 +7,8 @@
package model
import (
"context"
"github.com/syncthing/syncthing/internal/itererr"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
@@ -37,7 +39,7 @@ func (*sendOnlyFolder) PullErrors() []FileError {
}
// pull checks need for files that only differ by metadata (no changes on disk)
func (f *sendOnlyFolder) pull() (bool, error) {
func (f *sendOnlyFolder) pull(ctx context.Context) (bool, error) {
batch := NewFileInfoBatch(func(files []protocol.FileInfo) error {
f.updateLocalsFromPulling(files)
return nil
@@ -92,8 +94,8 @@ func (f *sendOnlyFolder) Override() {
f.doInSync(f.override)
}
func (f *sendOnlyFolder) override() error {
f.sl.Info("Overriding global state ")
func (f *sendOnlyFolder) override(ctx context.Context) error {
f.sl.InfoContext(ctx, "Overriding global state ")
f.setState(FolderScanning)
defer f.setState(FolderIdle)

View File

@@ -48,6 +48,7 @@ func init() {
// to be fetched.
type pullBlockState struct {
*sharedPullerState
block protocol.BlockInfo
}
@@ -55,6 +56,7 @@ type pullBlockState struct {
// copied.
type copyBlocksState struct {
*sharedPullerState
blocks []protocol.BlockInfo
have int
}
@@ -159,20 +161,20 @@ func newSendReceiveFolder(model *model, ignores *ignore.Matcher, cfg config.Fold
// pull returns true if it manages to get all needed items from peers, i.e. get
// the device in sync with the global state.
func (f *sendReceiveFolder) pull() (bool, error) {
l.Debugf("%v pulling", f)
func (f *sendReceiveFolder) pull(ctx context.Context) (bool, error) {
f.sl.DebugContext(ctx, "Pulling")
scanChan := make(chan string)
go f.pullScannerRoutine(scanChan)
go f.pullScannerRoutine(ctx, scanChan)
defer func() {
close(scanChan)
f.setState(FolderIdle)
}()
metricFolderPulls.WithLabelValues(f.ID).Inc()
ctx, cancel := context.WithCancel(f.ctx)
pullCtx, cancel := context.WithCancel(ctx)
defer cancel()
go addTimeUntilCancelled(ctx, metricFolderPullSeconds.WithLabelValues(f.ID))
go addTimeUntilCancelled(pullCtx, metricFolderPullSeconds.WithLabelValues(f.ID))
changed := 0
@@ -183,8 +185,8 @@ func (f *sendReceiveFolder) pull() (bool, error) {
var err error
for tries := range maxPullerIterations {
select {
case <-f.ctx.Done():
return false, f.ctx.Err()
case <-ctx.Done():
return false, ctx.Err()
default:
}
@@ -192,17 +194,16 @@ func (f *sendReceiveFolder) pull() (bool, error) {
// it to FolderSyncing during the last iteration.
f.setState(FolderSyncPreparing)
changed, err = f.pullerIteration(scanChan)
changed, err = f.pullerIteration(ctx, scanChan)
if err != nil {
return false, err
}
l.Debugln(f, "changed", changed, "on try", tries+1)
f.sl.DebugContext(ctx, "Pull iteration completed", "changed", changed, "try", tries+1)
if changed == 0 {
// No files were changed by the puller, so we are in
// sync (except for unrecoverable stuff like invalid
// filenames on windows).
// No files were changed by the puller, so we are in sync, or we
// are unable to make further progress for the moment.
break
}
}
@@ -212,7 +213,7 @@ func (f *sendReceiveFolder) pull() (bool, error) {
if pullErrNum > 0 {
f.pullErrors = make([]FileError, 0, len(f.tempPullErrors))
for path, err := range f.tempPullErrors {
f.sl.Warn("Failed to sync", slogutil.FilePath(path), slogutil.Error(err))
f.sl.WarnContext(ctx, "Failed to sync", slogutil.FilePath(path), slogutil.Error(err))
f.pullErrors = append(f.pullErrors, FileError{
Err: err,
Path: path,
@@ -229,14 +230,16 @@ func (f *sendReceiveFolder) pull() (bool, error) {
})
}
return changed == 0, nil
// We're done if we didn't change anything and didn't fail to change
// anything
return changed == 0 && pullErrNum == 0, nil
}
// pullerIteration runs a single puller iteration for the given folder and
// returns the number items that should have been synced (even those that
// might have failed). One puller iteration handles all files currently
// flagged as needed in the folder.
func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) {
func (f *sendReceiveFolder) pullerIteration(ctx context.Context, scanChan chan<- string) (int, error) {
f.errorsMut.Lock()
f.tempPullErrors = make(map[string]string)
f.errorsMut.Unlock()
@@ -251,12 +254,13 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
var doneWg sync.WaitGroup
var updateWg sync.WaitGroup
l.Debugln(f, "copiers:", f.Copiers, "pullerPendingKiB:", f.PullerMaxPendingKiB)
f.sl.DebugContext(ctx, "Starting puller iteration", "copiers", f.Copiers, "pullerPendingKiB", f.PullerMaxPendingKiB)
updateWg.Add(1)
var changed int // only read after updateWg closes
go func() {
// dbUpdaterRoutine finishes when dbUpdateChan is closed
f.dbUpdaterRoutine(dbUpdateChan)
changed = f.dbUpdaterRoutine(dbUpdateChan)
updateWg.Done()
}()
@@ -264,7 +268,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
copyWg.Add(1)
go func() {
// copierRoutine finishes when copyChan is closed
f.copierRoutine(copyChan, pullChan, finisherChan)
f.copierRoutine(ctx, copyChan, pullChan, finisherChan)
copyWg.Done()
}()
}
@@ -272,18 +276,18 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
pullWg.Add(1)
go func() {
// pullerRoutine finishes when pullChan is closed
f.pullerRoutine(pullChan, finisherChan)
f.pullerRoutine(ctx, pullChan, finisherChan)
pullWg.Done()
}()
doneWg.Add(1)
// finisherRoutine finishes when finisherChan is closed
go func() {
f.finisherRoutine(finisherChan, dbUpdateChan, scanChan)
f.finisherRoutine(ctx, finisherChan, dbUpdateChan, scanChan)
doneWg.Done()
}()
changed, fileDeletions, dirDeletions, err := f.processNeeded(dbUpdateChan, copyChan, scanChan)
fileDeletions, dirDeletions, err := f.processNeeded(ctx, dbUpdateChan, copyChan, scanChan)
// Signal copy and puller routines that we are done with the in data for
// this iteration. Wait for them to finish.
@@ -298,7 +302,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
doneWg.Wait()
if err == nil {
f.processDeletions(fileDeletions, dirDeletions, dbUpdateChan, scanChan)
f.processDeletions(ctx, fileDeletions, dirDeletions, dbUpdateChan, scanChan)
}
// Wait for db updates and scan scheduling to complete
@@ -310,8 +314,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error)
return changed, err
}
func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) {
changed := 0
func (f *sendReceiveFolder) processNeeded(ctx context.Context, dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (map[string]protocol.FileInfo, []protocol.FileInfo, error) {
var dirDeletions []protocol.FileInfo
fileDeletions := map[string]protocol.FileInfo{}
buckets := map[string][]protocol.FileInfo{}
@@ -323,25 +326,23 @@ func (f *sendReceiveFolder) processNeeded(dbUpdateChan chan<- dbUpdateJob, copyC
loop:
for file, err := range itererr.Zip(f.model.sdb.AllNeededGlobalFiles(f.folderID, protocol.LocalDeviceID, f.Order, 0, 0)) {
if err != nil {
return changed, nil, nil, err
return nil, nil, err
}
select {
case <-f.ctx.Done():
case <-ctx.Done():
break loop
default:
}
if f.IgnoreDelete && file.IsDeleted() {
l.Debugln(f, "ignore file deletion (config)", file.FileName())
f.sl.DebugContext(ctx, "Ignoring file deletion per config", slogutil.FilePath(file.FileName()))
continue
}
changed++
switch {
case f.ignores.Match(file.Name).IsIgnored():
file.SetIgnored()
l.Debugln(f, "Handling ignored file", file)
f.sl.DebugContext(ctx, "Handling ignored file", file.LogAttr())
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
case build.IsWindows && fs.WindowsInvalidFilename(file.Name) != nil:
@@ -355,8 +356,6 @@ loop:
// We can't pull an invalid file. Grab the error again since
// we couldn't assign it directly in the case clause.
f.newPullError(file.Name, fs.WindowsInvalidFilename(file.Name))
// No reason to retry for this
changed--
}
case file.IsDeleted():
@@ -370,7 +369,7 @@ loop:
default:
df, ok, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return changed, nil, nil, err
return nil, nil, err
}
// Local file can be already deleted, but with a lower version
// number, hence the deletion coming in again as part of
@@ -389,7 +388,7 @@ loop:
case file.Type == protocol.FileInfoTypeFile:
curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return changed, nil, nil, err
return nil, nil, err
}
if hasCurFile && file.BlocksEqual(curFile) {
// We are supposed to copy the entire file, and then fetch nothing. We
@@ -407,17 +406,17 @@ loop:
break
}
file.SetUnsupported()
l.Debugln(f, "Invalidating symlink (unsupported)", file.Name)
f.sl.DebugContext(ctx, "Invalidating unsupported symlink", slogutil.FilePath(file.Name))
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
case file.IsDirectory() && !file.IsSymlink():
l.Debugln(f, "Handling directory", file.Name)
f.sl.DebugContext(ctx, "Handling directory", slogutil.FilePath(file.Name))
if f.checkParent(file.Name, scanChan) {
f.handleDir(file, dbUpdateChan, scanChan)
}
case file.IsSymlink():
l.Debugln(f, "Handling symlink", file.Name)
f.sl.DebugContext(ctx, "Handling symlink", slogutil.FilePath(file.Name))
if f.checkParent(file.Name, scanChan) {
f.handleSymlink(file, dbUpdateChan, scanChan)
}
@@ -428,8 +427,8 @@ loop:
}
select {
case <-f.ctx.Done():
return changed, nil, nil, f.ctx.Err()
case <-ctx.Done():
return nil, nil, ctx.Err()
default:
}
@@ -438,8 +437,8 @@ loop:
nextFile:
for {
select {
case <-f.ctx.Done():
return changed, fileDeletions, dirDeletions, f.ctx.Err()
case <-ctx.Done():
return fileDeletions, dirDeletions, ctx.Err()
default:
}
@@ -450,7 +449,7 @@ nextFile:
fi, ok, err := f.model.sdb.GetGlobalFile(f.folderID, fileName)
if err != nil {
return changed, nil, nil, err
return nil, nil, err
}
if !ok {
// File is no longer in the index. Mark it as done and drop it.
@@ -479,7 +478,7 @@ nextFile:
// map.
desired := fileDeletions[candidate.Name]
if err := f.renameFile(candidate, desired, fi, dbUpdateChan, scanChan); err != nil {
l.Debugf("rename shortcut for %s failed: %s", fi.Name, err.Error())
f.sl.DebugContext(ctx, "Rename shortcut failed", slogutil.FilePath(fi.Name), slogutil.Error(err))
// Failed to rename, try next one.
continue
}
@@ -508,12 +507,12 @@ nextFile:
continue
}
if err := f.handleFile(fi, copyChan); err != nil {
if err := f.handleFile(ctx, fi, copyChan); err != nil {
f.newPullError(fileName, err)
}
}
return changed, fileDeletions, dirDeletions, nil
return fileDeletions, dirDeletions, nil
}
func popCandidate(buckets map[string][]protocol.FileInfo, key string) (protocol.FileInfo, bool) {
@@ -526,10 +525,10 @@ func popCandidate(buckets map[string][]protocol.FileInfo, key string) (protocol.
return cands[0], true
}
func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
func (f *sendReceiveFolder) processDeletions(ctx context.Context, fileDeletions map[string]protocol.FileInfo, dirDeletions []protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
for _, file := range fileDeletions {
select {
case <-f.ctx.Done():
case <-ctx.Done():
return
default:
}
@@ -540,13 +539,13 @@ func (f *sendReceiveFolder) processDeletions(fileDeletions map[string]protocol.F
// Process in reverse order to delete depth first
for i := range dirDeletions {
select {
case <-f.ctx.Done():
case <-ctx.Done():
return
default:
}
dir := dirDeletions[len(dirDeletions)-i-1]
l.Debugln(f, "Deleting dir", dir.Name)
f.sl.DebugContext(ctx, "Deleting directory", slogutil.FilePath(dir.Name))
f.deleteDir(dir, dbUpdateChan, scanChan)
}
}
@@ -706,10 +705,10 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
// Encrypted files have made-up filenames with two synthetic parent
// directories which don't have any meaning. Create those if necessary.
if _, err := f.mtimefs.Lstat(parent); !fs.IsNotExist(err) {
l.Debugf("%v parent not missing %v", f, file)
f.sl.Debug("Parent directory exists", slogutil.FilePath(file))
return true
}
l.Debugf("%v creating parent directory of %v", f, file)
f.sl.Debug("Creating parent directory", slogutil.FilePath(file))
if err := f.mtimefs.MkdirAll(parent, 0o755); err != nil {
f.newPullError(file, fmt.Errorf("creating parent dir: %w", err))
return false
@@ -878,7 +877,7 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
// care not declare another err.
var err error
l.Debugln(f, "Deleting file or symlink", file.Name)
f.sl.Debug("Deleting file or symlink", slogutil.FilePath(file.Name))
f.evLogger.Log(events.ItemStarted, map[string]string{
"folder": f.folderID,
@@ -999,7 +998,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
})
}()
l.Debugln(f, "taking rename shortcut", source.Name, "->", target.Name)
f.sl.Debug("Taking rename shortcut", "from", source.Name, "to", target.Name)
// Check that source is compatible with what we have in the DB
if err = f.checkToBeDeleted(source, cur, true, scanChan); err != nil {
@@ -1128,7 +1127,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, db
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState) error {
func (f *sendReceiveFolder) handleFile(ctx context.Context, file protocol.FileInfo, copyChan chan<- copyBlocksState) error {
curFile, hasCurFile, err := f.model.sdb.GetDeviceFile(f.folderID, protocol.LocalDeviceID, file.Name)
if err != nil {
return err
@@ -1144,7 +1143,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
reused := make([]int, 0, len(file.Blocks))
if f.Type != config.FolderTypeReceiveEncrypted {
blocks, reused = f.reuseBlocks(blocks, reused, file, tempName)
blocks, reused = f.reuseBlocks(ctx, blocks, reused, file, tempName)
}
// The sharedpullerstate will know which flags to use when opening the
@@ -1168,7 +1167,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
s := newSharedPullerState(file, f.mtimefs, f.folderID, tempName, blocks, reused, f.IgnorePerms || file.NoPermissions, hasCurFile, curFile, !f.DisableSparseFiles, !f.DisableFsync)
l.Debugf("%v need file %s; copy %d, reused %v", f, file.Name, len(blocks), len(reused))
f.sl.DebugContext(ctx, "Handling file", slogutil.FilePath(file.Name), "blocksToCopy", len(blocks), "reused", len(reused))
cs := copyBlocksState{
sharedPullerState: s,
@@ -1179,15 +1178,15 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
return nil
}
func (f *sendReceiveFolder) reuseBlocks(blocks []protocol.BlockInfo, reused []int, file protocol.FileInfo, tempName string) ([]protocol.BlockInfo, []int) {
func (f *sendReceiveFolder) reuseBlocks(ctx context.Context, blocks []protocol.BlockInfo, reused []int, file protocol.FileInfo, tempName string) ([]protocol.BlockInfo, []int) {
// Check for an old temporary file which might have some blocks we could
// reuse.
tempBlocks, err := scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil)
tempBlocks, err := scanner.HashFile(ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil)
if err != nil {
var caseErr *fs.CaseConflictError
if errors.As(err, &caseErr) {
if rerr := f.mtimefs.Rename(caseErr.Real, tempName); rerr == nil {
tempBlocks, err = scanner.HashFile(f.ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil)
tempBlocks, err = scanner.HashFile(ctx, f.ID, f.mtimefs, tempName, file.BlockSize(), nil)
}
}
}
@@ -1260,7 +1259,7 @@ func populateOffsets(blocks []protocol.BlockInfo) {
// shortcutFile sets file metadata, when that's the only thing that has
// changed.
func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
l.Debugln(f, "taking shortcut on", file.Name)
f.sl.Debug("Taking metadata shortcut", slogutil.FilePath(file.Name))
f.evLogger.Log(events.ItemStarted, map[string]string{
"folder": f.folderID,
@@ -1328,7 +1327,7 @@ func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo, dbUpdateChan ch
// copierRoutine reads copierStates until the in channel closes and performs
// the relevant copies when possible, or passes it to the puller routine.
func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBlockState, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) copierRoutine(ctx context.Context, in <-chan copyBlocksState, pullChan chan<- pullBlockState, out chan<- *sharedPullerState) {
otherFolderFilesystems := make(map[string]fs.Filesystem)
for folder, cfg := range f.model.cfg.Folders() {
if folder == f.ID {
@@ -1347,8 +1346,8 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
blocks:
for _, block := range state.blocks {
select {
case <-f.ctx.Done():
state.fail(fmt.Errorf("folder stopped: %w", f.ctx.Err()))
case <-ctx.Done():
state.fail(fmt.Errorf("folder stopped: %w", ctx.Err()))
break blocks
default:
}
@@ -1366,7 +1365,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
continue
}
if f.copyBlock(block, state, otherFolderFilesystems) {
if f.copyBlock(ctx, block, state, otherFolderFilesystems) {
state.copyDone(block)
continue
}
@@ -1395,13 +1394,13 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
}
// Returns true when the block was successfully copied.
func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocksState, otherFolderFilesystems map[string]fs.Filesystem) bool {
func (f *sendReceiveFolder) copyBlock(ctx context.Context, block protocol.BlockInfo, state copyBlocksState, otherFolderFilesystems map[string]fs.Filesystem) bool {
buf := protocol.BufferPool.Get(block.Size)
defer protocol.BufferPool.Put(buf)
// Hope that it's usually in the same folder, so start with that
// one. Also possibly more efficient copy (same filesystem).
if f.copyBlockFromFolder(f.ID, block, state, f.mtimefs, buf) {
if f.copyBlockFromFolder(ctx, f.ID, block, state, f.mtimefs, buf) {
return true
}
if state.failed() != nil {
@@ -1409,7 +1408,7 @@ func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocks
}
for folderID, ffs := range otherFolderFilesystems {
if f.copyBlockFromFolder(folderID, block, state, ffs, buf) {
if f.copyBlockFromFolder(ctx, folderID, block, state, ffs, buf) {
return true
}
if state.failed() != nil {
@@ -1422,17 +1421,17 @@ func (f *sendReceiveFolder) copyBlock(block protocol.BlockInfo, state copyBlocks
// Returns true when the block was successfully copied.
// The passed buffer must be large enough to accommodate the block.
func (f *sendReceiveFolder) copyBlockFromFolder(folderID string, block protocol.BlockInfo, state copyBlocksState, ffs fs.Filesystem, buf []byte) bool {
func (f *sendReceiveFolder) copyBlockFromFolder(ctx context.Context, folderID string, block protocol.BlockInfo, state copyBlocksState, ffs fs.Filesystem, buf []byte) bool {
for e, err := range itererr.Zip(f.model.sdb.AllLocalBlocksWithHash(folderID, block.Hash)) {
if err != nil {
// We just ignore this and continue pulling instead (though
// there's a good chance that will fail too, if the DB is
// unhealthy).
l.Debugf("Failed to get information from DB about block %v in copier (folderID %v, file %v): %v", block.Hash, f.folderID, state.file.Name, err)
f.sl.DebugContext(ctx, "Failed to get block information from database", "blockHash", block.Hash, slogutil.FilePath(state.file.Name), slogutil.Error(err))
return false
}
if !f.copyBlockFromFile(e.FileName, e.Offset, state, ffs, block, buf) {
if !f.copyBlockFromFile(ctx, e.FileName, e.Offset, state, ffs, block, buf) {
if state.failed() != nil {
return false
}
@@ -1452,17 +1451,17 @@ func (f *sendReceiveFolder) copyBlockFromFolder(folderID string, block protocol.
// Returns true when the block was successfully copied.
// The passed buffer must be large enough to accommodate the block.
func (f *sendReceiveFolder) copyBlockFromFile(srcName string, srcOffset int64, state copyBlocksState, ffs fs.Filesystem, block protocol.BlockInfo, buf []byte) bool {
func (f *sendReceiveFolder) copyBlockFromFile(ctx context.Context, srcName string, srcOffset int64, state copyBlocksState, ffs fs.Filesystem, block protocol.BlockInfo, buf []byte) bool {
fd, err := ffs.Open(srcName)
if err != nil {
l.Debugf("Failed to open file %v trying to copy block %v (folderID %v): %v", srcName, block.Hash, f.folderID, err)
f.sl.DebugContext(ctx, "Failed to open source file for block copy", slogutil.FilePath(srcName), "blockHash", block.Hash, slogutil.Error(err))
return false
}
defer fd.Close()
_, err = fd.ReadAt(buf, srcOffset)
if err != nil {
l.Debugf("Failed to read block from file %v in copier (folderID: %v, hash: %v): %v", srcName, f.folderID, block.Hash, err)
f.sl.DebugContext(ctx, "Failed to read block from file", slogutil.FilePath(srcName), "blockHash", block.Hash, slogutil.Error(err))
return false
}
@@ -1471,7 +1470,7 @@ func (f *sendReceiveFolder) copyBlockFromFile(srcName string, srcOffset int64, s
// trust. (The other side can and will verify.)
if f.Type != config.FolderTypeReceiveEncrypted {
if err := f.verifyBuffer(buf, block); err != nil {
l.Debugf("Failed to verify buffer in copier (folderID: %v): %v", f.folderID, err)
f.sl.DebugContext(ctx, "Failed to verify block buffer", slogutil.Error(err))
return false
}
}
@@ -1483,13 +1482,13 @@ func (f *sendReceiveFolder) copyBlockFromFile(srcName string, srcOffset int64, s
}
if f.CopyRangeMethod != config.CopyRangeMethodStandard {
err = f.withLimiter(func() error {
err = f.withLimiter(ctx, func() error {
dstFd.mut.Lock()
defer dstFd.mut.Unlock()
return fs.CopyRange(f.CopyRangeMethod.ToFS(), fd, dstFd.fd, srcOffset, block.Offset, int64(block.Size))
})
} else {
err = f.limitedWriteAt(dstFd, buf, block.Offset)
err = f.limitedWriteAt(ctx, dstFd, buf, block.Offset)
}
if err != nil {
state.fail(fmt.Errorf("dst write: %w", err))
@@ -1511,7 +1510,7 @@ func (*sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) err
return nil
}
func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) pullerRoutine(ctx context.Context, in <-chan pullBlockState, out chan<- *sharedPullerState) {
requestLimiter := semaphore.New(f.PullerMaxPendingKiB * 1024)
var wg sync.WaitGroup
@@ -1529,7 +1528,7 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *
bytes := state.block.Size
if err := requestLimiter.TakeWithContext(f.ctx, bytes); err != nil {
if err := requestLimiter.TakeWithContext(ctx, bytes); err != nil {
state.fail(err)
out <- state.sharedPullerState
continue
@@ -1541,13 +1540,13 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *
defer wg.Done()
defer requestLimiter.Give(bytes)
f.pullBlock(state, out)
f.pullBlock(ctx, state, out)
}()
}
wg.Wait()
}
func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) pullBlock(ctx context.Context, state pullBlockState, out chan<- *sharedPullerState) {
// Get an fd to the temporary file. Technically we don't need it until
// after fetching the block, but if we run into an error here there is
// no point in issuing the request to the network.
@@ -1570,8 +1569,8 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
loop:
for {
select {
case <-f.ctx.Done():
state.fail(fmt.Errorf("folder stopped: %w", f.ctx.Err()))
case <-ctx.Done():
state.fail(fmt.Errorf("folder stopped: %w", ctx.Err()))
break loop
default:
}
@@ -1598,10 +1597,10 @@ loop:
activity.using(selected)
var buf []byte
blockNo := int(state.block.Offset / int64(state.file.BlockSize()))
buf, lastError = f.model.RequestGlobal(f.ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, state.block.Size, state.block.Hash, selected.FromTemporary)
buf, lastError = f.model.RequestGlobal(ctx, selected.ID, f.folderID, state.file.Name, blockNo, state.block.Offset, state.block.Size, state.block.Hash, selected.FromTemporary)
activity.done(selected)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, selected.ID.Short(), "returned error:", lastError)
f.sl.DebugContext(ctx, "Block request returned error", slogutil.FilePath(state.file.Name), "offset", state.block.Offset, "size", state.block.Size, "device", selected.ID.Short(), slogutil.Error(lastError))
continue
}
@@ -1615,12 +1614,12 @@ loop:
lastError = f.verifyBuffer(buf, state.block)
}
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "hash mismatch")
f.sl.DebugContext(ctx, "Block hash mismatch", slogutil.FilePath(state.file.Name), "offset", state.block.Offset, "size", state.block.Size)
continue
}
// Save the block data we got from the cluster
err = f.limitedWriteAt(fd, buf, state.block.Offset)
err = f.limitedWriteAt(ctx, fd, buf, state.block.Offset)
if err != nil {
state.fail(fmt.Errorf("save: %w", err))
} else {
@@ -1685,10 +1684,10 @@ func (f *sendReceiveFolder) performFinish(file, curFile protocol.FileInfo, hasCu
return nil
}
func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
func (f *sendReceiveFolder) finisherRoutine(ctx context.Context, in <-chan *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) {
for state := range in {
if closed, err := state.finalClose(); closed {
l.Debugln(f, "closing", state.file.Name)
f.sl.DebugContext(ctx, "Closing temp file", slogutil.FilePath(state.file.Name))
f.queue.Done(state.file.Name)
@@ -1699,7 +1698,7 @@ func (f *sendReceiveFolder) finisherRoutine(in <-chan *sharedPullerState, dbUpda
if err != nil {
f.newPullError(state.file.Name, fmt.Errorf("finishing: %w", err))
} else {
slog.Info("Synced file", f.LogAttr(), state.file.LogAttr(), slog.Group("blocks", slog.Int("local", state.reused+state.copyTotal), slog.Int("download", state.pullTotal)))
slog.InfoContext(ctx, "Synced file", f.LogAttr(), state.file.LogAttr(), slog.Group("blocks", slog.Int("local", state.reused+state.copyTotal), slog.Int("download", state.pullTotal)))
minBlocksPerBlock := state.file.BlockSize() / protocol.MinBlockSize
blockStatsMut.Lock()
@@ -1739,9 +1738,10 @@ func (f *sendReceiveFolder) Jobs(page, perpage int) ([]string, []string, int) {
// dbUpdaterRoutine aggregates db updates and commits them in batches no
// larger than 1000 items, and no more delayed than 2 seconds.
func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) int {
const maxBatchTime = 2 * time.Second
changed := 0
changedDirs := make(map[string]struct{})
found := false
var lastFile protocol.FileInfo
@@ -1754,11 +1754,11 @@ func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
if !f.DisableFsync {
fd, err := f.mtimefs.Open(dir)
if err != nil {
l.Debugf("fsync %q failed: %v", dir, err)
f.sl.Debug("Fsync failed", slogutil.FilePath(dir), slogutil.Error(err))
continue
}
if err := fd.Sync(); err != nil {
l.Debugf("fsync %q failed: %v", dir, err)
f.sl.Debug("Fsync failed", slogutil.FilePath(dir), slogutil.Error(err))
}
fd.Close()
}
@@ -1817,8 +1817,8 @@ loop:
job.file.Sequence = 0
batch.Append(job.file)
batch.FlushIfFull()
changed++
case <-tick.C:
batch.Flush()
@@ -1826,11 +1826,12 @@ loop:
}
batch.Flush()
return changed
}
// pullScannerRoutine aggregates paths to be scanned after pulling. The scan is
// scheduled once when scanChan is closed (scanning can not happen during pulling).
func (f *sendReceiveFolder) pullScannerRoutine(scanChan <-chan string) {
func (f *sendReceiveFolder) pullScannerRoutine(ctx context.Context, scanChan <-chan string) {
toBeScanned := make(map[string]struct{})
for path := range scanChan {
@@ -1840,7 +1841,7 @@ func (f *sendReceiveFolder) pullScannerRoutine(scanChan <-chan string) {
if len(toBeScanned) != 0 {
scanList := make([]string, 0, len(toBeScanned))
for path := range toBeScanned {
l.Debugln(f, "scheduling scan after pulling for", path)
slog.DebugContext(ctx, "Scheduling scan after pulling", slogutil.FilePath(path))
scanList = append(scanList, path)
}
f.Scan(scanList)
@@ -1881,7 +1882,7 @@ func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan cha
})
for _, match := range matches[f.MaxConflicts:] {
if gerr := f.mtimefs.Remove(match); gerr != nil {
l.Debugln(f, "removing extra conflict", gerr)
f.sl.Debug("Failed to remove extra conflict copy", slogutil.Error(gerr))
}
}
}
@@ -1893,7 +1894,7 @@ func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan cha
}
func (f *sendReceiveFolder) newPullError(path string, err error) {
if errors.Is(err, f.ctx.Err()) {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
// Error because the folder stopped - no point logging/tracking
return
}
@@ -1914,7 +1915,7 @@ func (f *sendReceiveFolder) newPullError(path string, err error) {
errStr := fmt.Sprintf("syncing: %s", err)
f.tempPullErrors[path] = errStr
l.Debugf("%v new error for %v: %v", f, path, err)
f.sl.Debug("New pull error", slogutil.FilePath(path), slogutil.Error(err))
}
// deleteItemOnDisk deletes the file represented by old that is about to be replaced by new.
@@ -2114,7 +2115,7 @@ func (f *sendReceiveFolder) scanIfItemChanged(name string, stat fs.FileInfo, ite
// I.e. non-nil error status means "Do not delete!" or "is already deleted".
func (f *sendReceiveFolder) checkToBeDeleted(file, cur protocol.FileInfo, hasCur bool, scanChan chan<- string) error {
if err := osutil.TraversesSymlink(f.mtimefs, filepath.Dir(file.Name)); err != nil {
l.Debugln(f, "not deleting item behind symlink on disk, but update db", file.Name)
f.sl.Debug("Not deleting item behind symlink on disk, but updating database", slogutil.FilePath(file.Name))
return fs.ErrNotExist
}
@@ -2128,7 +2129,7 @@ func (f *sendReceiveFolder) checkToBeDeleted(file, cur protocol.FileInfo, hasCur
scanChan <- file.Name
return errModified
}
l.Debugln(f, "not deleting item we don't have, but update db", file.Name)
f.sl.Debug("Not deleting item we don't have, but updating database", slogutil.FilePath(file.Name))
return err
}
@@ -2142,7 +2143,7 @@ func (f *sendReceiveFolder) setPlatformData(file *protocol.FileInfo, name string
if f.SyncXattrs {
// Set extended attributes.
if err := f.mtimefs.SetXattr(name, file.Platform.Xattrs(), f.XattrFilter); errors.Is(err, fs.ErrXattrsNotSupported) {
l.Debugf("Cannot set xattrs on %q: %v", file.Name, err)
f.sl.Debug("Cannot set xattrs (not supported)", slogutil.FilePath(file.Name), slogutil.Error(err))
} else if err != nil {
return err
}
@@ -2183,15 +2184,15 @@ func (f *sendReceiveFolder) inWritableDir(fn func(string) error, path string) er
return inWritableDir(fn, f.mtimefs, path, f.IgnorePerms)
}
func (f *sendReceiveFolder) limitedWriteAt(fd io.WriterAt, data []byte, offset int64) error {
return f.withLimiter(func() error {
func (f *sendReceiveFolder) limitedWriteAt(ctx context.Context, fd io.WriterAt, data []byte, offset int64) error {
return f.withLimiter(ctx, func() error {
_, err := fd.WriteAt(data, offset)
return err
})
}
func (f *sendReceiveFolder) withLimiter(fn func() error) error {
if err := f.writeLimiter.TakeWithContext(f.ctx, 1); err != nil {
func (f *sendReceiveFolder) withLimiter(ctx context.Context, fn func() error) error {
if err := f.writeLimiter.TakeWithContext(ctx, 1); err != nil {
return err
}
defer f.writeLimiter.Give(1)
@@ -2233,7 +2234,7 @@ func existingConflicts(name string, fs fs.Filesystem) []string {
ext := filepath.Ext(name)
matches, err := fs.Glob(name[:len(name)-len(ext)] + ".sync-conflict-????????-??????*" + ext)
if err != nil {
l.Debugln("globbing for conflicts", err)
slog.Debug("Globbing for conflicts failed", slogutil.Error(err))
}
return matches
}

View File

@@ -65,8 +65,6 @@ func prepareTmpFile(to fs.Filesystem) (string, error) {
return tmpName, nil
}
var folders = []string{"default"}
var diffTestData = []struct {
a string
b string
@@ -112,8 +110,8 @@ func createEmptyFileInfo(t *testing.T, name string, fs fs.Filesystem) protocol.F
}
// Sets up a folder and model, but makes sure the services aren't actually running.
func setupSendReceiveFolder(t testing.TB, files ...protocol.FileInfo) (*testModel, *sendReceiveFolder, context.CancelFunc) {
w, fcfg, wCancel := newDefaultCfgWrapper()
func setupSendReceiveFolder(t testing.TB, files ...protocol.FileInfo) (*testModel, *sendReceiveFolder) {
w, fcfg := newDefaultCfgWrapper(t)
// Initialise model and stop immediately.
model := setupModel(t, w)
model.cancel()
@@ -121,14 +119,13 @@ func setupSendReceiveFolder(t testing.TB, files ...protocol.FileInfo) (*testMode
r, _ := model.folderRunners.Get(fcfg.ID)
f := r.(*sendReceiveFolder)
f.tempPullErrors = make(map[string]string)
f.ctx = context.Background()
// Update index
if files != nil {
f.updateLocalsFromScanning(files)
}
return model, f, wCancel
return model, f
}
// Layout of the files: (indexes from the above array)
@@ -146,12 +143,11 @@ func TestHandleFile(t *testing.T) {
requiredFile := existingFile
requiredFile.Blocks = blocks[1:]
_, f, wcfgCancel := setupSendReceiveFolder(t, existingFile)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t, existingFile)
copyChan := make(chan copyBlocksState, 1)
f.handleFile(requiredFile, copyChan)
f.handleFile(t.Context(), requiredFile, copyChan)
// Receive the results
toCopy := <-copyChan
@@ -188,8 +184,7 @@ func TestHandleFileWithTemp(t *testing.T) {
requiredFile := existingFile
requiredFile.Blocks = blocks[1:]
_, f, wcfgCancel := setupSendReceiveFolder(t, existingFile)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t, existingFile)
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
t.Fatal(err)
@@ -197,7 +192,7 @@ func TestHandleFileWithTemp(t *testing.T) {
copyChan := make(chan copyBlocksState, 1)
f.handleFile(requiredFile, copyChan)
f.handleFile(t.Context(), requiredFile, copyChan)
// Receive the results
toCopy := <-copyChan
@@ -238,8 +233,7 @@ func TestCopierFinder(t *testing.T) {
requiredFile.Blocks = blocks[1:]
requiredFile.Name = "file2"
_, f, wcfgCancel := setupSendReceiveFolder(t, existingFile)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t, existingFile)
if _, err := prepareTmpFile(f.Filesystem()); err != nil {
t.Fatal(err)
@@ -250,10 +244,10 @@ func TestCopierFinder(t *testing.T) {
finisherChan := make(chan *sharedPullerState, 1)
// Run a single fetcher routine
go f.copierRoutine(copyChan, pullChan, finisherChan)
go f.copierRoutine(t.Context(), copyChan, pullChan, finisherChan)
defer close(copyChan)
f.handleFile(requiredFile, copyChan)
f.handleFile(t.Context(), requiredFile, copyChan)
timeout := time.After(10 * time.Second)
pulls := make([]pullBlockState, 4)
@@ -302,7 +296,7 @@ func TestCopierFinder(t *testing.T) {
}
// Verify that the fetched blocks have actually been written to the temp file
blks, err := scanner.HashFile(context.TODO(), f.ID, f.Filesystem(), tempFile, protocol.MinBlockSize, nil)
blks, err := scanner.HashFile(t.Context(), f.ID, f.Filesystem(), tempFile, protocol.MinBlockSize, nil)
if err != nil {
t.Log(err)
}
@@ -319,8 +313,7 @@ func TestCopierCleanup(t *testing.T) {
// Create a file
file := setupFile("test", []int{0})
file.Size = 1
m, f, wcfgCancel := setupSendReceiveFolder(t, file)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t, file)
file.Blocks = []protocol.BlockInfo{blocks[1]}
file.Version = file.Version.Update(myID.Short())
@@ -352,8 +345,7 @@ func TestCopierCleanup(t *testing.T) {
func TestDeregisterOnFailInCopy(t *testing.T) {
file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
// Set up our evet subscription early
s := m.evLogger.Subscribe(events.ItemFinished)
@@ -371,8 +363,8 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
copyChan, copyWg := startCopier(t.Context(), f, pullChan, finisherBufferChan)
go f.finisherRoutine(t.Context(), finisherChan, dbUpdateChan, make(chan string))
defer func() {
close(copyChan)
@@ -382,7 +374,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
close(finisherChan)
}()
f.handleFile(file, copyChan)
f.handleFile(t.Context(), file, copyChan)
// Receive a block at puller, to indicate that at least a single copier
// loop has been performed.
@@ -451,8 +443,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
func TestDeregisterOnFailInPull(t *testing.T) {
file := setupFile("filex", []int{0, 2, 0, 0, 5, 0, 0, 8})
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
// Set up our evet subscription early
s := m.evLogger.Subscribe(events.ItemFinished)
@@ -470,14 +461,14 @@ func TestDeregisterOnFailInPull(t *testing.T) {
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
copyChan, copyWg := startCopier(t.Context(), f, pullChan, finisherBufferChan)
var pullWg sync.WaitGroup
pullWg.Add(1)
go func() {
f.pullerRoutine(pullChan, finisherBufferChan)
f.pullerRoutine(t.Context(), pullChan, finisherBufferChan)
pullWg.Done()
}()
go f.finisherRoutine(finisherChan, dbUpdateChan, make(chan string))
go f.finisherRoutine(t.Context(), finisherChan, dbUpdateChan, make(chan string))
defer func() {
// Unblock copier and puller
go func() {
@@ -492,7 +483,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
close(finisherChan)
}()
f.handleFile(file, copyChan)
f.handleFile(t.Context(), file, copyChan)
// Receive at finisher, we should error out as puller has nowhere to pull
// from.
@@ -553,8 +544,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
}
func TestIssue3164(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
ignDir := filepath.Join("issue3164", "oktodelete")
@@ -566,7 +556,7 @@ func TestIssue3164(t *testing.T) {
Name: "issue3164",
}
must(t, f.scanSubdirs(nil))
must(t, f.scanSubdirs(t.Context(), nil))
matcher := ignore.New(ffs)
must(t, matcher.Parse(bytes.NewBufferString("(?d)oktodelete"), ""))
@@ -583,8 +573,8 @@ func TestIssue3164(t *testing.T) {
func TestDiff(t *testing.T) {
for i, test := range diffTestData {
a, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.a), test.s, -1, nil)
b, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.b), test.s, -1, nil)
a, _ := scanner.Blocks(t.Context(), bytes.NewBufferString(test.a), test.s, -1, nil)
b, _ := scanner.Blocks(t.Context(), bytes.NewBufferString(test.b), test.s, -1, nil)
_, d := blockDiff(a, b)
if len(d) != len(test.d) {
t.Fatalf("Incorrect length for diff %d; %d != %d", i, len(d), len(test.d))
@@ -604,9 +594,9 @@ func TestDiff(t *testing.T) {
func BenchmarkDiff(b *testing.B) {
testCases := make([]struct{ a, b []protocol.BlockInfo }, 0, len(diffTestData))
for _, test := range diffTestData {
a, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.a), test.s, -1, nil)
b, _ := scanner.Blocks(context.TODO(), bytes.NewBufferString(test.b), test.s, -1, nil)
testCases = append(testCases, struct{ a, b []protocol.BlockInfo }{a, b})
aBlocks, _ := scanner.Blocks(b.Context(), bytes.NewBufferString(test.a), test.s, -1, nil)
bBlocks, _ := scanner.Blocks(b.Context(), bytes.NewBufferString(test.b), test.s, -1, nil)
testCases = append(testCases, struct{ a, b []protocol.BlockInfo }{aBlocks, bBlocks})
}
b.ReportAllocs()
b.ResetTimer()
@@ -643,8 +633,7 @@ func TestDiffEmpty(t *testing.T) {
// option is true and the permissions do not match between the file on disk and
// in the db.
func TestDeleteIgnorePerms(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
f.IgnorePerms = true
@@ -682,7 +671,7 @@ func TestCopyOwner(t *testing.T) {
)
// This test hung on a regression, taking a long time to fail - speed that up.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
defer cancel()
go func() {
<-ctx.Done()
@@ -695,8 +684,7 @@ func TestCopyOwner(t *testing.T) {
// Set up a folder with the CopyParentOwner bit and backed by a fake
// filesystem.
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
f.folder.FolderConfiguration = newFolderConfiguration(m.cfg, f.ID, f.Label, config.FilesystemTypeFake, "/TestCopyOwner")
f.folder.FolderConfiguration.CopyOwnershipFromParent = true
@@ -748,15 +736,15 @@ func TestCopyOwner(t *testing.T) {
// comes the finisher is done.
finisherChan := make(chan *sharedPullerState)
copierChan, copyWg := startCopier(f, nil, finisherChan)
go f.finisherRoutine(finisherChan, dbUpdateChan, nil)
copierChan, copyWg := startCopier(t.Context(), f, nil, finisherChan)
go f.finisherRoutine(t.Context(), finisherChan, dbUpdateChan, nil)
defer func() {
close(copierChan)
copyWg.Wait()
close(finisherChan)
}()
f.handleFile(file, copierChan)
f.handleFile(t.Context(), file, copierChan)
<-dbUpdateChan
info, err = f.mtimefs.Lstat("foo/bar/baz")
@@ -794,8 +782,7 @@ func TestCopyOwner(t *testing.T) {
// TestSRConflictReplaceFileByDir checks that a conflict is created when an existing file
// is replaced with a directory and versions are conflicting
func TestSRConflictReplaceFileByDir(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
name := "foo"
@@ -826,8 +813,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
// TestSRConflictReplaceFileByLink checks that a conflict is created when an existing file
// is replaced with a link and versions are conflicting
func TestSRConflictReplaceFileByLink(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
name := "foo"
@@ -859,8 +845,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
// TestDeleteBehindSymlink checks that we don't delete or schedule a scan
// when trying to delete a file behind a symlink.
func TestDeleteBehindSymlink(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
link := "link"
@@ -898,16 +883,14 @@ func TestDeleteBehindSymlink(t *testing.T) {
// Reproduces https://github.com/syncthing/syncthing/issues/6559
func TestPullCtxCancel(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
pullChan := make(chan pullBlockState)
finisherChan := make(chan *sharedPullerState)
var cancel context.CancelFunc
f.ctx, cancel = context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
go f.pullerRoutine(pullChan, finisherChan)
go f.pullerRoutine(ctx, pullChan, finisherChan)
defer close(pullChan)
emptyState := func() pullBlockState {
@@ -940,8 +923,7 @@ func TestPullCtxCancel(t *testing.T) {
}
func TestPullDeleteUnscannedDir(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
dir := "foobar"
@@ -969,14 +951,13 @@ func TestPullDeleteUnscannedDir(t *testing.T) {
}
func TestPullCaseOnlyPerformFinish(t *testing.T) {
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
name := "foo"
contents := []byte("contents")
writeFile(t, ffs, name, contents)
must(t, f.scanSubdirs(nil))
must(t, f.scanSubdirs(t.Context(), nil))
var cur protocol.FileInfo
hasCur := false
@@ -1032,8 +1013,7 @@ func TestPullCaseOnlySymlink(t *testing.T) {
}
func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) {
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
ffs := f.Filesystem()
name := "foo"
@@ -1043,7 +1023,7 @@ func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) {
must(t, ffs.CreateSymlink("target", name))
}
must(t, f.scanSubdirs(nil))
must(t, f.scanSubdirs(t.Context(), nil))
var cur protocol.FileInfo
hasCur := false
it, errFn := m.LocalFiles(f.ID, protocol.LocalDeviceID)
@@ -1089,8 +1069,7 @@ func testPullCaseOnlyDirOrSymlink(t *testing.T, dir bool) {
}
func TestPullTempFileCaseConflict(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
copyChan := make(chan copyBlocksState, 1)
@@ -1106,7 +1085,7 @@ func TestPullTempFileCaseConflict(t *testing.T) {
fd.Close()
}
f.handleFile(file, copyChan)
f.handleFile(t.Context(), file, copyChan)
cs := <-copyChan
if _, err := cs.tempFile(); err != nil {
@@ -1117,8 +1096,7 @@ func TestPullTempFileCaseConflict(t *testing.T) {
}
func TestPullCaseOnlyRename(t *testing.T) {
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
// tempNameConfl := fs.TempName(confl)
@@ -1132,7 +1110,7 @@ func TestPullCaseOnlyRename(t *testing.T) {
fd.Close()
}
must(t, f.scanSubdirs(nil))
must(t, f.scanSubdirs(t.Context(), nil))
cur, ok := m.testCurrentFolderFile(f.ID, name)
if !ok {
@@ -1158,8 +1136,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
t.Skip()
}
m, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
m, f := setupSendReceiveFolder(t)
conn := addFakeConn(m, device1, f.ID)
name := "foo"
@@ -1172,7 +1149,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
fd.Close()
}
must(t, f.scanSubdirs(nil))
must(t, f.scanSubdirs(t.Context(), nil))
file, ok := m.testCurrentFolderFile(f.ID, name)
if !ok {
@@ -1182,7 +1159,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
scanChan := make(chan string)
changed, err := f.pullerIteration(scanChan)
changed, err := f.pullerIteration(t.Context(), scanChan)
must(t, err)
if changed != 1 {
t.Error("Expected one change in pull, got", changed)
@@ -1200,8 +1177,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
}
func TestPullDeleteCaseConflict(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
name := "foo"
fi := protocol.FileInfo{Name: "Foo"}
@@ -1232,8 +1208,7 @@ func TestPullDeleteCaseConflict(t *testing.T) {
}
func TestPullDeleteIgnoreChildDir(t *testing.T) {
_, f, wcfgCancel := setupSendReceiveFolder(t)
defer wcfgCancel()
_, f := setupSendReceiveFolder(t)
parent := "parent"
del := "ignored"
@@ -1268,12 +1243,12 @@ func cleanupSharedPullerState(s *sharedPullerState) {
s.writer.mut.Unlock()
}
func startCopier(f *sendReceiveFolder, pullChan chan<- pullBlockState, finisherChan chan<- *sharedPullerState) (chan copyBlocksState, *sync.WaitGroup) {
func startCopier(ctx context.Context, f *sendReceiveFolder, pullChan chan<- pullBlockState, finisherChan chan<- *sharedPullerState) (chan copyBlocksState, *sync.WaitGroup) {
copyChan := make(chan copyBlocksState)
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
f.copierRoutine(copyChan, pullChan, finisherChan)
f.copierRoutine(ctx, copyChan, pullChan, finisherChan)
wg.Done()
}()
return copyChan, wg

View File

@@ -690,6 +690,7 @@ type ConnectionStats struct {
type ConnectionInfo struct {
protocol.Statistics
Address string `json:"address"`
Type string `json:"type"`
IsLocal bool `json:"isLocal"`
@@ -3484,6 +3485,7 @@ func redactPathError(err error) (error, bool) {
type redactedError struct {
error
redacted error
}

View File

@@ -77,9 +77,8 @@ func addFolderDevicesToClusterConfig(cc *protocol.ClusterConfig, remote protocol
}
func TestRequest(t *testing.T) {
wrapper, fcfg, cancel := newDefaultCfgWrapper()
wrapper, fcfg := newDefaultCfgWrapper(t)
ffs := fcfg.Filesystem()
defer cancel()
m := setupModel(t, wrapper)
defer cleanupModel(m)
@@ -164,8 +163,7 @@ func BenchmarkIndex_100(b *testing.B) {
}
func benchmarkIndex(b *testing.B, nfiles int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
m, _, fcfg := setupModelWithConnection(b)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := genFiles(nfiles)
@@ -191,8 +189,7 @@ func BenchmarkIndexUpdate_10000_1(b *testing.B) {
}
func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
m, _, fcfg := setupModelWithConnection(b)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := genFiles(nfiles)
@@ -223,7 +220,7 @@ func BenchmarkRequestOut(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
data, err := m.RequestGlobal(context.Background(), device1, "default", files[i%n].Name, 0, 0, 32, nil, false)
data, err := m.RequestGlobal(b.Context(), device1, "default", files[i%n].Name, 0, 0, 32, nil, false)
if err != nil {
b.Error(err)
}
@@ -1777,8 +1774,7 @@ func TestRWScanRecovery(t *testing.T) {
}
func TestGlobalDirectoryTree(t *testing.T) {
m, conn, fcfg, wCancel := setupModelWithConnection(t)
defer wCancel()
m, conn, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
var seq int64
@@ -2084,8 +2080,7 @@ func BenchmarkTree_100_10(b *testing.B) {
}
func benchmarkTree(b *testing.B, n1, n2 int) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(b)
defer wcfgCancel()
m, _, fcfg := setupModelWithConnection(b)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.ScanFolder(fcfg.ID)
@@ -2342,8 +2337,7 @@ func TestIssue3829(t *testing.T) {
// TestIssue4573 tests that contents of an unavailable dir aren't marked deleted
func TestIssue4573(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
testFs := fcfg.Filesystem()
defer os.RemoveAll(testFs.URI())
@@ -2372,8 +2366,7 @@ func TestIssue4573(t *testing.T) {
// TestInternalScan checks whether various fs operations are correctly represented
// in the db after scanning.
func TestInternalScan(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
testFs := fcfg.Filesystem()
defer os.RemoveAll(testFs.URI())
@@ -2472,8 +2465,7 @@ func TestCustomMarkerName(t *testing.T) {
}
func TestRemoveDirWithContent(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, conn, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -2533,8 +2525,7 @@ func TestRemoveDirWithContent(t *testing.T) {
}
func TestIssue4475(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, conn, fcfg := setupModelWithConnection(t)
defer cleanupModel(m)
testFs := fcfg.Filesystem()
@@ -2870,8 +2861,7 @@ func TestIssue4903(t *testing.T) {
func TestIssue5002(t *testing.T) {
// recheckFile should not panic when given an index equal to the number of blocks
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
ffs := fcfg.Filesystem()
fd, err := ffs.Create("foo")
@@ -2899,8 +2889,7 @@ func TestIssue5002(t *testing.T) {
}
func TestParentOfUnignored(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
ffs := fcfg.Filesystem()
must(t, ffs.Mkdir("bar", 0o755))
@@ -2979,15 +2968,13 @@ func TestFolderRestartZombies(t *testing.T) {
}
func TestRequestLimit(t *testing.T) {
wrapper, fcfg, cancel := newDefaultCfgWrapper()
wrapper, fcfg := newDefaultCfgWrapper(t)
ffs := fcfg.Filesystem()
file := "tmpfile"
fd, err := ffs.Create(file)
must(t, err)
fd.Close()
defer cancel()
waiter, err := wrapper.Modify(func(cfg *config.Configuration) {
_, i, _ := cfg.Device(device1)
cfg.Devices[i].MaxRequestKiB = 1
@@ -3037,8 +3024,7 @@ func TestConnCloseOnRestart(t *testing.T) {
protocol.CloseTimeout = oldCloseTimeout
}()
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, w)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
@@ -3079,8 +3065,7 @@ func TestConnCloseOnRestart(t *testing.T) {
}
func TestModTimeWindow(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
tfs := modtimeTruncatingFS{
trunc: 0,
Filesystem: fcfg.Filesystem(),
@@ -3141,8 +3126,7 @@ func TestModTimeWindow(t *testing.T) {
}
func TestDevicePause(t *testing.T) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, _, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
sub := m.evLogger.Subscribe(events.DevicePaused)
@@ -3171,8 +3155,7 @@ func TestDevicePause(t *testing.T) {
}
func TestDeviceWasSeen(t *testing.T) {
m, _, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, _, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.deviceWasSeen(device1)
@@ -3216,8 +3199,7 @@ func TestNewLimitedRequestResponse(t *testing.T) {
}
func TestSummaryPausedNoError(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
pauseFolder(t, wcfg, fcfg.ID, true)
m := setupModel(t, wcfg)
defer cleanupModel(m)
@@ -3229,14 +3211,15 @@ func TestSummaryPausedNoError(t *testing.T) {
}
func TestFolderAPIErrors(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
pauseFolder(t, wcfg, fcfg.ID, true)
m := setupModel(t, wcfg)
defer cleanupModel(m)
methods := []func(folder string) error{
m.ScanFolder,
func(folder string) error {
return m.ScanFolder(folder)
},
func(folder string) error {
return m.ScanFolderSubdirs(folder, nil)
},
@@ -3261,8 +3244,7 @@ func TestFolderAPIErrors(t *testing.T) {
}
func TestRenameSequenceOrder(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, wcfg)
defer cleanupModel(m)
@@ -3324,8 +3306,7 @@ func TestRenameSequenceOrder(t *testing.T) {
}
func TestRenameSameFile(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, wcfg)
defer cleanupModel(m)
@@ -3368,8 +3349,7 @@ func TestRenameSameFile(t *testing.T) {
}
func TestBlockListMap(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, wcfg)
defer cleanupModel(m)
@@ -3437,8 +3417,7 @@ func TestBlockListMap(t *testing.T) {
}
func TestScanRenameCaseOnly(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, wcfg)
defer cleanupModel(m)
@@ -3558,9 +3537,8 @@ func TestAddFolderCompletion(t *testing.T) {
}
func TestScanDeletedROChangedOnSR(t *testing.T) {
m, conn, fcfg, wCancel := setupModelWithConnection(t)
m, conn, fcfg := setupModelWithConnection(t)
ffs := fcfg.Filesystem()
defer wCancel()
defer cleanupModelAndRemoveDir(m, ffs.URI())
fcfg.Type = config.FolderTypeReceiveOnly
setFolder(t, m.cfg, fcfg)
@@ -3600,8 +3578,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSecond bool, pre func(config.Wrapper), fn func(config.Wrapper)) {
t.Helper()
wcfg, _, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, _ := newDefaultCfgWrapper(t)
m := setupModel(t, wcfg)
defer cleanupModel(m)
@@ -3663,8 +3640,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
// That then causes these files to be considered as needed, while they are not.
// https://github.com/syncthing/syncthing/issues/6961
func TestIssue6961(t *testing.T) {
wcfg, fcfg, wcfgCancel := newDefaultCfgWrapper()
defer wcfgCancel()
wcfg, fcfg := newDefaultCfgWrapper(t)
tfs := fcfg.Filesystem()
waiter, err := wcfg.Modify(func(cfg *config.Configuration) {
cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))
@@ -3728,8 +3704,7 @@ func TestIssue6961(t *testing.T) {
}
func TestCompletionEmptyGlobal(t *testing.T) {
m, conn, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, conn, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID.Short()), Sequence: 1}}
m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files)
@@ -3743,8 +3718,7 @@ func TestCompletionEmptyGlobal(t *testing.T) {
}
func TestNeedMetaAfterIndexReset(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
addDevice2(t, w, fcfg)
m := setupModel(t, w)
defer cleanupModelAndRemoveDir(m, fcfg.Path)
@@ -3786,8 +3760,7 @@ func TestCcCheckEncryption(t *testing.T) {
t.Skip("skipping on short testing - generating encryption tokens is slow")
}
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, w)
m.cancel()
defer cleanupModel(m)
@@ -3927,8 +3900,7 @@ func TestCcCheckEncryption(t *testing.T) {
func TestCCFolderNotRunning(t *testing.T) {
// Create the folder, but don't start it.
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
tfs := fcfg.Filesystem()
m := newModel(t, w, myID, nil)
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -3955,8 +3927,7 @@ func TestCCFolderNotRunning(t *testing.T) {
}
func TestPendingFolder(t *testing.T) {
w, _, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, _ := newDefaultCfgWrapper(t)
m := setupModel(t, w)
defer cleanupModel(m)
@@ -4035,11 +4006,10 @@ func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) {
}
func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) {
w, fcfg, wCancel := newDefaultCfgWrapper()
w, fcfg := newDefaultCfgWrapper(t)
tfs := fcfg.Filesystem()
fcfg.Type = ft
setFolder(t, w, fcfg)
defer wCancel()
m := setupModel(t, w)
defer cleanupModelAndRemoveDir(m, tfs.URI())

View File

@@ -30,8 +30,7 @@ func TestRequestSimple(t *testing.T) {
// Verify that the model performs a request and creates a file based on
// an incoming index update.
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -78,8 +77,7 @@ func TestSymlinkTraversalRead(t *testing.T) {
return
}
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
// We listen for incoming index updates and trigger when we see one for
@@ -121,8 +119,7 @@ func TestSymlinkTraversalWrite(t *testing.T) {
return
}
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
// We listen for incoming index updates and trigger when we see one for
@@ -180,8 +177,7 @@ func TestSymlinkTraversalWrite(t *testing.T) {
func TestRequestCreateTmpSymlink(t *testing.T) {
// Test that an update for a temporary file is invalidated
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
// We listen for incoming index updates and trigger when we see one for
@@ -356,8 +352,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
}
func TestIssue4841(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
received := make(chan []protocol.FileInfo)
@@ -405,8 +400,7 @@ func TestIssue4841(t *testing.T) {
}
func TestRescanIfHaveInvalidContent(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -467,8 +461,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
func TestParentDeletion(t *testing.T) {
t.Skip("flaky")
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
testFs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, testFs.URI())
@@ -546,8 +539,7 @@ func TestRequestSymlinkWindows(t *testing.T) {
t.Skip("windows specific test")
}
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
received := make(chan []protocol.FileInfo)
@@ -623,8 +615,7 @@ func equalContents(fs fs.Filesystem, path string, contents []byte) error {
func TestRequestRemoteRenameChanged(t *testing.T) {
t.Skip("flaky")
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModel(m)
@@ -756,8 +747,7 @@ func TestRequestRemoteRenameChanged(t *testing.T) {
}
func TestRequestRemoteRenameConflict(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModel(m)
@@ -846,8 +836,7 @@ func TestRequestRemoteRenameConflict(t *testing.T) {
}
func TestRequestDeleteChanged(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -925,8 +914,7 @@ func TestRequestDeleteChanged(t *testing.T) {
}
func TestNeedFolderFiles(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
defer cleanupModel(m)
sub := m.evLogger.Subscribe(events.RemoteIndexUpdated)
@@ -970,8 +958,7 @@ func TestNeedFolderFiles(t *testing.T) {
// propagated upon un-ignoring.
// https://github.com/syncthing/syncthing/issues/6038
func TestIgnoreDeleteUnignore(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
m := setupModel(t, w)
fss := fcfg.Filesystem()
defer cleanupModel(m)
@@ -1065,8 +1052,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
// TestRequestLastFileProgress checks that the last pulled file (here only) is registered
// as in progress.
func TestRequestLastFileProgress(t *testing.T) {
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -1100,8 +1086,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
done := make(chan struct{})
defer close(done)
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
@@ -1213,8 +1198,7 @@ func TestRequestIndexSenderPause(t *testing.T) {
}
func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
tfs := fcfg.Filesystem()
dir1 := "foo"
dir2 := "bar"
@@ -1280,8 +1264,7 @@ func TestRequestReceiveEncrypted(t *testing.T) {
t.Skip("skipping on short testing - scrypt is too slow")
}
w, fcfg, wCancel := newDefaultCfgWrapper()
defer wCancel()
w, fcfg := newDefaultCfgWrapper(t)
tfs := fcfg.Filesystem()
fcfg.Type = config.FolderTypeReceiveEncrypted
setFolder(t, w, fcfg)
@@ -1375,8 +1358,7 @@ func TestRequestGlobalInvalidToValid(t *testing.T) {
done := make(chan struct{})
defer close(done)
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
m, fc, fcfg := setupModelWithConnection(t)
fcfg.Devices = append(fcfg.Devices, config.FolderDeviceConfiguration{DeviceID: device2})
waiter, err := m.cfg.Modify(func(cfg *config.Configuration) {
cfg.SetDevice(newDeviceConfiguration(cfg.Defaults.Device, device2, "device2"))

View File

@@ -85,19 +85,24 @@ func init() {
}
func newConfigWrapper(cfg config.Configuration) (config.Wrapper, context.CancelFunc) {
wrapper := config.Wrap("", cfg, myID, events.NoopLogger)
ctx, cancel := context.WithCancel(context.Background())
go wrapper.Serve(ctx)
return wrapper, cancel
return newConfigWrapperFromContext(context.Background(), cfg)
}
func newDefaultCfgWrapper() (config.Wrapper, config.FolderConfiguration, context.CancelFunc) {
w, cancel := newConfigWrapper(defaultCfgWrapper.RawCopy())
func newDefaultCfgWrapper(t testing.TB) (config.Wrapper, config.FolderConfiguration) {
w, cancel := newConfigWrapperFromContext(t.Context(), defaultCfgWrapper.RawCopy())
t.Cleanup(cancel)
fcfg := newFolderConfig()
_, _ = w.Modify(func(cfg *config.Configuration) {
cfg.SetFolder(fcfg)
})
return w, fcfg, cancel
return w, fcfg
}
func newConfigWrapperFromContext(ctx context.Context, cfg config.Configuration) (config.Wrapper, context.CancelFunc) {
wrapper := config.Wrap("", cfg, myID, events.NoopLogger)
ctx, cancel := context.WithCancel(ctx)
go wrapper.Serve(ctx)
return wrapper, cancel
}
func newFolderConfig() config.FolderConfiguration {
@@ -108,11 +113,11 @@ func newFolderConfig() config.FolderConfiguration {
return cfg
}
func setupModelWithConnection(t testing.TB) (*testModel, *fakeConnection, config.FolderConfiguration, context.CancelFunc) {
func setupModelWithConnection(t testing.TB) (*testModel, *fakeConnection, config.FolderConfiguration) {
t.Helper()
w, fcfg, cancel := newDefaultCfgWrapper()
w, fcfg := newDefaultCfgWrapper(t)
m, fc := setupModelWithConnectionFromWrapper(t, w)
return m, fc, fcfg, cancel
return m, fc, fcfg
}
func setupModelWithConnectionFromWrapper(t testing.TB, w config.Wrapper) (*testModel, *fakeConnection) {
@@ -157,7 +162,7 @@ func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, protectedF
mdb.Close()
})
m := NewModel(cfg, id, mdb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(t.Context())
go evLogger.Serve(ctx)
return &testModel{
model: m,

View File

@@ -356,7 +356,7 @@ func (s *Service) tryNATDevice(ctx context.Context, natd Device, intAddr Address
l.Debugf("Error extending lease on %v (external port %d -> internal port %d): %v", natd.ID(), extPort, intAddr.Port, err)
}
for i := 0; i < 10; i++ {
for range 10 {
select {
case <-ctx.Done():
return []Address{}, ctx.Err()

View File

@@ -44,7 +44,7 @@ func CreateAtomic(path string) (*AtomicWriter, error) {
func CreateAtomicFilesystem(filesystem fs.Filesystem, path string) (*AtomicWriter, error) {
// The security of this depends on the tempfile having secure
// permissions, 0600, from the beginning. This is what os.CreateTemp
// does. We have a test that verifies that that is the case, should this
// does. We have a test that verifies that this is the case, should this
// ever change in the standard library in the future.
fd, err := TempFile(filesystem, filepath.Dir(path), TempPrefix)
if err != nil {

View File

@@ -48,7 +48,7 @@ func nextSuffix() string {
// to remove the file when no longer needed.
func TempFile(filesystem fs.Filesystem, dir, prefix string) (f fs.File, err error) {
nconflict := 0
for i := 0; i < 10000; i++ {
for range 10000 {
name := filepath.Join(dir, prefix+nextSuffix())
f, err = filesystem.OpenFile(name, fs.OptReadWrite|fs.OptCreate|fs.OptExclusive, 0o600)
if fs.IsExist(err) {

View File

@@ -7,7 +7,6 @@
package osutil
import (
"fmt"
"path/filepath"
"github.com/syncthing/syncthing/lib/fs"
@@ -19,7 +18,7 @@ type TraversesSymlinkError struct {
}
func (e *TraversesSymlinkError) Error() string {
return fmt.Sprintf("traverses symlink: %s", e.path)
return "traverses symlink: " + e.path
}
// NotADirectoryError is an error indicating an expected path is not a directory
@@ -28,7 +27,7 @@ type NotADirectoryError struct {
}
func (e *NotADirectoryError) Error() string {
return fmt.Sprintf("not a directory: %s", e.path)
return "not a directory: " + e.path
}
// TraversesSymlink returns an error if any path component of name (including name

View File

@@ -9,7 +9,6 @@ package pmp
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
"strings"
@@ -90,7 +89,7 @@ type wrapper struct {
}
func (w *wrapper) ID() string {
return fmt.Sprintf("NAT-PMP@%s", w.gatewayIP.String())
return "NAT-PMP@" + w.gatewayIP.String()
}
func (w *wrapper) GetLocalIPv4Address() net.IP {

View File

@@ -14,6 +14,7 @@ import (
type countingReader struct {
io.Reader
idString string
tot atomic.Int64 // bytes
last atomic.Int64 // unix nanos
@@ -41,6 +42,7 @@ func (c *countingReader) Last() time.Time {
type countingWriter struct {
io.Writer
idString string
tot atomic.Int64 // bytes
last atomic.Int64 // unix nanos

View File

@@ -181,7 +181,7 @@ func luhnify(s string) (string, error) {
}
res := make([]byte, 4*(13+1))
for i := 0; i < 4; i++ {
for i := range 4 {
p := s[i*13 : (i+1)*13]
copy(res[i*(13+1):], p)
l, err := luhn32(p)
@@ -199,7 +199,7 @@ func unluhnify(s string) (string, error) {
}
res := make([]byte, 52)
for i := 0; i < 4; i++ {
for i := range 4 {
p := s[i*(13+1) : (i+1)*(13+1)-1]
copy(res[i*13:], p)
l, err := luhn32(p)
@@ -216,7 +216,7 @@ func unluhnify(s string) (string, error) {
func chunkify(s string) string {
chunks := len(s) / 7
res := make([]byte, chunks*(7+1)-1)
for i := 0; i < chunks; i++ {
for i := range chunks {
if i > 0 {
res[i*(7+1)-1] = '-'
}

View File

@@ -171,6 +171,7 @@ func (e encryptedModel) Closed(err error) {
// encrypts outgoing metadata and decrypts incoming responses.
type encryptedConnection struct {
ConnectionInfo
conn *rawConnection
folderKeys *folderKeyRegistry
keyGen *KeyGenerator

View File

@@ -406,7 +406,7 @@ func (c *rawConnection) readerLoop() {
for {
msg, err := c.readMessage(fourByteBuf)
if err != nil {
if err == errUnknownMessage {
if errors.Is(err, errUnknownMessage) {
// Unknown message types are skipped, for future extensibility.
continue
}

View File

@@ -41,7 +41,7 @@ func String(l int) string {
var sb strings.Builder
sb.Grow(l)
for i := 0; i < l; i++ {
for range l {
sb.WriteByte(randomCharset[defaultSecureRand.Intn(len(randomCharset))])
}
return sb.String()

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