Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0c401efeb | |||
| 658ea62052 | |||
| 7435e762fb | |||
| f6c551fe87 | |||
| f3a9ee8ed6 | |||
| f538b47070 | |||
| 3709174a86 | |||
| 5dd22cfd85 | |||
| cf5ea40b1a | |||
| 6c51a59c52 | |||
| 1919c89de4 | |||
| 774aa11795 | |||
| 44abd15162 | |||
| 0372a79986 | |||
| 531d7f1a9a | |||
| 6bff89a618 | |||
| 86ac4e5017 | |||
| 84c6b37913 | |||
| 987e631176 | |||
| 1f57187461 | |||
| a7f47d381e | |||
| 1a7825c7ec | |||
| 97fb677887 | |||
| 124f6979a4 | |||
| 39778de04b | |||
| bcaabedc8e | |||
| 46fb888ea3 | |||
| 36c3331696 | |||
| 2850b3a46c | |||
| 2721b7b522 | |||
| b1ccf3f3fd | |||
| 199e07e3d2 | |||
| 74997c05e8 | |||
| 017ef5a57b | |||
| 6b9fa76c01 | |||
| a211fafdc4 | |||
| e4e9402cd9 | |||
| f806967958 | |||
| 6a26d56ad9 | |||
| 5d877f65f5 | |||
| e36bf6d6b1 | |||
| 5febc056a8 | |||
| f234a61fb4 | |||
| 3ac9b83f40 | |||
| 1d4df9c1f4 | |||
| a80b53d113 | |||
| b594b7491f | |||
| e2cb283155 | |||
| ab9b11749c | |||
| 1a3cbca017 | |||
| 76f91750c1 | |||
| 1106e2e332 | |||
| 351be52481 | |||
| b39c56f82d | |||
| 3b05ba2a8f |
+17
-2
@@ -1,13 +1,28 @@
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 10
|
||||
cooldown:
|
||||
default-days: 14
|
||||
groups:
|
||||
actions:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
open-pull-requests-limit: 10
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
cooldown:
|
||||
default-days: 14
|
||||
groups:
|
||||
dependencies:
|
||||
applies-to: version-updates
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
@@ -19,7 +19,7 @@ permissions:
|
||||
jobs:
|
||||
docker-syncthing:
|
||||
name: Build and push Docker images
|
||||
if: github.repository == 'syncthing/syncthing'
|
||||
if: github.repository_owner == 'syncthing'
|
||||
runs-on: ubuntu-latest
|
||||
environment: docker
|
||||
strategy:
|
||||
@@ -40,13 +40,13 @@ jobs:
|
||||
check-latest: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -60,10 +60,10 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
|
||||
- name: Set Docker tags (all branches)
|
||||
run: |
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
echo "TAGS=$tags" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.${{ matrix.pkg }}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Build Syncthing (Nightly)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run nightly build at 05:00 UTC
|
||||
- cron: '00 05 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build-syncthing:
|
||||
uses: ./.github/workflows/build-syncthing.yaml
|
||||
# if we only want nightlies to run for specific users:
|
||||
# if: contains(fromJSON('["syncthing", "calmh"]'), github.repository_owner)
|
||||
secrets: inherit
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
needs:
|
||||
- build-test
|
||||
- package-linux
|
||||
- package-illumos
|
||||
# - package-illumos
|
||||
- package-cross
|
||||
- package-source
|
||||
- package-debian
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
go-version: "~1.25.7" # temporarily stay on Go 1.25 due to linker error on Go 1.26
|
||||
cache: false
|
||||
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
@@ -249,7 +249,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Sign files with Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0.5.1
|
||||
uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
|
||||
with:
|
||||
azure-tenant-id: ${{ secrets.AZURE_TRUSTED_SIGNING_TENANT_ID }}
|
||||
azure-client-id: ${{ secrets.AZURE_TRUSTED_SIGNING_CLIENT_ID }}
|
||||
@@ -284,10 +284,37 @@ jobs:
|
||||
#
|
||||
|
||||
package-linux:
|
||||
name: Package for Linux
|
||||
name: Package for Linux (${{ matrix.group }})
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- facts
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# split the build into three groups with roughtly the same amount of
|
||||
# work, in order to reduce wall clock time
|
||||
matrix:
|
||||
include:
|
||||
- group: common
|
||||
targets: |
|
||||
amd64 zig cc -target x86_64-linux-musl
|
||||
386 zig cc -target x86-linux-musl
|
||||
arm zig cc -target arm-linux-musleabi -mcpu=arm1136j_s
|
||||
arm64 zig cc -target aarch64-linux-musl
|
||||
extra-packages: ""
|
||||
- group: mips
|
||||
targets: |
|
||||
mips zig cc -target mips-linux-musleabi
|
||||
mipsle zig cc -target mipsel-linux-musleabi
|
||||
mips64 mips64-linux-gnuabi64-gcc
|
||||
mips64le mips64el-linux-gnuabi64-gcc
|
||||
extra-packages: "gcc-mips64-linux-gnuabi64 gcc-mips64el-linux-gnuabi64"
|
||||
- group: other
|
||||
targets: |
|
||||
riscv64 zig cc -target riscv64-linux-musl
|
||||
s390x zig cc -target s390x-linux-musl
|
||||
loong64 zig cc -target loongarch64-linux-musl
|
||||
ppc64le zig cc -target powerpc64le-linux-musl
|
||||
extra-packages: ""
|
||||
env:
|
||||
VERSION: ${{ needs.facts.outputs.version }}
|
||||
RELEASE_KIND: ${{ needs.facts.outputs.release-kind }}
|
||||
@@ -299,41 +326,36 @@ jobs:
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
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-linux-${{ matrix.group }}-${{ hashFiles('go.sum') }}
|
||||
|
||||
- name: Install extra packages
|
||||
if: matrix.extra-packages != ''
|
||||
run: sudo apt-get install -y ${{ matrix.extra-packages }}
|
||||
|
||||
- name: Create packages
|
||||
run: |
|
||||
sudo apt-get install -y gcc-mips64-linux-gnuabi64 gcc-mips64el-linux-gnuabi64
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch amd64 -cc "zig cc -target x86_64-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch 386 -cc "zig cc -target x86-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch arm -cc "zig cc -target arm-linux-musleabi -mcpu=arm1136j_s" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch arm64 -cc "zig cc -target aarch64-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mips -cc "zig cc -target mips-linux-musleabi" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mipsle -cc "zig cc -target mipsel-linux-musleabi" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mips64 -cc mips64-linux-gnuabi64-gcc tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch mips64le -cc mips64el-linux-gnuabi64-gcc tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch riscv64 -cc "zig cc -target riscv64-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch s390x -cc "zig cc -target s390x-linux-musl" tar "$tgt"
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch loong64 -cc "zig cc -target loongarch64-linux-musl" tar "$tgt"
|
||||
# go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch ppc64 -cc "zig cc -target powerpc64-linux-musl" tar "$tgt" # fails with linkmode not supported
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch ppc64le -cc "zig cc -target powerpc64le-linux-musl" tar "$tgt"
|
||||
echo "$TARGETS" | while read -r arch cc; do
|
||||
[ -z "$arch" ] && continue
|
||||
for tgt in syncthing stdiscosrv strelaysrv ; do
|
||||
go run build.go -tags "${{env.TAGS_LINUX}}" -goos linux -goarch "$arch" -cc "$cc" tar "$tgt"
|
||||
done
|
||||
done
|
||||
env:
|
||||
CGO_ENABLED: "1"
|
||||
EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"
|
||||
TARGETS: ${{ matrix.targets }}
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packages-linux
|
||||
name: packages-linux-${{ matrix.group }}
|
||||
path: |
|
||||
*.tar.gz
|
||||
compat.json
|
||||
@@ -350,7 +372,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build syncthing in OmniOS VM
|
||||
uses: vmactions/omnios-vm@v1
|
||||
uses: vmactions/omnios-vm@68da93c6d9812b29fc90c5b5141b093f84a590fb # v1
|
||||
with:
|
||||
envs: "VERSION GO_VERSION CGO_ENABLED"
|
||||
usesh: true
|
||||
@@ -623,7 +645,7 @@ jobs:
|
||||
needs:
|
||||
- codesign-windows
|
||||
- package-linux
|
||||
- package-illumos
|
||||
# - package-illumos
|
||||
- package-macos
|
||||
- package-cross
|
||||
- package-source
|
||||
@@ -723,7 +745,7 @@ jobs:
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
|
||||
- uses: ruby/setup-ruby@v1
|
||||
- uses: ruby/setup-ruby@eab2afb99481ca09a4e91171a8e0aee0e89bfedd # v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
|
||||
@@ -731,7 +753,7 @@ jobs:
|
||||
run: |
|
||||
gem install fpm
|
||||
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
@@ -888,14 +910,17 @@ jobs:
|
||||
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
||||
gh release create "$VERSION" \
|
||||
$maybePrerelease \
|
||||
--draft \
|
||||
--title "$VERSION" \
|
||||
--notes-from-tag
|
||||
fi
|
||||
|
||||
gh release upload --clobber "$VERSION" \
|
||||
packages/*.asc packages/*.json \
|
||||
packages/syncthing-*.tar.gz \
|
||||
packages/syncthing-*.zip \
|
||||
packages/syncthing_*.deb
|
||||
gh release edit "$VERSION" --draft=false
|
||||
|
||||
PKGS=$(pwd)/packages
|
||||
cd /tmp # gh will not release for repo x while inside repo y
|
||||
@@ -904,12 +929,14 @@ jobs:
|
||||
if ! gh release view --json name "$VERSION" >/dev/null 2>&1 ; then
|
||||
gh release create "$VERSION" \
|
||||
$maybePrerelease \
|
||||
--draft \
|
||||
--title "$VERSION" \
|
||||
--notes "https://github.com/syncthing/syncthing/releases/tag/$VERSION"
|
||||
fi
|
||||
gh release upload --clobber "$VERSION" \
|
||||
$PKGS/*.asc \
|
||||
$PKGS/*${repo}*
|
||||
gh release edit "$VERSION" --draft=false
|
||||
done
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
|
||||
@@ -1016,7 +1043,7 @@ jobs:
|
||||
go-version: ${{ needs.facts.outputs.go-version }}
|
||||
cache: false
|
||||
|
||||
- uses: mlugg/setup-zig@v2
|
||||
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
@@ -1046,23 +1073,23 @@ jobs:
|
||||
EXTRA_LDFLAGS: "-linkmode=external -extldflags=-static"
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
|
||||
- name: Set version tags
|
||||
run: |
|
||||
version=${VERSION#v}
|
||||
repo=ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}
|
||||
ref="${{github.ref_name}}"
|
||||
ref="${REF_NAME}"
|
||||
ref=${ref//\//-} # slashes to dashes
|
||||
|
||||
# List of tags for ghcr.io
|
||||
@@ -1080,6 +1107,8 @@ jobs:
|
||||
|
||||
echo Pushing to $tags
|
||||
echo "DOCKER_TAGS=$tags" >> $GITHUB_ENV
|
||||
env:
|
||||
REF_NAME: ${{github.ref_name}}
|
||||
|
||||
- name: Prepare context dir
|
||||
run: |
|
||||
@@ -1087,7 +1116,7 @@ jobs:
|
||||
mv bin/* script ctx
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
with:
|
||||
context: ctx
|
||||
file: ${{ matrix.dockerfile }}
|
||||
@@ -1163,7 +1192,7 @@ jobs:
|
||||
run: go run build.go assets
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
|
||||
with:
|
||||
only-new-issues: true
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: yesolutions/mirror-action@master
|
||||
- uses: yesolutions/mirror-action@662fce0eced8996f64d7fa264d76cddd84827f33 # master
|
||||
with:
|
||||
REMOTE: ssh://git@codeberg.org/${{ github.repository }}.git
|
||||
GIT_SSH_PRIVATE_KEY: ${{ secrets.CODEBERG_PUSH_KEY }}
|
||||
|
||||
@@ -8,8 +8,9 @@ on:
|
||||
jobs:
|
||||
|
||||
run-recommendation:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check for a recommendation
|
||||
if: github.repository_owner == 'syncthing'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: docker://ghcr.io/calmh/github-org-members:latest
|
||||
|
||||
@@ -20,8 +20,9 @@ jobs:
|
||||
|
||||
labels:
|
||||
name: Set labels
|
||||
if: github.repository_owner == 'syncthing'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: srvaroa/labeler@v1
|
||||
- uses: srvaroa/labeler@9c29ad1ef33d169f9ef33c52722faf47a566bcf3 # v1
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -12,6 +12,7 @@ permissions:
|
||||
jobs:
|
||||
create-release-tag:
|
||||
name: Create release tag
|
||||
if: github.repository_owner == 'syncthing'
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
steps:
|
||||
@@ -53,7 +54,7 @@ jobs:
|
||||
git push origin "$NEXT"
|
||||
|
||||
- name: Trigger the build
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
|
||||
with:
|
||||
workflow: build-syncthing.yaml
|
||||
ref: refs/tags/${{ env.NEXT }}
|
||||
|
||||
@@ -8,9 +8,9 @@ on:
|
||||
jobs:
|
||||
|
||||
trigger-nightly:
|
||||
name: Push to release-nightly to trigger build
|
||||
if: github.repository_owner == 'syncthing'
|
||||
runs-on: ubuntu-latest
|
||||
name: Push to release-nightly to trigger build
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
@@ -76,6 +76,9 @@ linters:
|
||||
# Don't necessarily rewrite !(foo || bar) to !foo && !bar
|
||||
- linters: [staticcheck]
|
||||
text: QF1001
|
||||
# Don't necessarily pass the context just for the sake of the logger
|
||||
- linters: [contextcheck]
|
||||
text: "->log`"
|
||||
settings:
|
||||
sloglint:
|
||||
context: "scope"
|
||||
|
||||
@@ -53,6 +53,7 @@ approval_rules:
|
||||
options:
|
||||
ignore_update_merges: true
|
||||
allow_non_author_contributor: true
|
||||
invalidate_on_push: true
|
||||
|
||||
# Regular pull requests require approval by an active contributor
|
||||
- name: is approved by a syncthing contributor
|
||||
@@ -63,6 +64,7 @@ approval_rules:
|
||||
options:
|
||||
ignore_update_merges: true
|
||||
allow_non_author_contributor: true
|
||||
invalidate_on_push: true
|
||||
|
||||
# Changes to some files (translations, dependencies, compatibility) do not
|
||||
# require approval if they were proposed by a contributor and have a
|
||||
|
||||
@@ -68,6 +68,7 @@ Aurélien Rainone <476650+arl@users.noreply.github.com>
|
||||
BAHADIR YILMAZ <bahadiryilmaz32@gmail.com>
|
||||
Bart De Vries (mogwa1) <devriesb@gmail.com>
|
||||
Beat Reichenbach <44111292+beatreichenbach@users.noreply.github.com>
|
||||
Ben Norcombe <bennorcombe@pm.me>
|
||||
Ben Shepherd (benshep) <bjashepherd@gmail.com>
|
||||
Ben Sidhom (bsidhom) <bsidhom@gmail.com>
|
||||
Benedikt Heine (bebehei) <bebe@bebehei.de>
|
||||
@@ -90,6 +91,7 @@ chucic <chucic@seznam.cz>
|
||||
cjc7373 <niuchangcun@gmail.com>
|
||||
Colin Kennedy (moshen) <moshen.colin@gmail.com>
|
||||
Cromefire_ <tim.l@nghorst.net> <26320625+cromefire@users.noreply.github.com>
|
||||
cui <cuiweixie@gmail.com>
|
||||
Cyprien Devillez <cypx@users.noreply.github.com>
|
||||
d-volution <49024624+d-volution@users.noreply.github.com>
|
||||
Dan <benda.daniel@gmail.com>
|
||||
@@ -172,6 +174,7 @@ John Rinehart (fuzzybear3965) <johnrichardrinehart@gmail.com>
|
||||
Jonas Thelemann <e-mail@jonas-thelemann.de>
|
||||
Jonathan <artback@protonmail.com> <jonagn@gmail.com>
|
||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||
JRNitre <nichinichisou67@outlook.com>
|
||||
jtagcat <git-514635f7@jtag.cat> <git-12dbd862@jtag.cat>
|
||||
Julian Lehrhuber <jul13579@users.noreply.github.com>
|
||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||
@@ -194,6 +197,7 @@ Leo Arias (elopio) <yo@elopio.net>
|
||||
Liu Siyuan (liusy182) <liusy182@gmail.com> <liusy182@hotmail.com>
|
||||
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
|
||||
LSmithx2 <42276854+lsmithx2@users.noreply.github.com>
|
||||
Luiz Angelo Daros de Luca <luizluca@gmail.com>
|
||||
Lukas Lihotzki <lukas@lihotzki.de>
|
||||
Luke Hamburg <1992842+luckman212@users.noreply.github.com>
|
||||
luzpaz <luzpaz@users.noreply.github.com>
|
||||
@@ -265,6 +269,7 @@ Prathik P Kulkarni <83969842+prathik8794@users.noreply.github.com>
|
||||
pullmerge <166967364+pullmerge@users.noreply.github.com>
|
||||
Quentin Hibon <qh.public@yahoo.com>
|
||||
Rahmi Pruitt <rjpruitt16@gmail.com>
|
||||
RealCharlesChia <161665317+RealCharlesChia@users.noreply.github.com>
|
||||
red_led <red-led@users.noreply.github.com>
|
||||
Robert Carosi (nov1n) <robert@carosi.nl>
|
||||
Roberto Santalla <roobre@users.noreply.github.com>
|
||||
@@ -307,6 +312,7 @@ Tom Jakubowski <tom@crystae.net>
|
||||
Tully Robinson (tojrobinson) <tully@tojr.org>
|
||||
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
|
||||
Tyler Kropp <kropptyler@gmail.com>
|
||||
Umer-Azaz <umer_azaz@yahoo.com>
|
||||
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
||||
Val Markovic <val@markovic.io>
|
||||
vapatel2 <149737089+vapatel2@users.noreply.github.com>
|
||||
@@ -316,6 +322,7 @@ Vik <63919734+ViktorOn@users.noreply.github.com>
|
||||
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
|
||||
villekalliomaki <53118179+villekalliomaki@users.noreply.github.com>
|
||||
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
||||
vvaswani <2571660+vvaswani@users.noreply.github.com>
|
||||
wangguoliang <liangcszzu@163.com>
|
||||
WangXi <xib1102@icloud.com>
|
||||
Will Rouesnel <wrouesnel@wrouesnel.com>
|
||||
|
||||
@@ -101,6 +101,8 @@ func main() {
|
||||
}
|
||||
|
||||
log.SetOutput(os.Stdout)
|
||||
log.Println(build.LongVersionFor("stcrashreceiver"))
|
||||
|
||||
if err := http.ListenAndServe(params.Listen, mux); err != nil {
|
||||
log.Fatalln("HTTP serve:", err)
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
|
||||
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/cmd/infra/strelaypoolsrv/auto"
|
||||
"github.com/syncthing/syncthing/lib/assets"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/geoip"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
@@ -146,6 +147,8 @@ func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
log.Println(build.LongVersionFor("strelaypoolsrv"))
|
||||
|
||||
requests = make(chan request, requestQueueLen)
|
||||
geoip, err := geoip.NewGeoLite2CityProvider(context.Background(), geoipAccountID, geoipLicenseKey, os.TempDir())
|
||||
if err != nil {
|
||||
@@ -425,7 +428,7 @@ func handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
newRelay.URL = uri.String()
|
||||
} else if host != rhost && relayCert == nil {
|
||||
if debug {
|
||||
log.Println("IP address advertised does not match client IP address", r.RemoteAddr, uri)
|
||||
log.Println("IP address advertised does not match client IP address", rhost, uri)
|
||||
}
|
||||
http.Error(w, fmt.Sprintf("IP advertised %s does not match client IP %s", host, rhost), http.StatusUnauthorized)
|
||||
return
|
||||
@@ -449,13 +452,13 @@ func handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
case requests <- request{&newRelay, reschan, prometheus.NewTimer(relayTestActionsSeconds.WithLabelValues("queue"))}:
|
||||
result := <-reschan
|
||||
if result.err != nil {
|
||||
log.Println("Join from", r.RemoteAddr, "failed:", result.err)
|
||||
log.Println("Join from", rhost, "failed:", result.err)
|
||||
globalBlocklist.AddError(rhost)
|
||||
relayTestsTotal.WithLabelValues("failed").Inc()
|
||||
http.Error(w, result.err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Println("Join from", r.RemoteAddr, "succeeded")
|
||||
log.Println("Join from", rhost, "succeeded")
|
||||
globalBlocklist.ClearErrors(rhost)
|
||||
relayTestsTotal.WithLabelValues("success").Inc()
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/syncthing/syncthing/internal/slogutil"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/httpcache"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
)
|
||||
@@ -44,6 +45,8 @@ func main() {
|
||||
Level: slog.LevelInfo,
|
||||
})))
|
||||
|
||||
slog.Info(build.LongVersionFor("stupgrades"))
|
||||
|
||||
if err := server(¶ms); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/syncthing/syncthing/cmd/infra/ursrv/serve"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
@@ -24,6 +25,8 @@ func main() {
|
||||
Level: slog.LevelInfo,
|
||||
})))
|
||||
|
||||
slog.Info(build.LongVersionFor("ursrv"))
|
||||
|
||||
var cli CLI
|
||||
ctx := kong.Parse(&cli)
|
||||
if err := ctx.Run(); err != nil {
|
||||
|
||||
@@ -67,7 +67,7 @@ type contextKey int
|
||||
|
||||
const idKey contextKey = iota
|
||||
|
||||
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool, desiredNotFoundRate float64) *apiSrv {
|
||||
func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator, useHTTP, compression bool, desiredUnseenNotFoundRate, desiredSeenNotFoundRate float64) *apiSrv {
|
||||
return &apiSrv{
|
||||
addr: addr,
|
||||
cert: cert,
|
||||
@@ -78,13 +78,13 @@ func newAPISrv(addr string, cert tls.Certificate, db database, repl replicator,
|
||||
seenTracker: &retryAfterTracker{
|
||||
name: "seenTracker",
|
||||
bucketStarts: time.Now(),
|
||||
desiredRate: desiredNotFoundRate / 2,
|
||||
desiredRate: desiredSeenNotFoundRate,
|
||||
currentDelay: notFoundRetryUnknownMinSeconds,
|
||||
},
|
||||
notSeenTracker: &retryAfterTracker{
|
||||
name: "notSeenTracker",
|
||||
bucketStarts: time.Now(),
|
||||
desiredRate: desiredNotFoundRate / 2,
|
||||
desiredRate: desiredUnseenNotFoundRate,
|
||||
currentDelay: notFoundRetryUnknownMaxSeconds / 2,
|
||||
},
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func (s *apiSrv) handleGET(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *apiSrv) handlePOST(remoteAddr *net.TCPAddr, w http.ResponseWriter, req *http.Request) {
|
||||
reqID := req.Context().Value(idKey).(requestID)
|
||||
|
||||
rawCert, err := certificateBytes(req)
|
||||
rawCert, err := s.certificateBytes(req)
|
||||
if err != nil {
|
||||
slog.Debug("Request without certificates", "id", reqID, "error", err)
|
||||
announceRequestsTotal.WithLabelValues("no_certificate").Inc()
|
||||
@@ -330,10 +330,13 @@ func handlePing(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func certificateBytes(req *http.Request) ([]byte, error) {
|
||||
func (s *apiSrv) certificateBytes(req *http.Request) ([]byte, error) {
|
||||
if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
|
||||
return req.TLS.PeerCertificates[0].Raw, nil
|
||||
}
|
||||
if !s.useHTTP {
|
||||
return nil, errors.New("no certificate presented")
|
||||
}
|
||||
|
||||
var bs []byte
|
||||
|
||||
@@ -564,5 +567,17 @@ func (t *retryAfterTracker) retryAfterS() int {
|
||||
}
|
||||
t.curCount++
|
||||
t.mut.Unlock()
|
||||
return t.currentDelay + rand.Intn(t.currentDelay/4)
|
||||
|
||||
// Skewed normal distribution with the mean at currentDelay and the
|
||||
// limits (50% and 150%) at 3 standard deviations
|
||||
nf := rand.NormFloat64()
|
||||
minD := max(notFoundRetryUnknownMinSeconds, t.currentDelay/2)
|
||||
maxD := min(notFoundRetryUnknownMaxSeconds, t.currentDelay*3/2)
|
||||
intv := float64(maxD - t.currentDelay)
|
||||
if nf < 0 {
|
||||
intv = float64(t.currentDelay - minD)
|
||||
}
|
||||
nf = min(max(nf*intv/3+float64(t.currentDelay), notFoundRetryUnknownMinSeconds), notFoundRetryUnknownMaxSeconds)
|
||||
|
||||
return int(nf)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
@@ -106,12 +107,59 @@ func addr(host string, port int) *net.TCPAddr {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryAfterSHistogram(t *testing.T) {
|
||||
tracker := &retryAfterTracker{
|
||||
name: "test",
|
||||
bucketStarts: time.Now(),
|
||||
desiredRate: 100,
|
||||
currentDelay: 1800,
|
||||
}
|
||||
|
||||
const n = 1000
|
||||
bucketSize := 60 // seconds per histogram bucket
|
||||
numBuckets := (notFoundRetryUnknownMaxSeconds + bucketSize - 1) / bucketSize
|
||||
buckets := make([]int, numBuckets)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
v := tracker.retryAfterS()
|
||||
if v < notFoundRetryUnknownMinSeconds || v > notFoundRetryUnknownMaxSeconds {
|
||||
t.Fatalf("retryAfterS() = %d, out of range [%d, %d]", v, notFoundRetryUnknownMinSeconds, notFoundRetryUnknownMaxSeconds)
|
||||
}
|
||||
b := (v - 1) / bucketSize
|
||||
if b >= numBuckets {
|
||||
b = numBuckets - 1
|
||||
}
|
||||
buckets[b]++
|
||||
}
|
||||
|
||||
// Print a horizontal histogram
|
||||
maxCount := 0
|
||||
for _, c := range buckets {
|
||||
if c > maxCount {
|
||||
maxCount = c
|
||||
}
|
||||
}
|
||||
barWidth := 60
|
||||
for i, c := range buckets {
|
||||
lo := i*bucketSize + 1
|
||||
hi := (i + 1) * bucketSize
|
||||
if hi > notFoundRetryUnknownMaxSeconds {
|
||||
hi = notFoundRetryUnknownMaxSeconds
|
||||
}
|
||||
bar := ""
|
||||
if maxCount > 0 {
|
||||
bar = strings.Repeat("#", c*barWidth/maxCount)
|
||||
}
|
||||
t.Logf("%4d-%4ds | %-*s %d", lo, hi, barWidth, bar, c)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAPIRequests(b *testing.B) {
|
||||
db := newInMemoryStore(b.TempDir(), 0, nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go db.Serve(ctx)
|
||||
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000)
|
||||
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000, 1000)
|
||||
srv := httptest.NewServer(http.HandlerFunc(api.handler))
|
||||
|
||||
kf := b.TempDir() + "/cert"
|
||||
|
||||
@@ -306,7 +306,7 @@ func (s *inMemoryStore) write() (err error) {
|
||||
}
|
||||
|
||||
if err := bw.Flush(); err != nil {
|
||||
_ = fd.Close
|
||||
_ = fd.Close()
|
||||
return err
|
||||
}
|
||||
if err := fd.Close(); err != nil {
|
||||
|
||||
+15
-12
@@ -14,7 +14,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
@@ -58,13 +58,16 @@ const (
|
||||
var debug = false
|
||||
|
||||
type CLI struct {
|
||||
Cert string `group:"Listen" help:"Certificate file" default:"./cert.pem" env:"DISCOVERY_CERT_FILE"`
|
||||
Key string `group:"Listen" help:"Key file" default:"./key.pem" env:"DISCOVERY_KEY_FILE"`
|
||||
HTTP bool `group:"Listen" help:"Listen on HTTP (behind an HTTPS proxy)" env:"DISCOVERY_HTTP"`
|
||||
Compression bool `group:"Listen" help:"Enable GZIP compression of responses" env:"DISCOVERY_COMPRESSION"`
|
||||
Listen string `group:"Listen" help:"Listen address" default:":8443" env:"DISCOVERY_LISTEN"`
|
||||
MetricsListen string `group:"Listen" help:"Metrics listen address" env:"DISCOVERY_METRICS_LISTEN"`
|
||||
DesiredNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies (/s)" default:"1000"`
|
||||
Cert string `group:"Listen" help:"Certificate file" default:"./cert.pem" env:"DISCOVERY_CERT_FILE"`
|
||||
Key string `group:"Listen" help:"Key file" default:"./key.pem" env:"DISCOVERY_KEY_FILE"`
|
||||
HTTP bool `group:"Listen" help:"Listen on HTTP (behind an HTTPS proxy)" env:"DISCOVERY_HTTP"`
|
||||
Compression bool `group:"Listen" help:"Enable GZIP compression of responses" env:"DISCOVERY_COMPRESSION"`
|
||||
Listen string `group:"Listen" help:"Listen address" default:":8443" env:"DISCOVERY_LISTEN"`
|
||||
MetricsListen string `group:"Listen" help:"Metrics listen address" env:"DISCOVERY_METRICS_LISTEN"`
|
||||
DesiredUnseenNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies for never seen devices (/s)" default:"1000" env:"DISCOVERY_UNSEEN_RATE"`
|
||||
DesiredSeenNotFoundRate float64 `group:"Listen" help:"Desired maximum rate of not-found replies for previously seen devices (/s)" default:"1000" env:"DISCOVERY_SEEN_RATE"`
|
||||
|
||||
ShutdownDelay float64 `help:"Time to wait before shutdown after receiving a shutdown signal (s)" env:"DISCOVERY_SHUTDOWN_DELAY"`
|
||||
|
||||
DBDir string `group:"Database" help:"Database directory" default:"." env:"DISCOVERY_DB_DIR"`
|
||||
DBFlushInterval time.Duration `group:"Database" help:"Interval between database flushes" default:"5m" env:"DISCOVERY_DB_FLUSH_INTERVAL"`
|
||||
@@ -96,8 +99,6 @@ func main() {
|
||||
}
|
||||
slog.Info(build.LongVersionFor("stdiscosrv"))
|
||||
|
||||
buildInfo.WithLabelValues(build.Version, runtime.Version(), build.User, build.Date.UTC().Format("2006-01-02T15:04:05Z")).Set(1)
|
||||
|
||||
var cert tls.Certificate
|
||||
if !cli.HTTP {
|
||||
var err error
|
||||
@@ -148,7 +149,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Start the main API server.
|
||||
qs := newAPISrv(cli.Listen, cert, db, repl, cli.HTTP, cli.Compression, cli.DesiredNotFoundRate)
|
||||
qs := newAPISrv(cli.Listen, cert, db, repl, cli.HTTP, cli.Compression, cli.DesiredUnseenNotFoundRate, cli.DesiredSeenNotFoundRate)
|
||||
main.Add(qs)
|
||||
|
||||
// If we have a metrics port configured, start a metrics handler.
|
||||
@@ -168,9 +169,11 @@ func main() {
|
||||
// Cancel on signal
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
signal.Notify(signalChan, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-signalChan
|
||||
slog.Info("Received signal; shutting down", "signal", sig)
|
||||
slog.Info("Received signal; shutting down", "signal", sig, "delay", cli.ShutdownDelay)
|
||||
time.Sleep(time.Duration(float64(time.Second) * cli.ShutdownDelay))
|
||||
cancel()
|
||||
}()
|
||||
|
||||
|
||||
@@ -11,14 +11,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
buildInfo = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "discovery",
|
||||
Name: "build_info",
|
||||
Help: "A metric with a constant '1' value labeled by version, goversion, builduser and builddate from which stdiscosrv was built.",
|
||||
}, []string{"version", "goversion", "builduser", "builddate"})
|
||||
|
||||
apiRequestsTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "syncthing",
|
||||
@@ -132,7 +124,7 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(buildInfo,
|
||||
prometheus.MustRegister(
|
||||
apiRequestsTotal, apiRequestsSeconds,
|
||||
lookupRequestsTotal, announceRequestsTotal,
|
||||
replicationSendsTotal, replicationRecvsTotal,
|
||||
|
||||
+14
-7
@@ -71,9 +71,13 @@ var (
|
||||
|
||||
// httpClient is the HTTP client we use for outbound requests. It has a
|
||||
// timeout and may get further options set during initialization.
|
||||
var httpClient = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
var (
|
||||
httpTransport = &http.Transport{}
|
||||
httpClient = &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: httpTransport,
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
||||
@@ -132,9 +136,7 @@ func main() {
|
||||
// also come from that address.
|
||||
laddr.Port = 0
|
||||
boundDialer := &net.Dialer{LocalAddr: laddr}
|
||||
httpClient.Transport = &http.Transport{
|
||||
DialContext: boundDialer.DialContext,
|
||||
}
|
||||
httpTransport.DialContext = boundDialer.DialContext
|
||||
}
|
||||
|
||||
log.Println(longVer)
|
||||
@@ -163,6 +165,11 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Outgoing HTTPS requests may use our certificate for authentication
|
||||
httpTransport.TLSClientConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
tlsCfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{protocol.ProtocolName},
|
||||
@@ -277,7 +284,7 @@ func main() {
|
||||
for _, pool := range pools {
|
||||
pool = strings.TrimSpace(pool)
|
||||
if len(pool) > 0 {
|
||||
go poolHandler(pool, uri, mapping, cert)
|
||||
go poolHandler(pool, uri, mapping)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-20
@@ -4,7 +4,6 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
@@ -17,7 +16,7 @@ const (
|
||||
httpStatusEnhanceYourCalm = 429
|
||||
)
|
||||
|
||||
func poolHandler(pool string, uri *url.URL, mapping mapping, ownCert tls.Certificate) {
|
||||
func poolHandler(pool string, uri *url.URL, mapping mapping) {
|
||||
if debug {
|
||||
log.Println("Joining", pool)
|
||||
}
|
||||
@@ -32,24 +31,7 @@ func poolHandler(pool string, uri *url.URL, mapping mapping, ownCert tls.Certifi
|
||||
uriCopy.String(),
|
||||
})
|
||||
|
||||
poolUrl, err := url.Parse(pool)
|
||||
if err != nil {
|
||||
log.Printf("Could not parse pool url '%s': %v", pool, err)
|
||||
}
|
||||
|
||||
client := http.DefaultClient
|
||||
if poolUrl.Scheme == "https" {
|
||||
// Sent our certificate in join request
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{ownCert},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.Post(pool, "application/json", &b)
|
||||
resp, err := httpClient.Post(pool, "application/json", &b) //nolint:noctx
|
||||
if err != nil {
|
||||
log.Printf("Error joining pool %v: HTTP request: %v", pool, err)
|
||||
time.Sleep(time.Minute)
|
||||
|
||||
@@ -1054,7 +1054,7 @@ func (m migratingAPI) Serve(ctx context.Context) error {
|
||||
srv := &http.Server{
|
||||
Addr: m.addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Write([]byte("*** Database migration in progress ***\n\n"))
|
||||
for _, line := range slogutil.GlobalRecorder.Since(time.Time{}) {
|
||||
_, _ = line.WriteTo(w, slogutil.DefaultLineFormat)
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/AudriusButkevicius/recli v0.0.7
|
||||
github.com/alecthomas/kong v1.14.0
|
||||
github.com/alecthomas/kong v1.15.0
|
||||
github.com/aws/aws-sdk-go v1.55.8
|
||||
github.com/calmh/incontainer v1.0.0
|
||||
github.com/calmh/xdr v1.2.0
|
||||
@@ -12,7 +12,7 @@ require (
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/getsentry/raven-go v0.2.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/go-ldap/ldap/v3 v3.4.13
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gofrs/flock v0.13.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
@@ -22,17 +22,17 @@ require (
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/maruel/panicparse/v2 v2.5.0
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
github.com/mattn/go-sqlite3 v1.14.44
|
||||
github.com/maxmind/geoipupdate/v6 v6.1.0
|
||||
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
|
||||
github.com/oschwald/geoip2-golang v1.13.0
|
||||
github.com/pierrec/lz4/v4 v4.1.25
|
||||
github.com/pierrec/lz4/v4 v4.1.26
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/quic-go/quic-go v0.59.0
|
||||
github.com/rabbitmq/amqp091-go v1.10.0
|
||||
github.com/rabbitmq/amqp091-go v1.11.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
|
||||
github.com/shirou/gopsutil/v4 v4.26.2
|
||||
github.com/shirou/gopsutil/v4 v4.26.4
|
||||
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
||||
github.com/thejerf/suture/v4 v4.0.6
|
||||
@@ -40,19 +40,19 @@ require (
|
||||
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
|
||||
github.com/willabides/kongplete v0.4.0
|
||||
github.com/wlynxg/anet v0.0.5
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/text v0.34.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a
|
||||
golang.org/x/net v0.54.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/text v0.37.0
|
||||
golang.org/x/time v0.15.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
modernc.org/sqlite v1.46.1
|
||||
modernc.org/sqlite v1.50.0
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
||||
@@ -94,12 +94,12 @@ require (
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/libc v1.72.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
@@ -107,8 +107,11 @@ require (
|
||||
// https://github.com/gobwas/glob/pull/55
|
||||
replace github.com/gobwas/glob v0.2.3 => github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b
|
||||
|
||||
// https://github.com/jackpal/gateway/pull/49
|
||||
replace github.com/jackpal/gateway v1.1.1 => github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4
|
||||
|
||||
// https://github.com/mattn/go-sqlite3/pull/1338
|
||||
replace github.com/mattn/go-sqlite3 v1.14.34 => github.com/calmh/go-sqlite3 v1.14.35-0.20260303222015-6a03636c4bc9
|
||||
replace github.com/mattn/go-sqlite3 v1.14.44 => github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d
|
||||
|
||||
tool (
|
||||
github.com/calmh/xdr/cmd/genxdr
|
||||
|
||||
@@ -2,13 +2,13 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AudriusButkevicius/recli v0.0.7 h1:9zjbYlTupi+W5SJXm2cR2sV2mJAIg1sIfDcsW7hrkPM=
|
||||
github.com/AudriusButkevicius/recli v0.0.7/go.mod h1:Nhfib1j/VFnLrXL9cHgA+/n2O6P5THuWelOnbfPNd78=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/kong v1.14.0 h1:gFgEUZWu2ZmZ+UhyZ1bDhuutbKN1nTtJTwh19Wsn21s=
|
||||
github.com/alecthomas/kong v1.14.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
||||
github.com/alecthomas/kong v1.15.0 h1:BVJstKbpO73zKpmIu+m/aLRrNmWwxXPIGTNin9VmLVI=
|
||||
github.com/alecthomas/kong v1.15.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
|
||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||
@@ -19,8 +19,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b h1:Fjm4GuJ+TGMgqfGHN42IQArJb77CfD/mAwLbDUoJe6g=
|
||||
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b/go.mod h1:91K7jfEsgJSyfSrX+gmrRfZMtntx6JsHolWubGXDopg=
|
||||
github.com/calmh/go-sqlite3 v1.14.35-0.20260303222015-6a03636c4bc9 h1:GBPtnVJ3i+/zX4+2bdPQ2zi176yykw+yqRvpyVULLoo=
|
||||
github.com/calmh/go-sqlite3 v1.14.35-0.20260303222015-6a03636c4bc9/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d h1:liVtRMlDBqDyR4qKkMRIFTdUK6+aoG6Oh97DKnhrCHc=
|
||||
github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/calmh/incontainer v1.0.0 h1:g2cTUtZuFGmMGX8GoykPkN1Judj2uw8/3/aEtq4Z/rg=
|
||||
github.com/calmh/incontainer v1.0.0/go.mod h1:eOhqnw15c9X+4RNBe0W3HlUZFfX16O0EDsCOInTndHY=
|
||||
github.com/calmh/xdr v1.2.0 h1:GaGSNH4ZDw9kNdYqle6+RcAENiaQ8/611Ok+jQbBEeU=
|
||||
@@ -59,8 +59,8 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
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.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
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=
|
||||
@@ -106,8 +106,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jackpal/gateway v1.1.1 h1:UXXXkJGIHFsStms9ZBgGpoaFEJP7oJtFn5vplIT68E8=
|
||||
github.com/jackpal/gateway v1.1.1/go.mod h1:Tl1vZVtUaXx5j6P5HFmv45alhEi4yHHLfT4PRbB7eyw=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
@@ -144,6 +142,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4 h1:8TAc8a26Pp0+8I6rj43BqhMi84D1zvtY0sPzO3IKnBw=
|
||||
github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4/go.mod h1:Tl1vZVtUaXx5j6P5HFmv45alhEi4yHHLfT4PRbB7eyw=
|
||||
github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00=
|
||||
github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPbLDsSI873hweQ=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -179,8 +179,8 @@ github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNs
|
||||
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=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -202,8 +202,8 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
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/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8=
|
||||
github.com/rabbitmq/amqp091-go v1.11.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
@@ -216,8 +216,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
|
||||
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY=
|
||||
github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -265,13 +265,13 @@ 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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
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=
|
||||
@@ -280,13 +280,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.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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=
|
||||
@@ -309,25 +309,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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0=
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE=
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE=
|
||||
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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
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.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
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=
|
||||
@@ -358,20 +358,20 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
|
||||
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
|
||||
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
|
||||
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
|
||||
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -380,8 +380,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "أنشئ وشارك المجلدات الموجودة في المسار الافتراضي تلقائيا.",
|
||||
"Available debug logging facilities:": "خدمات سجلات تدقيق البرمجيات المتوفرة:",
|
||||
"Be careful!": "احذر!",
|
||||
"Block Indexing": "فهرسة الكُتل",
|
||||
"Body:": "جسم:",
|
||||
"Bugs": "أخطاء برمجية",
|
||||
"Cancel": "إلغاء",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "جهاز",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "الجهاز \"{{name}}\" ({{device}} في {{address}}) يرغب في الاتصال، إضافة جهاز جديد؟",
|
||||
"Device Certificate": "شهادة الجهاز",
|
||||
"Device Group": "مجموعة الجهاز",
|
||||
"Device ID": "معرف الجهاز",
|
||||
"Device Identification": "معرف الجهاز",
|
||||
"Device Name": "اسم الجهاز",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "فلترة بالتاريخ",
|
||||
"Filter by name": "فلتر باستخدام الاسم",
|
||||
"Folder": "مجلد",
|
||||
"Folder Group": "مجموعة المجلد",
|
||||
"Folder ID": "مُعرِّف المجلد",
|
||||
"Folder Label": "تسمية المجلد",
|
||||
"Folder Path": "مسار المجلد",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "تتبع السجل متوقف، مَرِّر للأسفل للاستئناف.",
|
||||
"Login failed, see Syncthing logs for details.": "فشل تسجيل الدخول، اطَّلِع على سِجِلِّ Syncthing للتفاصيل.",
|
||||
"Logs": "سجلات",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "الاحتفاظ بفهرس لجميع الكُتل الموجودة في المجلد، مما يتيح إعادة استخدام الكتل من ملفات أخرى عند مزامنة التغييرات. عطّل هذا الخيار لتقليل حجم قاعدة البيانات، ولكن على حساب عدم القدرة على إعادة استخدام الكتل عبر الملفات المختلفة.",
|
||||
"Major Upgrade": "ترقية أساسية",
|
||||
"Mass actions": "التأثيرات العامة",
|
||||
"Maximum Age": "أقصى مدة",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "اطفئ",
|
||||
"Oldest First": "الأقدم أولا",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "تسمية وصفية اختيارية للمجلد. يمكن أن تكون مختلفة على كل جهاز.",
|
||||
"Optional group for the device. Can be different on each device.": "مجموعة اختيارية للجهاز. يمكن أن تختلف من جهاز لآخر.",
|
||||
"Optional group for the folder. Can be different on each device.": "مجموعة اختيارية للمجلد. يمكن أن تختلف من جهاز لآخر.",
|
||||
"Options": "خيارات",
|
||||
"Out of Sync": "خارج التزامن",
|
||||
"Out of Sync Items": "عناصر خارج التزامن",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "مترنِّح",
|
||||
"Staggered File Versioning": "تقسمات إصدارات الملف مهترئة",
|
||||
"Start Browser": "تشغيل المتصفح",
|
||||
"Starting": "يبدأ",
|
||||
"Statistics": "إحصائيات",
|
||||
"Stay logged in": "ابقِ مُسجل الدخول",
|
||||
"Stopped": "متوقف",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Автоматично създава в подразбираната папка или споделя папките, които устройството предлага.",
|
||||
"Available debug logging facilities:": "Достъпни улеснения при отстраняване на дефекти:",
|
||||
"Be careful!": "Внимание!",
|
||||
"Block Indexing": "Индексиране на блокове",
|
||||
"Body:": "Съдържание:",
|
||||
"Bugs": "Дефекти",
|
||||
"Cancel": "Отказ",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Устройство",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) с адрес {{address}} желае да се свърже. Да бъде ли добавено?",
|
||||
"Device Certificate": "Сертификат на устройството",
|
||||
"Device Group": "Група устройства",
|
||||
"Device ID": "Идентификатор на устройство",
|
||||
"Device Identification": "Идентификатор на устройство",
|
||||
"Device Name": "Име на устройството",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Филтриране по дата",
|
||||
"Filter by name": "Филтриране по име",
|
||||
"Folder": "Папка",
|
||||
"Folder Group": "Група папки",
|
||||
"Folder ID": "Идентификатор на папката",
|
||||
"Folder Label": "Име на папката",
|
||||
"Folder Path": "Път до папката",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Добавяне на редове към дневника е спряно. Плъзнете най-долу за да продължи.",
|
||||
"Login failed, see Syncthing logs for details.": "Грешка при вход, за подробности проверете в дневника на Syncthing.",
|
||||
"Logs": "Дневници",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Поддържане на индекс на всички блокове в папката, което позволява повторното им използване от други файлове при синхронизиране на промените. Изключете, за да намалите размера на базата данни за сметка на невъзможността да използвате блокове между различните файлове.",
|
||||
"Major Upgrade": "Обновяване на значимо издание",
|
||||
"Mass actions": "Мащабни действия",
|
||||
"Maximum Age": "Максимална възраст",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Изключено",
|
||||
"Oldest First": "Първо най-старите",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Незадължително име на папката. Може да бъде различно на всяко устройство.",
|
||||
"Optional group for the device. Can be different on each device.": "Незадължителна група за устройството. Може да е различна за всяко устройство.",
|
||||
"Optional group for the folder. Can be different on each device.": "Незадължителна група за папката. Може да е различна на всяко устройство.",
|
||||
"Options": "Настройки",
|
||||
"Out of Sync": "Несинхронизирано",
|
||||
"Out of Sync Items": "Несинхронизирани елементи",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Разпределени",
|
||||
"Staggered File Versioning": "Разпределени версии",
|
||||
"Start Browser": "Отваряне на мрежов четец",
|
||||
"Starting": "Стартиране",
|
||||
"Statistics": "Статистика",
|
||||
"Stay logged in": "Оставане в системата",
|
||||
"Stopped": "Спряна",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"Connection Type": "Tipus de connexió",
|
||||
"Connections": "Connexions",
|
||||
"Connections via relays might be rate limited by the relay": "Les connexions mitjançant relés poden estar limitades pel relé",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Ara està disponible la revisió continua de canvix dins de Syncthing. Acò detectarà els canvis i llençarà un escaneig sols a les rutes modificades. Els beneficis són que els canvis es propaguen mé ràpidamente i es necessiten menys escanejos complets.",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Ara està disponible la revisió continua de canvis dins de Syncthing. Això detectarà els canvis i llançarà un escaneig sols a les rutes modificades. Els beneficis són que els canvis es propaguen més ràpidament i es necessiten menys escanejos complets.",
|
||||
"Copied from elsewhere": "Copiat de qualsevol lloc",
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copied!": "Copiat!",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatisch Ordner im Standardpfad erstellen oder freigeben, die dieses Gerät ankündigt.",
|
||||
"Available debug logging facilities:": "Verfügbare Debugging-Möglichkeiten:",
|
||||
"Be careful!": "Vorsicht!",
|
||||
"Block Indexing": "Blockindizierung",
|
||||
"Body:": "Nachrichtentext:",
|
||||
"Bugs": "Fehler",
|
||||
"Cancel": "Abbrechen",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Gerät",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät „{{name}}“ ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
|
||||
"Device Certificate": "Gerätezertifikat",
|
||||
"Device Group": "Gerätegruppe",
|
||||
"Device ID": "Gerätekennung",
|
||||
"Device Identification": "Geräteidentifikation",
|
||||
"Device Name": "Gerätename",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Nach Datum sortieren",
|
||||
"Filter by name": "Nach Namen sortieren",
|
||||
"Folder": "Ordner",
|
||||
"Folder Group": "Ordnergruppe",
|
||||
"Folder ID": "Ordnerkennung",
|
||||
"Folder Label": "Ordnerbezeichnung",
|
||||
"Folder Path": "Ordnerpfad",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Protokolländerungsverfolgung angehalten. Zum Ende blättern, um fortzufahren.",
|
||||
"Login failed, see Syncthing logs for details.": "Anmeldung fehlgeschlagen, siehe Syncthing-Protokolle für Details.",
|
||||
"Logs": "Protokolle",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Ein Index über alle Blöcke im Ordner führen, sodass Blöcke aus anderen Dateien bei der Synchronisierung von Änderungen wiederverwendet werden können. Diese Option deaktivieren, um die Datenbankgröße zu verringern – allerdings können Blöcke dann nicht mehr dateiübergreifend wiederverwendet werden.",
|
||||
"Major Upgrade": "Hauptversionsaktualisierung",
|
||||
"Mass actions": "Massenaktionen",
|
||||
"Maximum Age": "Höchstalter",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Aus",
|
||||
"Oldest First": "Älteste zuerst",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Optionale beschreibende Bezeichnung des Ordners. Kann auf jedem Gerät unterschiedlich sein.",
|
||||
"Optional group for the device. Can be different on each device.": "Optionale Gruppe für das Gerät. Kann auf jedem Gerät unterschiedlich sein.",
|
||||
"Optional group for the folder. Can be different on each device.": "Optionale Gruppe für den Ordner. Kann auf jedem Gerät unterschiedlich sein.",
|
||||
"Options": "Optionen",
|
||||
"Out of Sync": "Nicht synchronisiert",
|
||||
"Out of Sync Items": "Nicht synchronisierte Elemente",
|
||||
@@ -291,7 +297,7 @@
|
||||
"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": "Pfad zum Ordner auf dem lokalen Gerät. Ordner wird erzeugt, wenn er nicht existiert. Das Tilde-Zeichen (~) kann als Abkürzung verwendet werden für",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Pfad, in dem Versionen gespeichert werden sollen (leer lassen für den Standardordner .stversions im freigegebenen Ordner).",
|
||||
"Paths": "Pfade",
|
||||
"Pause": "Pause",
|
||||
"Pause": "Pausiert",
|
||||
"Pause All": "Alles pausieren",
|
||||
"Paused": "Pausiert",
|
||||
"Paused (Unused)": "Pausiert (nicht genutzt)",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Stufenweise",
|
||||
"Staggered File Versioning": "Stufenweise Dateiversionierung",
|
||||
"Start Browser": "Browser starten",
|
||||
"Starting": "Startet",
|
||||
"Statistics": "Statistiken",
|
||||
"Stay logged in": "Angemeldet bleiben",
|
||||
"Stopped": "Gestoppt",
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"Sync Protocol Listen Addresses": "Διευθύνσεις για το πρωτόκολλο συγχρονισμού",
|
||||
"Sync Status": "Κατάσταση συγχρονισμού",
|
||||
"Syncing": "Συγχρονίζω",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "Συγχρονισμός αναγνωριστικού συσκευής για \"{{devicename}}\"",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "Αναγνωριστικό Syncthing για συσκευή \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Το Syncthing έχει απενεργοποιηθεί.",
|
||||
"Syncthing includes the following software or portions thereof:": "Το Syncthing περιλαμβάνει τα παρακάτω λογισμικά ή μέρη αυτών:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Το Syncthing είναι ελεύθερο λογισμικό και ανοικτού κώδικα, με άδεια MPL v2.0.",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
||||
"Available debug logging facilities:": "Available debug logging facilities:",
|
||||
"Be careful!": "Be careful!",
|
||||
"Block Indexing": "Block Indexing",
|
||||
"Body:": "Body:",
|
||||
"Bugs": "Bugs",
|
||||
"Cancel": "Cancel",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Device",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||
"Device Certificate": "Device Certificate",
|
||||
"Device Group": "Device Group",
|
||||
"Device ID": "Device ID",
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Filter by date",
|
||||
"Filter by name": "Filter by name",
|
||||
"Folder": "Folder",
|
||||
"Folder Group": "Folder Group",
|
||||
"Folder ID": "Folder ID",
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Path": "Folder Path",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
||||
"Login failed, see Syncthing logs for details.": "Login failed, see Syncthing logs for details.",
|
||||
"Logs": "Logs",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Mass actions": "Mass actions",
|
||||
"Maximum Age": "Maximum Age",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Off",
|
||||
"Oldest First": "Oldest First",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||
"Optional group for the device. Can be different on each device.": "Optional group for the device. Can be different on each device.",
|
||||
"Optional group for the folder. Can be different on each device.": "Optional group for the folder. Can be different on each device.",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"Out of Sync Items": "Out of Sync Items",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Staggered",
|
||||
"Staggered File Versioning": "Staggered File Versioning",
|
||||
"Start Browser": "Start Browser",
|
||||
"Starting": "Starting",
|
||||
"Statistics": "Statistics",
|
||||
"Stay logged in": "Stay logged in",
|
||||
"Stopped": "Stopped",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
|
||||
"Available debug logging facilities:": "Available debug logging facilities:",
|
||||
"Be careful!": "Be careful!",
|
||||
"Block Indexing": "Block Indexing",
|
||||
"Body:": "Body:",
|
||||
"Bugs": "Bugs",
|
||||
"Cancel": "Cancel",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Device",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||
"Device Certificate": "Device Certificate",
|
||||
"Device Group": "Device Group",
|
||||
"Device ID": "Device ID",
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Filter by date",
|
||||
"Filter by name": "Filter by name",
|
||||
"Folder": "Folder",
|
||||
"Folder Group": "Folder Group",
|
||||
"Folder ID": "Folder ID",
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Path": "Folder Path",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
||||
"Login failed, see Syncthing logs for details.": "Login failed, see Syncthing logs for details.",
|
||||
"Logs": "Logs",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Mass actions": "Mass actions",
|
||||
"Maximum Age": "Maximum Age",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Off",
|
||||
"Oldest First": "Oldest First",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||
"Optional group for the device. Can be different on each device.": "Optional group for the device. Can be different on each device.",
|
||||
"Optional group for the folder. Can be different on each device.": "Optional group for the folder. Can be different on each device.",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"Out of Sync Items": "Out of Sync Items",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Staggered",
|
||||
"Staggered File Versioning": "Staggered File Versioning",
|
||||
"Start Browser": "Start Browser",
|
||||
"Starting": "Starting",
|
||||
"Statistics": "Statistics",
|
||||
"Stay logged in": "Stay logged in",
|
||||
"Stopped": "Stopped",
|
||||
|
||||
@@ -1,369 +1,567 @@
|
||||
{
|
||||
"A device with that ID is already added.": "Aparato kun samtia ID estis jam aldonita.",
|
||||
"A device with that ID is already added.": "Aparato kun tiu identigilo estis jam aldonita.",
|
||||
"A negative number of days doesn't make sense.": "Negativa numero de tagoj ne havas sencon.",
|
||||
"A new major version may not be compatible with previous versions.": "Nova ĉefa versio eble ne kongruanta kun antaŭaj versioj.",
|
||||
"API Key": "Ŝlosilo API",
|
||||
"A new major version may not be compatible with previous versions.": "Nova ĉefa versio eble povas ne kongrui kun antaŭaj versioj.",
|
||||
"API Key": "Ŝlosilo de aplikaĵa interfaco",
|
||||
"About": "Pri",
|
||||
"Action": "Ago",
|
||||
"Actions": "Agoj",
|
||||
"Active filter rules": "Aktivaj filtrilaj reguloj",
|
||||
"Add": "Aldoni",
|
||||
"Add Device": "Aldoni aparaton",
|
||||
"Add Folder": "Aldoni dosierujon",
|
||||
"Add Remote Device": "Aldoni foran aparaton",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Aldoni aparatojn de la enkondukanto ĝis nia aparatlisto, por reciproke komunigitaj dosierujoj.",
|
||||
"Add new folder?": "Ĉu aldoni novan dosierujon ?",
|
||||
"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.": "Aldone, plena reskana intervalo estos pliigita (60-oble, t.e. nova defaŭlto estas 1h). Vi povas ankaŭ agordi ĝin permane por ĉiu dosierujo poste post elekto de Ne.",
|
||||
"Add filter entry": "Aldoni filtrilon",
|
||||
"Add ignore patterns": "Aldoni ŝablonon de ignorado",
|
||||
"Add new folder?": "Ĉu aldoni novan dosierujon?",
|
||||
"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.": "Aldone, la plena intertempo de reskanado estos pliigita (60-oble, t.e. nova defaŭlto estas 1h). Vi povas ankaŭ agordi ĝin permane por ĉiu dosierujo poste post elekto de \"Ne\".",
|
||||
"Address": "Adreso",
|
||||
"Addresses": "Adresoj",
|
||||
"Advanced": "Altnivela",
|
||||
"Advanced Configuration": "Altnivela Agordo",
|
||||
"All Data": "Ĉiuj Datumoj",
|
||||
"Advanced": "Altnivelaj",
|
||||
"Advanced Configuration": "Altnivelaj agordoj",
|
||||
"All Data": "Ĉiuj datumoj",
|
||||
"All Time": "Ĉiam",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Ĉiuj dosierujoj, kiuj estas dividitaj kun ĉi tiu aparato devas esti protektitaj per pasvorto, tiel ĉiuj senditaj datumoj estas nelegeblaj sen la pasvorto.",
|
||||
"Allow Anonymous Usage Reporting?": "Permesi Anoniman Raporton de Uzado?",
|
||||
"Allowed Networks": "Permesitaj Retoj",
|
||||
"Allow Anonymous Usage Reporting?": "Permesi anoniman raporton de uzado?",
|
||||
"Allowed Networks": "Permesitaj retoj",
|
||||
"Alphabetic": "Alfabeta",
|
||||
"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.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la komunigita dosierujo. Se la vojo al la apliko elhavas blankoj, ĝi devas esti inter citiloj.",
|
||||
"Anonymous Usage Reporting": "Anonima Raporto de Uzado",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formato de anonima raporto de uzado ŝanĝis. Ĉu vi ŝatus transiri al la nova formato?",
|
||||
"Altered by ignoring deletes.": "Ŝanĝita per ignorado de forigadoj.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Ĉiam ŝaltita, kiam la tipo de dosierujo estas \"{{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.": "Ekstera komando manipulas la konservadon de versioj. Ĝi devas forigi la dosieron el la komunigita dosierujo. Se la vojo al la aplikaĵo enhavas spacetojn, ĝi devas esti inter citiloj.",
|
||||
"Anonymous Usage Reporting": "Anonima raportado de uzado",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formato de anonima raporto de uzado ŝanĝiĝis. Ĉu vi ŝatus transiri al la nova formato?",
|
||||
"Applied to LAN": "Aplikita en lokaj retoj",
|
||||
"Apply": "Apliki",
|
||||
"Are you sure you want to override all remote changes?": "Ĉu vi certas, ke vi volas transpasi ĉiujn forajn ŝanĝojn?",
|
||||
"Are you sure you want to permanently delete all these files?": "Ĉu vi certas, ke vi volas porĉiame forigi ĉiujn ĉi tiujn dosierojn?",
|
||||
"Are you sure you want to remove device {%name%}?": "Ĉu vi certas, ke vi volas forigi aparaton {{name}}?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Ĉu vi certas, ke vi volas forigi dosierujon {{label}}?",
|
||||
"Are you sure you want to restore {%count%} files?": "Ĉu vi certas, ke vi volas restarigi {{count}} dosierojn?",
|
||||
"Are you sure you want to revert all local changes?": "Ĉu vi certas, ke vi volas malfari ĉiujn lokajn ŝanĝojn?",
|
||||
"Are you sure you want to upgrade?": "Ĉu vi certe volas plinovigi ?",
|
||||
"Auto Accept": "Akcepti Aŭtomate",
|
||||
"Automatic Crash Reporting": "Aŭtomata raportado de kraŝoj",
|
||||
"Are you sure you want to upgrade?": "Ĉu vi certe volas ĝisdatiĝi?",
|
||||
"Authentication Required": "Aŭtentokontrolo bezonata",
|
||||
"Authors": "Aŭtoroj",
|
||||
"Auto Accept": "Akcepti aŭtomate",
|
||||
"Automatic Crash Reporting": "Aŭtomata raportado de paneoj",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Aŭtomata ĝisdatigo nun proponas la elekton inter stabilaj eldonoj kaj kandidataj eldonoj.",
|
||||
"Automatic upgrades": "Aŭtomataj ĝisdatigoj",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Aŭtomataj ĝisdatigoj ĉiam ŝaltitaj por kandidataj eldonoj.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Aŭtomate krei aŭ komunigi dosierujojn, kiujn ĉi tiu aparato anoncas, ĉe la defaŭlta vojo.",
|
||||
"Available debug logging facilities:": "Disponeblaj elpurigadaj protokoliloj:",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Aŭtomataj ĝisdatigoj estas ĉiam ŝaltitaj por kandidataj eldonoj.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Aŭtomate krei aŭ komunigi dosierujojn, kiujn ĉi tiu aparato anoncas ĉe la defaŭlta vojo.",
|
||||
"Available debug logging facilities:": "Disponeblaj sencimigaj protokoliloj:",
|
||||
"Be careful!": "Atentu!",
|
||||
"Block Indexing": "Indeksado de blokoj",
|
||||
"Body:": "Enhavo:",
|
||||
"Bugs": "Cimoj",
|
||||
"Cancel": "Nuligi",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Ne povas esti ŝaltita, kiam la tipo de dosierujo estas \"{{foldertype}}\".",
|
||||
"Changelog": "Ŝanĝoprotokolo",
|
||||
"Clean out after": "Purigi poste",
|
||||
"Click to see full identification string and QR code.": "Alklaku por vidi la plenan identigan signovicon kaj QR-kodo",
|
||||
"Clean out after": "Purigi post",
|
||||
"Cleaning Versions": "Purigado de versioj",
|
||||
"Cleanup Interval": "Intertempo de purigado",
|
||||
"Click to see full identification string and QR code.": "Alklaku por vidi la plenan identigan signovicon kaj QR-kodon.",
|
||||
"Close": "Fermi",
|
||||
"Command": "Komando",
|
||||
"Comment, when used at the start of a line": "Komento, kiam uzita ĉe la komenco de lineo",
|
||||
"Comment, when used at the start of a line": "Komento, kiam uzata ĉe la komenco de linio",
|
||||
"Compression": "Densigo",
|
||||
"Configuration Directory": "Dosierujo de agordaro",
|
||||
"Configuration File": "Dosiero de agordaro",
|
||||
"Configured": "Agordita",
|
||||
"Connection Error": "Eraro de Konekto",
|
||||
"Connection Type": "Tipo de Konekto",
|
||||
"Connected (Unused)": "Konektita (ne uzata)",
|
||||
"Connection Error": "Eraro de konekto",
|
||||
"Connection Management": "Mastrumado de konektoj",
|
||||
"Connection Type": "Tipo de konekto",
|
||||
"Connections": "Konektoj",
|
||||
"Connections via relays might be rate limited by the relay": "Konektoj, kiu uzas plusendilon, povas esti limigitaj en ofteco fare de la plusendilo",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Daŭra rigardado je ŝanĝoj estas nun havebla ene Syncthing. Ĉi tio detektos ŝangoj sur disko kaj skanos nur modifitajn vojojn. La avantaĝo estas en pli rapifa propagiĝo de ŝanĝoj kaj bezono je malpli plenaj skanoj.",
|
||||
"Copied from elsewhere": "Kopiita el aliloke",
|
||||
"Copied from original": "Kopiita el la originalo",
|
||||
"Currently Shared With Devices": "Nune komunigita kun aparatoj",
|
||||
"Copied!": "Kopiita!",
|
||||
"Copy": "Kopii",
|
||||
"Copy failed! Try to select and copy manually.": "Kopiado malsukcesis! Klopodu elekti kaj kopii permane.",
|
||||
"Currently Shared With Devices": "Nune kunhavata kun aparatoj",
|
||||
"Custom Range": "Propra intervalo",
|
||||
"Danger!": "Danĝero!",
|
||||
"Debugging Facilities": "Elpurigadiloj",
|
||||
"Database Location": "Loko de datumbazo",
|
||||
"Debug": "Sencimigo",
|
||||
"Debugging Facilities": "Sencimigiloj",
|
||||
"Default": "Defaŭlta",
|
||||
"Default Configuration": "Defaŭlta agordo",
|
||||
"Default Folder": "Defaŭlta Dosierujo",
|
||||
"Default Device": "Defaŭlta aparato",
|
||||
"Default Folder": "Defaŭlta dosierujo",
|
||||
"Default Ignore Patterns": "Defaŭltaj ŝablonoj de ignorado",
|
||||
"Defaults": "Defaŭltoj",
|
||||
"Delete": "Forigu",
|
||||
"Delete Unexpected Items": "Forigi neatenditajn erojn",
|
||||
"Deleted {%file%}": "Forigita: {{file}}",
|
||||
"Deselect All": "Malelekti Ĉiujn",
|
||||
"Deselect devices to stop sharing this folder with.": "Malelekti aparatojn por ĉesi komunigi tiun ĉi dosierujon kun ili.",
|
||||
"Deselect devices to stop sharing this folder with.": "Malelekti aparatojn por ĉesi kunhavadon de tiu ĉi dosierujo kun ili.",
|
||||
"Deselect folders to stop sharing with this device.": "Malelekti dosierujojn por ĉesi kunhavadon kun tiu ĉi aparato.",
|
||||
"Device": "Aparato",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Aparato \"{{name}}\" ({{device}} ĉe {{address}}) volas konekti. Aldoni la novan aparaton?",
|
||||
"Device ID": "Aparato ID",
|
||||
"Device Identification": "Identigo de Aparato",
|
||||
"Device Name": "Nomo de Aparato",
|
||||
"Device Certificate": "Atestilo de aparato",
|
||||
"Device Group": "Grupo de aparato",
|
||||
"Device ID": "Identigilo de aparato",
|
||||
"Device Identification": "Identigado de aparato",
|
||||
"Device Name": "Nomo de aparato",
|
||||
"Device Status": "Stato de aparato",
|
||||
"Device is untrusted, enter encryption password": "Aparato ne estas fidinda, entajpu pasvorto por ĉifrado",
|
||||
"Device rate limits": "Limoj de rapideco de aparato",
|
||||
"Device that last modified the item": "Aparato kiu laste modifis la eron",
|
||||
"Device that last modified the item": "Aparato, kiu laste modifis la eron",
|
||||
"Devices": "Aparatoj",
|
||||
"Disable Crash Reporting": "Malŝalti raportadon de kraŝoj",
|
||||
"Disabled": "Malebligita",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Malebligita perioda skanado kaj malebligita rigardado je ŝanĝoj",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Malebligita perioda skanado kaj ebligita rigardado je ŝanĝoj",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Malebligita perioda skanado kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
|
||||
"Disabled": "Malŝaltita",
|
||||
"Disabled periodic scanning and disabled watching for changes": "Perioda skanado kaj atendado por ŝanĝoj malŝaltitaj",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Perioda skanado malŝaltita kaj atendado por ŝanĝoj ŝaltita",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Perioda skanado malŝaltita kaj agordado de atendado por ŝanĝoj malsukcesis, reprovado ĉiuminute:",
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Malŝaltas komparadon kaj sinkronigadon de dosieraj permesoj. Uzinda por operaciumoj kaj dosiersistemoj, en kiuj mankas aŭ nekongruas permesoj (ekz. FAT, exFAT, Synology, Android).",
|
||||
"Discard": "Forĵeti",
|
||||
"Disconnected": "Malkonektita",
|
||||
"Disconnected (Inactive)": "Malkonektita (malaktiva)",
|
||||
"Disconnected (Unused)": "Malkonektita (ne uzata)",
|
||||
"Discovered": "Malkovrita",
|
||||
"Discovery": "Malkovro",
|
||||
"Discovery Failures": "Malsukcesoj de Malkovro",
|
||||
"Discovery Failures": "Malsukcesoj de malkovrado",
|
||||
"Discovery Status": "Stato de malkovrado",
|
||||
"Dismiss": "Forsendi",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Ne aldonu ĝin al la listo de ignoraĵoj, por ke tiu ĉi sciigo povos reaperi poste.",
|
||||
"Do not restore": "Ne restarigu",
|
||||
"Do not restore all": "Ne restarigu ĉion",
|
||||
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi rigardado je ŝanĝoj por ĉiuj viaj dosierujoj?",
|
||||
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi atendadon por ŝanĝoj por ĉiuj viaj dosierujoj?",
|
||||
"Documentation": "Dokumentado",
|
||||
"Download Rate": "Elŝutrapideco",
|
||||
"Downloaded": "Elŝutita",
|
||||
"Downloading": "Elŝutado",
|
||||
"Edit": "Redakti",
|
||||
"Edit Device": "Redakti Aparaton",
|
||||
"Edit Folder": "Redakti Dosierujon",
|
||||
"Edit Device": "Redakti aparaton",
|
||||
"Edit Device Defaults": "Redakti aparatajn defaŭltojn",
|
||||
"Edit Folder": "Redakti dosierujon",
|
||||
"Edit Folder Defaults": "Redakti dosierujajn defaŭltojn",
|
||||
"Editing {%path%}.": "Redaktado de {{path}}.",
|
||||
"Enable Crash Reporting": "Ŝalti raportadon de kraŝoj",
|
||||
"Enable NAT traversal": "Ŝaltu trairan NAT",
|
||||
"Enable Relaying": "Ŝaltu Relajsadon",
|
||||
"Enabled": "Ebligita",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enigu ne negativan nombron (ekz. \"2.35\") kaj elektu uniton. Procentoj estas kiel parto de tuta grandeco de disko.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enigu ne privilegiitan numeron de pordo (1024- 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigu adresojn dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\") aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
|
||||
"Enter ignore patterns, one per line.": "Enigu ignorantajn ŝablonojn, unu po linio.",
|
||||
"Enable Crash Reporting": "Ŝalti raportadon de paneoj",
|
||||
"Enable NAT traversal": "Ŝaltu trairadon de NAT",
|
||||
"Enable Relaying": "Ŝalti plusendadon",
|
||||
"Enabled": "Ŝaltita",
|
||||
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "Ŝaltas sendadon de aldonaj dosieraj atributoj al aliaj aparatoj, kaj aplikadon de alvenantaj atributoj. Ĉi tio povas postuli, ke la programo estas rulata kun pliigitaj permesoj.",
|
||||
"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.": "Ŝaltas sendadon de aldonaj dosieraj atributoj al aliaj aparatoj, sed ne aplikadon de alvenantaj atributoj. Ĉi tio povas grave influi rendimenton de la programo. Ĉiam ŝaltita, se \"Sinkronigi aldonajn atributojn\" estas ŝaltita.",
|
||||
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "Ŝaltas sendadon de informoj pri propreco al aliaj aparatoj, kaj aplikadon de alvenantaj informoj. Ĉi tio ofte postulas, ke la programo estas rulata kun pliigitaj permesoj.",
|
||||
"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.": "Ŝaltas sendadon de informoj pri propreco al aliaj aparatoj, sed ne aplikadon de alvenantaj informoj. Ĉi tio povas grave influi rendimenton de programo. Ĉiam ŝaltita, se \"Sinkronigi proprecon\" estas ŝaltita.",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enigu ne negativan nombron (ekz. \"2.35\") kaj elektu uniton. Elcentoj funkcias kiel parto de la tuta grandeco de disko.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Enigu ne privilegiitan numeron de pordo (1024 - 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigu adresojn, dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\"), aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
|
||||
"Enter ignore patterns, one per line.": "Enigu ŝablonojn de ignorado, unu po linio.",
|
||||
"Enter up to three octal digits.": "Entajpu ĝis tri okumajn ciferojn.",
|
||||
"Error": "Eraro",
|
||||
"External File Versioning": "Ekstera Versionado de Dosiero",
|
||||
"Failed Items": "Malsukcesaj Eroj",
|
||||
"Failed to set up, retrying": "Malsukcesis agordi, provante denove",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Malsukceso por konekti al IPv6 serviloj atendante se ekzistas neniu IPv6 konektebleco.",
|
||||
"File Pull Order": "Ordo por Tiri Dosieron",
|
||||
"File Versioning": "Versionado de Dosieroj",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al .stversions dosierujo kiam anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al date stampitaj versioj en .stversions dosierujo kiam ili estas anstataŭigitaj aŭ forigitaj en Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosieroj estas protektataj kontraŭ ŝanĝoj faritaj en aliaj aparatoj, sed ŝanĝoj faritaj en ĉi tiu aparato estos senditaj al cetera parto de la grupo.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Dosieroj estas sinkronigitaj de la grupo, sed ajnaj ŝanĝoj faritaj loke ne estis senditaj al aliaj aparatoj.",
|
||||
"Filesystem Watcher Errors": "Eraroj de Rigardanto de Dosiersistemo",
|
||||
"Filter by date": "Filtri per daton",
|
||||
"Filter by name": "Filtri per nomon",
|
||||
"Extended Attributes": "Aldonaj atributoj",
|
||||
"Extended Attributes Filter": "Filtrilo de aldonaj atributoj",
|
||||
"External": "Ekstera",
|
||||
"External File Versioning": "Ekstera versiado de dosieroj",
|
||||
"Failed Items": "Malsukcesaj eroj",
|
||||
"Failed to load file versions.": "Eraro dum ŝarĝado de dosieraj versioj.",
|
||||
"Failed to load ignore patterns.": "Eraro dum ŝarĝado de ŝablonoj de ignorado.",
|
||||
"Failed to set up, retrying": "Agordado malsukcesis, reprovado",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Malsukceso dum konekto al serviloj de protokolo IPv6 estas atendebla, se ekzistas neniu konekteblo per IPv6.",
|
||||
"File Pull Order": "Ordo por tirado de dosieroj",
|
||||
"File Versioning": "Versiado de dosieroj",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al dosierujo \".stversions\", kiam anstataŭigitaj aŭ forigitaj de Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Dosieroj estas movigitaj al dato-stampitaj versioj en dosierujo \".stversions\", kiam anstataŭigitaj aŭ forigitaj de Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosieroj estas protektataj kontraŭ ŝanĝoj, faritaj en aliaj aparatoj, sed ŝanĝoj, faritaj en ĉi tiu aparato, estos senditaj al la restantaj aparatoj.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Dosieroj estas sinkronigitaj de la aparataro, sed ajnaj ŝanĝoj, faritaj loke, ne estos senditaj al aliaj aparatoj.",
|
||||
"Filesystem Watcher Errors": "Eraroj de atendado por dosiersistemo",
|
||||
"Filter by date": "Filtri per dato",
|
||||
"Filter by name": "Filtri per nomo",
|
||||
"Folder": "Dosierujo",
|
||||
"Folder ID": "Dosieruja ID",
|
||||
"Folder Label": "Dosieruja Etikedo",
|
||||
"Folder Path": "Dosieruja Vojo",
|
||||
"Folder Type": "Dosieruja Tipo",
|
||||
"Folder Group": "Grupo de dosierujoj",
|
||||
"Folder ID": "Dosieruja identigilo",
|
||||
"Folder Label": "Dosieruja etikedo",
|
||||
"Folder Path": "Dosieruja vojo",
|
||||
"Folder Status": "Stato de dosierujo",
|
||||
"Folder Type": "Dosieruja tipo",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Dosieruja tipo \"{{receiveEncrypted}}\" povas esti agordita nur dum aldonado de nova dosierujo.",
|
||||
"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.": "Dosieruja tipo \"{{receiveEncrypted}}\" ne povas esti ŝanĝita post aldonado de dosierujo. Vi devas forigi la dosierujon, forigi aŭ malĉifri la datumoj en la disko, kaj aldoni la dosierujon denove.",
|
||||
"Folders": "Dosierujoj",
|
||||
"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.": "Por la sekvantaj dosierujoj eraro okazis dum komencado de rigardado je ŝanĝoj. Provante denove ĉiuminute, do eraroj eble foriros baldaŭ. Se ili persistas, provu ripari subkuŝantan problemon kaj petu helpon, se vi ne povas.",
|
||||
"Full Rescan Interval (s)": "Plena Reskana Intervalo (s)",
|
||||
"GUI": "Grafika Interfaco",
|
||||
"GUI Authentication Password": "Pasvorta Aŭtentigo en Grafika Interfaco",
|
||||
"GUI Authentication User": "Uzanta Aŭtentigo en Grafika Interfaco",
|
||||
"GUI Listen Address": "Adreso de Aŭskultado en Grafika Interfaco",
|
||||
"GUI Theme": "Etoso de Grafika Interfaco",
|
||||
"General": "Ĝenerala",
|
||||
"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.": "Por la sekvantaj dosierujoj eraro okazis dum komencado de atendado por ŝanĝoj. Reprovado okazos ĉiuminute, do eraroj eble foriros baldaŭ. Se ili persistas, provu ripari la problemon, kaŭzantan ĉi tion, kaj petu helpon, se vi ne povas.",
|
||||
"Forever": "Por ĉiam",
|
||||
"Full Rescan Interval (s)": "Intertempo de plena reskanado (s)",
|
||||
"GUI": "Grafika interfaco",
|
||||
"GUI / API HTTPS Certificate": "Atestilo de HTTPS por grafika / aplikaĵa interfaco",
|
||||
"GUI Authentication Password": "Pasvorto por aŭtentigo per grafika interfaco",
|
||||
"GUI Authentication User": "Uzanto por aŭtentigo per grafika interfaco",
|
||||
"GUI Authentication: Set User and Password": "Aŭtentigo por grafika interfaco: Agordi uzantnomon kaj pasvorton",
|
||||
"GUI Listen Address": "Adreso de aŭskultado por grafika interfaco",
|
||||
"GUI Override Directory": "Dosierujo de transpaso por grafika interfaco",
|
||||
"GUI Theme": "Etoso de grafika interfaco",
|
||||
"General": "Ĝeneralaj",
|
||||
"Generate": "Generi",
|
||||
"Global Discovery": "Malloka Malkovro",
|
||||
"Global Discovery Servers": "Serviloj de Malloka Malkovro",
|
||||
"Global State": "Malloka Stato",
|
||||
"Global Discovery": "Malloka malkovrado",
|
||||
"Global Discovery Servers": "Serviloj de malloka malkovrado",
|
||||
"Global State": "Malloka stato",
|
||||
"Help": "Helpo",
|
||||
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Avizo: nur negaj reguloj estas detektita, dum la defaŭlto estas negado. Pripensu aldonadon de \"permesu ĉion\" kiel la lasta regulo.",
|
||||
"Home page": "Hejma paĝo",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vi volas malhelpi aliajn uzantojn sur ĉi tiu komputilo atingi Syncthing kaj per ĝi viajn dosierojn, konsideru agordi aŭtentokontrolon.",
|
||||
"Ignore": "Ignoru",
|
||||
"Ignore Patterns": "Ignorantaj Ŝablonoj",
|
||||
"Ignore Permissions": "Ignori Permesojn",
|
||||
"Ignored Devices": "Ignoritaj Aparatoj",
|
||||
"Ignored Folders": "Ignoritaj Dosierujoj",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Tamen, viaj aktualaj agordoj indikas, ke vi ne volas ŝalti ĝin. Aŭtomata paneoraportado estis malŝaltita por vi.",
|
||||
"Identification": "Identigo",
|
||||
"If untrusted, enter encryption password": "Se ne fidinda, entajpu pasvorton por ĉifrado",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vi volas malhelpi aliajn uzantojn sur ĉi tiu komputilo atingi la programon Syncthing kaj, per ĝi, viajn dosierojn, pripensu agordi aŭtentigon.",
|
||||
"Ignore": "Ignori",
|
||||
"Ignore Patterns": "Ŝablonoj de ignorado",
|
||||
"Ignore Permissions": "Ignori permesojn",
|
||||
"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.": "Ŝablonoj de ignorado povas esti aldonitaj nur post kreado de dosierujo. Se ŝaltita, tekstokampo por enigo de ŝablonoj estos prezentita post konservado.",
|
||||
"Ignored Devices": "Ignoritaj aparatoj",
|
||||
"Ignored Folders": "Ignoritaj dosierujoj",
|
||||
"Ignored at": "Ignorita ĉe",
|
||||
"Incoming Rate Limit (KiB/s)": "Alvenanta Rapideco Limo (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Erara agordo povas difekti viajn dosierujajn enhavojn kaj senefikigi Syncthing-n.",
|
||||
"Introduced By": "Enkondukita Per",
|
||||
"Introducer": "Enkondukanto",
|
||||
"Included Software": "Inkluzivita programaro",
|
||||
"Incoming Rate Limit (KiB/s)": "Limigo de alvenanta rapideco (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Erara agordo povas difekti viajn dosierujajn enhavojn kaj senefikigi la programon Syncthing.",
|
||||
"Incorrect user name or password.": "Malĝusta uzantnomo aŭ pasvorto.",
|
||||
"Info": "Informo",
|
||||
"Internally used paths:": "Interne uzataj vojoj:",
|
||||
"Introduced By": "Konatigita de",
|
||||
"Introducer": "Konatiganto",
|
||||
"Introduction": "Konatigo",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversigo de la donita kondiĉo (t.e. ne ekskludi)",
|
||||
"Keep Versions": "Konservi Versiojn",
|
||||
"Keep Versions": "Konservi versiojn",
|
||||
"LDAP": "LDAP",
|
||||
"Largest First": "Plej Granda Unue",
|
||||
"Last Scan": "Lasta Skano",
|
||||
"Last seen": "Lasta vidita",
|
||||
"Latest Change": "Lasta Ŝanĝo",
|
||||
"Learn more": "Lerni pli",
|
||||
"Largest First": "Plej grandaj unue",
|
||||
"Last 30 Days": "Lastaj 30 tagoj",
|
||||
"Last 7 Days": "Lastaj 7 tagoj",
|
||||
"Last Month": "Lasta monato",
|
||||
"Last Scan": "Lasta skano",
|
||||
"Last seen": "Laste vidita je",
|
||||
"Latest Change": "Lasta ŝanĝo",
|
||||
"Learn more": "Lernu pli",
|
||||
"Learn more at {%url%}": "Lernu pli ĉe {{url}}",
|
||||
"Limit": "Limo",
|
||||
"Limit Bandwidth in LAN": "Limigi trafikon en lokaj retoj",
|
||||
"Listener Failures": "Malsukcesoj de aŭskultiloj",
|
||||
"Listener Status": "Stato de aŭskultiloj",
|
||||
"Listeners": "Aŭskultantoj",
|
||||
"Loading data...": "Ŝarĝas datumojn...",
|
||||
"Loading...": "Ŝarĝas...",
|
||||
"Loading data...": "Ŝarĝado de datumoj...",
|
||||
"Loading...": "Ŝarĝado...",
|
||||
"Local Additions": "Lokaj aldonoj",
|
||||
"Local Discovery": "Loka Malkovro",
|
||||
"Local State": "Loka Stato",
|
||||
"Local State (Total)": "Loka Stato (Tuta)",
|
||||
"Locally Changed Items": "Loke Ŝanĝitaj Eroj",
|
||||
"Local Discovery": "Loka malkovrado",
|
||||
"Local State": "Loka stato",
|
||||
"Local State (Total)": "Loka stato (tuta)",
|
||||
"Locally Changed Items": "Loke ŝanĝitaj eroj",
|
||||
"Log": "Protokolo",
|
||||
"Log File": "Protokola dosiero",
|
||||
"Log In": "Ensaluti",
|
||||
"Log Out": "Elsaluti",
|
||||
"Log in to see paths information.": "Ensalutu por vidi informojn pri vojoj.",
|
||||
"Log in to see version information.": "Ensalutu por vidi informojn pri versioj.",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Sekvado de protokolo paŭzita. Rulumu al la supro por daŭrigi.",
|
||||
"Login failed, see Syncthing logs for details.": "Ensaluto malsukcesis, vidu la protokolojn de Syncthing por detaloj.",
|
||||
"Logs": "Protokoloj",
|
||||
"Major Upgrade": "Ĉefa Ĝisdatigo",
|
||||
"Mass actions": "Amasa agoj",
|
||||
"Maximum Age": "Maksimuma Aĝo",
|
||||
"Metadata Only": "Nur Metadatumoj",
|
||||
"Minimum Free Disk Space": "Minimuma Libera Diskospaco",
|
||||
"Mod. Device": "Mod. Aparato",
|
||||
"Mod. Time": "Mod. Tempo",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Konservi indekson de ĉiuj blokoj en la dosierujo, ebligante reuzadon de blokoj el aliaj dosierojn dum sinkronigado de ŝanĝoj. Malŝalti por malpliigado de datumbazo, kun kosto de neebleco de reuzado de blokoj inter dosieroj.",
|
||||
"Major Upgrade": "Granda ĝisdatigo",
|
||||
"Mass actions": "Amasaj agoj",
|
||||
"Maximum Age": "Maksimuma aĝo",
|
||||
"Maximum single entry size": "Maksimuma grando de unuopa ero",
|
||||
"Maximum total size": "Maksimuma tuta grando",
|
||||
"Metadata Only": "Nur metadatumoj",
|
||||
"Minimum Free Disk Space": "Minimuma libera diskospaco",
|
||||
"Mod. Device": "Mod. aparato",
|
||||
"Mod. Time": "Tempo de mod.",
|
||||
"More than a month ago": "Pli ol monato antaŭ",
|
||||
"More than a week ago": "Pli ol semajno antaŭ",
|
||||
"More than a year ago": "Pli ol jaro antaŭ",
|
||||
"Move to top of queue": "Movi al la supro de atendovico",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Multnivela ĵokero (egalas multoblajn dosierujaj niveloj)",
|
||||
"Never": "Neniam",
|
||||
"New Device": "Nova Aparato",
|
||||
"New Folder": "Nova Dosierujo",
|
||||
"Newest First": "Plejnova Unue",
|
||||
"New Device": "Nova aparato",
|
||||
"New Folder": "Nova dosierujo",
|
||||
"Newest First": "Plej novaj unue",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Sen Dosiera Versionado",
|
||||
"No File Versioning": "Sen versiado de dosieroj",
|
||||
"No files will be deleted as a result of this operation.": "Neniuj dosieroj estos forigitaj rezulte de ĉi tiu ago.",
|
||||
"No rules set": "Neniuj reguloj agorditaj",
|
||||
"No upgrades": "Sen ĝisdatigoj",
|
||||
"Not shared": "Ne kunhavata",
|
||||
"Notice": "Avizo",
|
||||
"Number of Connections": "Nombro de konektoj",
|
||||
"OK": "Bone",
|
||||
"Off": "Malŝata",
|
||||
"Oldest First": "Malnova Unue",
|
||||
"Off": "Malŝaltita",
|
||||
"Oldest First": "Plej malnovaj unue",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Laŭvola priskriba etikedo por la dosierujo. Povas esti malsama en ĉiu aparato.",
|
||||
"Options": "Opcioj",
|
||||
"Out of Sync": "Elsinkronigita",
|
||||
"Out of Sync Items": "Elsinkronigitaj Eroj",
|
||||
"Outgoing Rate Limit (KiB/s)": "Eliranta Rapideco Limo (KiB/s)",
|
||||
"Override Changes": "Transpasi Ŝanĝojn",
|
||||
"Optional group for the device. Can be different on each device.": "Laŭvola grupo por la aparato. Povas esti malsama en ĉiu aparato.",
|
||||
"Optional group for the folder. Can be different on each device.": "Laŭvola grupo por la dosierujo. Povas esti malsama en ĉiu aparato.",
|
||||
"Options": "Agordoj",
|
||||
"Out of Sync": "Desinkronigita",
|
||||
"Out of Sync Items": "Desinkronigitaj eroj",
|
||||
"Outgoing Rate Limit (KiB/s)": "Limo de eliranta rapideco (KiB/s)",
|
||||
"Override": "Transpasi",
|
||||
"Override Changes": "Transpasi ŝanĝojn",
|
||||
"Ownership": "Propreco",
|
||||
"Password": "Pasvorto",
|
||||
"Path": "Vojo",
|
||||
"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": "Vojo de la dosierujo en la loka komputilo. Kreiĝos se ne ekzistas. La tilda signo (~) povas esti uzata kiel mallongigilo por",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Vojo kies versioj devus esti stokitaj (lasu malplena por la defaŭlta .stversions dosierujo en la komunigita dosierujo).",
|
||||
"Pause": "Paŭzu",
|
||||
"Pause All": "Paŭzu Ĉion",
|
||||
"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": "Vojo de la dosierujo en la loka komputilo. Estos kreita, se ne ekzistas. La tilda signo (~) povas esti uzata kiel mallongigilo por",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Vojo, kie versioj devus esti stokitaj (lasu malplena por la defaŭlta dosierujo \".stversions\" en la kunhavata dosierujo).",
|
||||
"Paths": "Vojoj",
|
||||
"Pause": "Paŭzigi",
|
||||
"Pause All": "Paŭzigi ĉiujn",
|
||||
"Paused": "Paŭzita",
|
||||
"Paused (Unused)": "Paŭzita (ne uzata)",
|
||||
"Pending changes": "Pritraktataj ŝanĝoj",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Perioda skanado ĉe donita intervalo kaj malebligita rigardado je ŝanĝoj",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Perioda skanado ĉe donita intervalo kaj ebligita rigardado je ŝanĝoj",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Perioda skanado ĉe donita intervalo kaj malsukcesis agordi rigardadon je ŝanĝoj. Provante denove ĉiuminute:",
|
||||
"Please consult the release notes before performing a major upgrade.": "Bonvolu konsulti la notojn de eldono antaŭ elfari ĉefan ĝisdatigon.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bonvolu agordi GUI Authentication Uzanto kaj Pasvorto en la agordoj dialogo.",
|
||||
"Please wait": "Bonvolu atendi",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefikso indikanta, ke la dosiero povas esti forigita, se ĝi malhelpas forigi dosierujon",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefikso indikanta, ke la ŝablono devus esti egalita usklecoblinde.",
|
||||
"Preparing to Sync": "Pretigante sinkronigadon",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Perioda skanado ĉe donita intervalo ŝaltita kaj atendado por ŝanĝoj malŝaltita",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Perioda skanado ĉe donita intertempo kaj atendado por ŝanĝoj ŝaltitaj",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Perioda skanado ĉe donita intertempo kaj agordado de atendado por ŝanĝoj malsukcesis, reprovado ĉiuminute:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Porĉiame aldoni ĝin al la listo de ignoraĵoj, silentigante estontajn sciigojn.",
|
||||
"Please consult the release notes before performing a major upgrade.": "Bonvolu konsultu la notojn de eldono antaŭ elfarado de granda ĝisdatigo.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bonvolu agordu la uzanton kaj pasvorton de aŭtentigo per grafika interfaco en la dialogo \"Agordoj\".",
|
||||
"Please wait": "Bonvolu atendu",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefikso, indikanta, ke la dosiero povas esti forigita, se ĝi malhelpas forigi dosierujon",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Prefikso, indikanta, ke la ŝablono devus esti komparita usklecoblinde",
|
||||
"Preparing to Sync": "Pretigado por sinkronigado",
|
||||
"Preview": "Antaŭrigardo",
|
||||
"Preview Usage Report": "Antaŭrigardo Uzada Raporto",
|
||||
"Quick guide to supported patterns": "Rapida gvidilo pri subtenata ŝablonoj",
|
||||
"QR code": "QR-kodo",
|
||||
"QUIC LAN": "Loka reto per QUIC",
|
||||
"QUIC WAN": "Malloka reto per QUIC",
|
||||
"Quick guide to supported patterns": "Rapida gvidilo pri subtenataj ŝablonoj",
|
||||
"Random": "Hazarda",
|
||||
"Receive Only": "Nur Ricevi",
|
||||
"Recent Changes": "Lastatempaj Ŝanĝoj",
|
||||
"Reduced by ignore patterns": "Malpliigita per ignorantaj ŝablonoj",
|
||||
"Release Notes": "Notoj de Eldono",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidataj eldonoj enhavas la lastajn trajtojn kaj korektojn. Ili estas similaj al la tradiciaj dusemajnaj Syncthing eldonoj.",
|
||||
"Remote Devices": "Foraj Aparatoj",
|
||||
"Remove": "Forigu",
|
||||
"Remove Device": "Forigi Aparaton",
|
||||
"Remove Folder": "Forigi Dosierujon",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Nepra identigilo por la dosierujo. Devas esti la sama en ĉiuj aparatoj de la grupo.",
|
||||
"Rescan": "Reskanu",
|
||||
"Rescan All": "Reskanu Ĉion",
|
||||
"Receive Encrypted": "Ricevi ĉifritajn",
|
||||
"Receive Only": "Nur ricevi",
|
||||
"Received data is already encrypted": "Ricevataj datumoj estas jam ĉifritaj",
|
||||
"Recent Changes": "Lastatempaj ŝanĝoj",
|
||||
"Reduced by ignore patterns": "Malpliigita per ŝablonoj de ignorado",
|
||||
"Relay LAN": "Loka reto kun plusendado",
|
||||
"Relay WAN": "Malloka reto kun plusendado",
|
||||
"Release Notes": "Notoj de eldono",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Kandidataj eldonoj enhavas la lastajn funkciojn kaj korektojn. Ili estas similaj al la tradiciaj dusemajnaj eldonoj de Syncthing.",
|
||||
"Remote Devices": "Foraj aparatoj",
|
||||
"Remote GUI": "Fora grafika interfaco",
|
||||
"Remove": "Forigi",
|
||||
"Remove Device": "Forigi aparaton",
|
||||
"Remove Folder": "Forigi dosierujon",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Nepra identigilo por la dosierujo. Devas esti la sama en ĉiuj aparatoj de la aparataro.",
|
||||
"Rescan": "Reskani",
|
||||
"Rescan All": "Reskani ĉiujn",
|
||||
"Rescans": "Reskanoj",
|
||||
"Restart": "Restartu",
|
||||
"Restart Needed": "Restarto Bezonata",
|
||||
"Restart": "Restartigi",
|
||||
"Restart Needed": "Restarto bezonata",
|
||||
"Restarting": "Restartado",
|
||||
"Restore": "Restarigi",
|
||||
"Restore Versions": "Restarigi Versiojn",
|
||||
"Resume": "Daŭrigu",
|
||||
"Resume All": "Daŭrigu Ĉion",
|
||||
"Restore": "Restaŭri",
|
||||
"Restore Versions": "Restaŭri versiojn",
|
||||
"Resume": "Daŭrigi",
|
||||
"Resume All": "Daŭrigi ĉiujn",
|
||||
"Reused": "Reuzita",
|
||||
"Revert Local Changes": "Reverti Lokajn Ŝangojn",
|
||||
"Save": "Konservu",
|
||||
"Scan Time Remaining": "Restanta Tempo de Skano",
|
||||
"Revert": "Malfari",
|
||||
"Revert Local Changes": "Malfari lokajn ŝanĝojn",
|
||||
"Save": "Konservi",
|
||||
"Saving changes": "Konservado de ŝanĝoj",
|
||||
"Scan Time Remaining": "Restanta tempo de skanado",
|
||||
"Scanning": "Skanado",
|
||||
"See external versioning help for supported templated command line parameters.": "Vidu informlibron de ekstera versionado por subtenata ŝablona parametroj de komandlinio.",
|
||||
"Select All": "Elekti Ĉiujn",
|
||||
"See external versioning help for supported templated command line parameters.": "Vidu informlibron de ekstera versiado por subtenataj ŝablonaj parametroj de komandlinio.",
|
||||
"Select All": "Elekti ĉiujn",
|
||||
"Select a version": "Elekti version",
|
||||
"Select additional devices to share this folder with.": "Elektu pliajn aparatojn por komunigi tiun ĉi dosierujon kun ili.",
|
||||
"Select additional folders to share with this device.": "Elektu aldonajn dosierujojn, kiuj estos kunhavita kun ĉi tiu aparato.",
|
||||
"Select latest version": "Elekti plej novan version",
|
||||
"Select oldest version": "Elekti plej malnovan version",
|
||||
"Send & Receive": "Sendi kaj Ricevi",
|
||||
"Send Only": "Nur Sendi",
|
||||
"Send & Receive": "Sendi kaj ricevi",
|
||||
"Send Extended Attributes": "Sendi aldonajn atributojn",
|
||||
"Send Only": "Nur sendi",
|
||||
"Send Ownership": "Sendi proprecon",
|
||||
"Set Ignores on Added Folder": "Agordi ignoraĵojn por aldonata dosierujo",
|
||||
"Settings": "Agordoj",
|
||||
"Share": "Komunigi",
|
||||
"Share Folder": "Komunigu Dosierujon",
|
||||
"Share this folder?": "Komunigi ĉi tiun dosierujon?",
|
||||
"Shared With": "Komunigita Kun",
|
||||
"Sharing": "Komunigo",
|
||||
"Show ID": "Montru ID",
|
||||
"Show QR": "Montru QR",
|
||||
"Show diff with previous version": "Montri diferenco kun antaŭa versio",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Montrita anstataŭ ID de Aparato en la statuso de la grupo. Estos anoncita al aliaj aparatoj kiel laŭvola defaŭlta nomo.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Montri anstataŭ ID de Aparato en la statuso de la grupo. Estos ĝisdatigita al la nomo de la aparato sciigante se ĝi estas lasita malplena.",
|
||||
"Shut Down": "Sistemfermo",
|
||||
"Shutdown Complete": "Sistemfermo Tuta",
|
||||
"Simple File Versioning": "Simpla Versionado de Dosieroj",
|
||||
"Share": "Kunhavigi",
|
||||
"Share Folder": "Kunhavigi dosierujon",
|
||||
"Share by Email": "Kunhavigi per retpoŝto",
|
||||
"Share by SMS": "Kunhavigi per SMS",
|
||||
"Share this folder?": "Ĉu kunhavigi ĉi tiun dosierujon?",
|
||||
"Shared Folders": "Kunhavataj dosieroj",
|
||||
"Shared With": "Kunhavata kun",
|
||||
"Sharing": "Kunhavado",
|
||||
"Show ID": "Montri identigilon",
|
||||
"Show QR": "Montri QR-kodon",
|
||||
"Show detailed discovery status": "Montri detalan staton de malkovrado",
|
||||
"Show detailed listener status": "Montri detalan staton de aŭskultiloj",
|
||||
"Show diff with previous version": "Montri diferencon kun antaŭa versio",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Montrita anstataŭ identigilo de aparato en la statuso de la aparatato. Estos anoncita al aliaj aparatoj kiel laŭvola defaŭlta nomo.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Montrita anstataŭ identigilo de aparato en la statuso de la aparataro. Estos ĝisdatigita al la nomo de la aparato, kiun ĝi sciigos, se lasita malplena.",
|
||||
"Shut Down": "Malŝalti",
|
||||
"Shutdown Complete": "Malŝaltado plenumita",
|
||||
"Simple": "Simpla",
|
||||
"Simple File Versioning": "Simpla versiado de dosieroj",
|
||||
"Single level wildcard (matches within a directory only)": "Ununivela ĵokero (egalas nur ene de dosierujo)",
|
||||
"Size": "Grandeco",
|
||||
"Smallest First": "Plej Malgranda Unue",
|
||||
"Some items could not be restored:": "Iuj eroj ne povis esti restarigitaj:",
|
||||
"Size": "Grando",
|
||||
"Smallest First": "Plej malgrandaj unue",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Iuj metodoj de malkovrado ne povas esti starigitaj por trovado de aliaj aparatoj aŭ anoncado de tiu ĉi aparato:",
|
||||
"Some items could not be restored:": "Iuj eroj ne povis esti restaŭritaj:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Iuj adresoj de aŭskultado ne povas esti ŝaltitaj por akceptado de konektoj:",
|
||||
"Source Code": "Fontkodo",
|
||||
"Stable releases and release candidates": "Stabilaj eldonoj kaj kandidataj eldonoj",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Stabilaj eldonoj prokrastas je ĉirkaŭ du semjanoj. Dum tiu tempo ili estos testataj kiel kandidataj eldonoj.",
|
||||
"Stable releases only": "Nur stabilaj eldonoj",
|
||||
"Staggered File Versioning": "Gradigita Dosiera Versionado",
|
||||
"Start Browser": "Startu Retumilon",
|
||||
"Staggered": "Gradigita",
|
||||
"Staggered File Versioning": "Gradigita versiado de dosieroj",
|
||||
"Start Browser": "Starti retumilon",
|
||||
"Starting": "Komencado",
|
||||
"Statistics": "Statistikoj",
|
||||
"Stay logged in": "Resti ensalutita",
|
||||
"Stopped": "Haltita",
|
||||
"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.": "Enmemorigas kaj sinkronigas nur ĉifritajn datumojn. Dosierujoj en ĉiuj konektitaj aparatoj devas ankaŭ esti agordita kun la sama pasvorto aŭ havi tipon \"{{receiveEncrypted}}\".",
|
||||
"Subject:": "Temo:",
|
||||
"Support": "Subteno",
|
||||
"Support Bundle": "Pakaĵo por subteno",
|
||||
"Sync Protocol Listen Addresses": "Aŭskultado Adresoj de Sinkprotokolo",
|
||||
"Syncing": "Sinkronigas",
|
||||
"Sync Extended Attributes": "Sinkronigi aldonajn atributojn",
|
||||
"Sync Ownership": "Sinkronigi proprecon",
|
||||
"Sync Protocol Listen Addresses": "Aŭskultataj adresoj de sinkroniga protokolo",
|
||||
"Sync Status": "Stato de sinkronigado",
|
||||
"Syncing": "Sinkronigado",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "Identigilo Syncthing de aparato \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Syncthing estis malŝaltita.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inkluzivas la jenajn programarojn aŭ porciojn ĝiajn:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing estas libera kaj malferma fonta programaro licencita kiel MPL v2.0.",
|
||||
"Syncthing is restarting.": "Syncthing estas restartanta.",
|
||||
"Syncthing is upgrading.": "Syncthing estas ĝisdatigita.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ŝajnas nefunkcii, aŭ estas problemo kun via retkonekto. Reprovado...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ŝajnas renkonti problemon kun la traktado de via peto. Bonvolu refreŝigi la paĝon aŭ restarti Syncthing se la problemo daŭras.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing inkluzivas la jenajn programarojn aŭ ties porciojn:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing estas libera kaj malfermitkoda programo, licencita per permesilo 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 estas programo por daŭra sinkronigado de dosieroj. Ĝi sinkronigas dosierojn inter du aŭ pli komputiloj realtempe, sekure protektita de rigardantoj. Viaj datumoj restas viaj, kaj vi meritas elekti, kie ili estas enmemorigataj, kun kiuj eksteraj liverantoj ili estas kunhavataj, kaj kiel ili estas transmetataj tra la interreto.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing aŭskultas per la sekvaj retaj adresoj por provoj de konekto el aliaj aparatoj:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing ne aŭskultas por konektaj el aliaj aparatoj per iu ajn adreso. Nur elirantaj konektoj povos funkcii.",
|
||||
"Syncthing is restarting.": "Syncthing restartiĝas.",
|
||||
"Syncthing is saving changes.": "Syncthing konservas la ŝanĝojn.",
|
||||
"Syncthing is upgrading.": "Syncthing ĝisdatiĝas.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing nun subtenas aŭtomatan raportadon de paneoj al la disvolvantoj. Ĉi tiu funkcio estas defaŭlte ŝaltita.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing ŝajnas nefunkcia, aŭ estas problemo kun via retkonekto. Reprovado…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ŝajnas renkonti problemon kun la traktado de via peto. Bonvolu refreŝigu la paĝon aŭ restarti la programon Syncthing, se la problemo daŭras.",
|
||||
"TCP LAN": "Loka reto per TCP",
|
||||
"TCP WAN": "Malloka reto per TCP",
|
||||
"Take me back": "Prenu min reen",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "La adreso de grafika interfaco estas superregita per startigaj agordoj. Ŝanĝoj ĉi tie ne efektiviĝas dum la superrego estas aktuala.",
|
||||
"The Syncthing Authors": "La aŭtoroj de Syncthing",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "La administra interfaco de Syncthing estas agordita por permesi foran atingon sen pasvorto.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "La agregita statistikoj estas publike disponebla ĉe la URL malsupre.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La agordo estis registrita sed ne aktivigita. Syncthing devas restarti por aktivigi la novan agordon.",
|
||||
"The device ID cannot be blank.": "La aparato ID ne povas esti malplena.",
|
||||
"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).": "La aparato ID por eniri ĉi tie estas trovebla per \"Agoj > Montru ID\" dialogo en la alia aparato. Interspacoj kaj streketoj estas opcio (ignorigita).",
|
||||
"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.": "La ĉifrita raporto de uzado estas sendata ĉiutage. Ĝi estas uzata por sekvi komunajn platformojn, dosierujajn grandojn kaj aplikaĵajn versiojn. Se la raporto datumaro ŝanĝis, vi estos avertata per ĉi tiu dialogo denove.",
|
||||
"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 enigita aparato ID ne ŝajnas valida. Ĝi devas esti signoĉeno el 52 aŭ 56 karaktroj longa enhavanta leterojn kaj nombrojn, kun interspacoj kaj streketoj opciaj.",
|
||||
"The folder ID cannot be blank.": "La dosierujo ID ne povas esti malplena.",
|
||||
"The folder ID must be unique.": "La dosierujo ID devas esti unika.",
|
||||
"The cleanup interval cannot be blank.": "La intertempo de purigado ne povas esti malplena.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La agordo estis registrita, sed ne aktivigita. Syncthing devas restarti por aktivigi la novan agordaron.",
|
||||
"The device ID cannot be blank.": "La aparata identigilo ne povas esti malplena.",
|
||||
"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).": "La identigilo de aparato por eniri ĉi tie estas trovebla per dialogo \"Agoj > Montri identigilon\" en la alia aparato. Spacetoj kaj streketoj estas opciaj (ignoritaj).",
|
||||
"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.": "La ĉifrita raporto de uzado estas sendata ĉiutage. Ĝi estas uzata por sekvi kutimajn platformojn, dosierujajn grandojn kaj aplikaĵajn versiojn. Se la raporto datumaro ŝanĝiĝis, vi estos avertata per ĉi tiu dialogo denove.",
|
||||
"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 enigita aparata identigilo ne ŝajnas valida. Ĝi devas esti signoĉeno el 52 aŭ 56 simboloj longa, enhavanta leterojn kaj nombrojn, kun opciaj interspacoj kaj streketoj.",
|
||||
"The folder ID cannot be blank.": "La dosieruja identigilo ne povas esti malplena.",
|
||||
"The folder ID must be unique.": "La dosieruja identigilo devas esti unika.",
|
||||
"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.": "La enhavo de dosierujo en aliaj aparatoj estas superskribita por esti sama al ĉi tiu aparato. Dosieroj, ne ekzistantaj ĉi tie, estos forigitaj en aliaj aparatoj.",
|
||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "La enhavo de dosierujo en ĉi tiu aparato estas superskribita por esti sama al aliaj aparatoj. Dosieroj, freŝe aldonitaj ĉi tien, estos forigitaj.",
|
||||
"The folder path cannot be blank.": "La vojo de dosierujo ne povas esti malplena.",
|
||||
"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.": "La jenaj intervaloj estas uzataj: dum la unua horo version restas dum ĉiuj 30 sekundoj, dum la unua tago versio restas konservita dum ĉiu horo, dum la unuaj 30 tagoj versio estas konservita dum ĉiu tago, ĝis la maksimume aĝa versio restas konservita dum ĉiu semajno.",
|
||||
"The following items could not be synchronized.": "La sekvantaj eroj ne povas esti sinkronigitaj.",
|
||||
"The following items were changed locally.": "La sekvantaj eroj estis ŝanĝitaj loke.",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "La sekvaj metodoj estas uzataj por malkovri aliajn aparatojn en la reto kaj anonci ĉi tiun aparaton por esti trovita de aliaj:",
|
||||
"The following text will automatically be inserted into a new message.": "La sekva teksto estos aŭtomate enmetita al novan mesaĝon.",
|
||||
"The following unexpected items were found.": "La sekvaj neatenditaj eroj estis trovitaj.",
|
||||
"The interval must be a positive number of seconds.": "La intertempo devas esti pozitiva nombro da sekundoj.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "La intertempo, en sekundoj, por ruli purigadon en la dosierujo de versioj. Nul malŝaltas periodan purigadon.",
|
||||
"The maximum age must be a number and cannot be blank.": "La maksimuma aĝo devas esti nombro kaj ne povas esti malplena.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "La maksimuma tempo por konservi version (en tagoj, agordi je 0 por konservi versiojn eterne).",
|
||||
"The number of connections must be a non-negative number.": "La nombro de konektoj devas esti ne-negativan nombron.",
|
||||
"The number of days must be a number and cannot be blank.": "La nombro da tagoj devas esti nombro kaj ne povas esti malplena.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "La nombro da tagoj por konservi dosierojn en la rubujo. Nulo signifas eterne.",
|
||||
"The number of old versions to keep, per file.": "La nombro da malnovaj versioj por konservi, po ĉiu dosiero.",
|
||||
"The number of versions must be a number and cannot be blank.": "La nombro da versioj devas esti nombro kaj ne povas esti malplena.",
|
||||
"The path cannot be blank.": "La vojo ne povas esti malplena.",
|
||||
"The rate limit is applied to the accumulated traffic of all connections to this device.": "La limigo de ofteco estas aplikita al la amasigita trafiko de ĉiuj konektoj al ĉiu ti aparato.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "La rapideca limo devas esti pozitiva nombro (0: senlimo)",
|
||||
"The remote device has not accepted sharing this folder.": "La fora aparato ne akceptis kunhavadon de ĉiu ti dosierujo.",
|
||||
"The remote device has paused this folder.": "La fora aparato paŭzigis ĉi tiun dosierujon.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "La intervalo de reskano devas esti pozitiva nombro da sekundoj.",
|
||||
"There are no devices to share this folder with.": "Estas neniu aparato kun kiu komunigi tiun ĉi dosierujon.",
|
||||
"There are no devices to share this folder with.": "Estas neniu aparato, kun kiu kunhavigi tiun ĉi dosierujon.",
|
||||
"There are no file versions to restore.": "Estas neniuj dosieraj versioj por restaŭrado.",
|
||||
"There are no folders to share with this device.": "Estas neniuj dosierujoj por kunhavado kun tiu ĉi aparato.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Ili estas reprovitaj aŭtomate kaj estos sinkronigitaj kiam la eraro estas solvita.",
|
||||
"This Device": "Ĉi Tiu Aparato",
|
||||
"This Device": "Ĉi tiu aparato",
|
||||
"This Month": "Tiu ĉi monato",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Ĉi tio povas facile doni al kodumuloj atingon por legi kaj ŝanĝi ajnajn dosierojn en via komputilo.",
|
||||
"This is a major version upgrade.": "Ĉi tio estas ĉefversio ĝisdatigita.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Ĉi tiu agordo regas la libera spaco postulita sur la hejma (t.e. indeksa datumbaza) disko.",
|
||||
"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.": "Ĉi tiu aparato ne povas aŭtomate malkovri aliajn aparatojn aŭ anonci sia adreso por esti trovita de aliaj. Nur aparatoj kun statike agorditaj adresoj povas konekti.",
|
||||
"This is a major version upgrade.": "Ĉi tio estas granda ĝisdatigo.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Ĉi tiu agordo regas la liberan spacon, postulitan sur la hejma disko (t.e. en kiu la indeksa datumbazo estas enmemorigata).",
|
||||
"Time": "Tempo",
|
||||
"Time the item was last modified": "Tempo de lasta modifo de la ero",
|
||||
"Trash Can File Versioning": "Rubuja Dosiera Versionado",
|
||||
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "Por konekti al aparato Syncthing nomita \"{{devicename}}\", aldonu novan foran aparaton kun ĉi tiu identigilo en via aparato:",
|
||||
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Por permesi regulon, ŝalti la markobutonon. Por negi regulon, malŝalti ĝin.",
|
||||
"Today": "Hodiaŭ",
|
||||
"Trash Can": "Rubujo",
|
||||
"Trash Can File Versioning": "Rubuja versiado de dosieroj",
|
||||
"Type": "Tipo",
|
||||
"UNIX Permissions": "Permesoj UNIX",
|
||||
"UNIX Permissions": "Permesoj de UNIX",
|
||||
"Unavailable": "Ne disponebla",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Ne disponebla/Malebligita de administranto aŭ subtenanto",
|
||||
"Undecided (will prompt)": "Hezitema (demandos)",
|
||||
"Unignore": "Malignoru",
|
||||
"Unexpected Items": "Neatenditaj eroj",
|
||||
"Unexpected items have been found in this folder.": "Neatenditaj eroj estis trovitaj en ĉi tiu dosierujo.",
|
||||
"Unignore": "Malignori",
|
||||
"Unknown": "Nekonata",
|
||||
"Unshared": "Nekomunigita",
|
||||
"Unshared Devices": "Malkomunigitaj aparatoj",
|
||||
"Unshared": "Nekunhavata",
|
||||
"Unshared Devices": "Nekunhavataj aparatoj",
|
||||
"Unshared Folders": "Nekunhavataj dosierujoj",
|
||||
"Untrusted": "Ne fidita",
|
||||
"Up to Date": "Ĝisdata",
|
||||
"Upgrade": "Altgradigo",
|
||||
"Upgrade To {%version%}": "Altgradigi Al {{version}}",
|
||||
"Upgrading": "Altgradigata",
|
||||
"Updated {%file%}": "Ĝisdatigita: {{file}}",
|
||||
"Upgrade": "Ĝisdatigo",
|
||||
"Upgrade To {%version%}": "Ĝisdatigi al {{version}}",
|
||||
"Upgrading": "Ĝisdatigado",
|
||||
"Upload Rate": "Alŝutrapideco",
|
||||
"Uptime": "Daŭro de funkciado",
|
||||
"Usage reporting is always enabled for candidate releases.": "Uzada raportado ĉiam ŝaltita por kandidataj eldonoj.",
|
||||
"Use HTTPS for GUI": "Uzi HTTPS por grafika interfaco.",
|
||||
"Use HTTPS for GUI": "Uzi HTTPS por grafika interfaco",
|
||||
"Use notifications from the filesystem to detect changed items.": "Uzi sciigoj de la dosiersistemo por detekti ŝanĝitajn erojn.",
|
||||
"User": "Uzanto",
|
||||
"User Home": "Hejmo de uzanto",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Uzantnomo kaj pasvorto ne estis agorditaj por aŭtentokontrolo per grafika interfaco. Bonvolu pripensi agordi ilin.",
|
||||
"Using a QUIC connection over LAN": "QUIC-konekto sur loka reto uzata",
|
||||
"Using a QUIC connection over WAN": "QUIC-konekto sur malloka reto uzata",
|
||||
"Using a direct TCP connection over LAN": "Rekta TCP-konekto sur loka reto uzata",
|
||||
"Using a direct TCP connection over WAN": "Rekta TCP-konekto sur malloka reto uzata",
|
||||
"Version": "Versio",
|
||||
"Versions": "Versioj",
|
||||
"Versions Path": "Vojo de Versioj",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioj estas aŭtomate forigita se ili estas pli malnovaj ol la maksimuma aĝo aŭ superas la nombron da dosieroj permesita en intervalo.",
|
||||
"Waiting to Scan": "Atendante skanadon",
|
||||
"Waiting to Sync": "Atendante sinkronigadon",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas parenta dosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas parenta dosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Versions Path": "Vojo de versioj",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioj estas aŭtomate forigitaj, se ili estas pli malnovaj ol la maksimuma aĝo, aŭ superas la nombron da dosieroj, permesitaj en intertempo.",
|
||||
"Waiting to Clean": "Atendado por purigado",
|
||||
"Waiting to Scan": "Atendado por skanado",
|
||||
"Waiting to Sync": "Atendado por sinkronigado",
|
||||
"Warning": "Averto",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas parenca dosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas parenca dosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Averto, ĉi tiu vojo estas subdosierujo de ekzistanta dosierujo \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Averto: se vi uzas ekstera rigardanto simila al {{syncthingInotify}}, vi devas certiĝi ĝi estas senaktivita.",
|
||||
"Watch for Changes": "Rigardi Ŝanĝojn",
|
||||
"Watching for Changes": "Rigardado je Ŝanĝoj",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Rigardado je ŝanĝoj malkovras plejparton de la ŝanĝoj sen perioda skanado.",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Averto: se vi uzas eksteran rigardanton, similan al {{syncthingInotify}}, vi devas certiĝi, ke ĝi estas malaktiva.",
|
||||
"Watch for Changes": "Atendi por ŝanĝoj",
|
||||
"Watching for Changes": "Atendado por ŝanĝoj",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Atendado por ŝanĝoj malkovras plejparton de la ŝanĝoj sen perioda skanado.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Dum la aldonado de nova aparato, memoru ke ĉi tiu aparato devas esti aldonita en la alia flanko ankaŭ.",
|
||||
"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.": "Dum la aldonado de nova dosierujo, memoru ke la Dosieruja ID estas uzita por ligi la dosierujojn kune inter aparatoj. Ili estas literfakodistingaj kaj devas kongrui precize inter ĉiuj aparatoj.",
|
||||
"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.": "Dum la aldonado de nova dosierujo, memoru ke la dosieruja identigilo estas uzita por ligi la dosierujojn kune inter aparatoj. Ili estas usklecodistingaj kaj devas kongrui precize inter ĉiuj aparatoj.",
|
||||
"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.": "Kiam agordita kiel pli ol unu en ambaŭ aparatoj, Syncthing provos starigi multajn samtempajn konektojn. Se la valoroj malsamaj, la pleja estos uzata. Agordu kiel nul, por ke Syncthing decidos mem.",
|
||||
"Yes": "Jes",
|
||||
"Yesterday": "Hieraŭ",
|
||||
"You can also copy and paste the text into a new message manually.": "Vi povas ankaŭ kopii kaj alglui la tekston en novan mesaĝon permane.",
|
||||
"You can also select one of these nearby devices:": "Vi povas ankaŭ elekti unu el ĉi tiuj proksimaj aparatoj:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Vi povas ŝanĝi vian elekton iam ajn en la Agorda dialogo.",
|
||||
"You can change your choice at any time in the Settings dialog.": "Vi povas ŝanĝi vian elekton iam ajn en la dialogo \"Agordoj\".",
|
||||
"You can read more about the two release channels at the link below.": "Vi povas legi plu pri la du eldonkanaloj per la malsupra ligilo.",
|
||||
"You have no ignored devices.": "Vi havas neniujn ignoritajn aparatojn.",
|
||||
"You have no ignored folders.": "Vi havas neniujn ignoritajn dosierujojn.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Vi havas ne konservitaj ŝanĝoj. Ĉu vi vere volas forĵeti ilin?",
|
||||
"You have no ignored devices.": "Vi havas neniujn ignoratajn aparatojn.",
|
||||
"You have no ignored folders.": "Vi havas neniujn ignoratajn dosierujojn.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Vi havas nekonservitajn ŝanĝojn. Ĉu vi vere volas forĵeti ilin?",
|
||||
"You must keep at least one version.": "Vi devas konservi almenaŭ unu version.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Vi neniam devus aldoni aŭ ŝanĝi ion ajn loke en dosierujo \"{{receiveEncrypted}}\".",
|
||||
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Via SMS-apo devus malfermi, por ke vi povas elekti la adresaton kaj sendi la mesaĝon el via telefonnumero.",
|
||||
"Your email app should open to let you choose the recipient and send it from your own address.": "Via retpoŝta apo devus malfermi, por ke vi povas elekti la adresaton kaj sendi la mesaĝon el via retpoŝtadreso.",
|
||||
"days": "tagoj",
|
||||
"deleted": "forigita",
|
||||
"deny": "negi",
|
||||
"directories": "dosierujoj",
|
||||
"file": "dosiero",
|
||||
"files": "dosieroj",
|
||||
"folder": "dosierujo",
|
||||
"full documentation": "tuta dokumentado",
|
||||
"items": "eroj",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas komunigi dosierujon \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas komunigi dosierujon \"{{folderlabel}}\" ({{folder}})."
|
||||
"modified": "modifita",
|
||||
"permit": "permesi",
|
||||
"seconds": "sekundoj",
|
||||
"theme": {
|
||||
"name": {
|
||||
"black": "Nigra",
|
||||
"dark": "Malhela",
|
||||
"default": "Defaŭlta",
|
||||
"light": "Hela"
|
||||
}
|
||||
},
|
||||
"unknown device": "nekonata aparato",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} volas kunhavigi dosierujon \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} volas kunhavigi dosierujon \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} povas rekonatigi ĉi tiun aparaton."
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"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!",
|
||||
"Block Indexing": "Indexación por bloques",
|
||||
"Body:": "Contenido:",
|
||||
"Bugs": "Errores",
|
||||
"Cancel": "Cancelar",
|
||||
@@ -99,6 +100,7 @@
|
||||
"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 Group": "Grupo dispositivo",
|
||||
"Device ID": "ID del dispositivo",
|
||||
"Device Identification": "Identificador del Dispositivo",
|
||||
"Device Name": "Nombre del dispositivo",
|
||||
@@ -149,7 +151,7 @@
|
||||
"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",
|
||||
"Error": "Error",
|
||||
"Extended Attributes": "Atributos extendidos",
|
||||
"Extended Attributes Filter": "Filtro de atributos extendidos",
|
||||
"External": "Externo",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Filtrar por fecha",
|
||||
"Filter by name": "Filtrar por nombre",
|
||||
"Folder": "Carpeta",
|
||||
"Folder Group": "Grupo carpeta",
|
||||
"Folder ID": "ID de la carpeta",
|
||||
"Folder Label": "Etiqueta de la carpeta",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
@@ -249,6 +252,7 @@
|
||||
"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",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Mantiene un índice de todos los bloques de la carpeta, lo que permite reutilizar bloques de otros archivos al sincronizar cambios. Desactívalo para reducir el tamaño de la base de datos a costa de no poder reutilizar bloques entre archivos.",
|
||||
"Major Upgrade": "Actualización importante",
|
||||
"Mass actions": "Acción masiva",
|
||||
"Maximum Age": "Antigüedad máxima",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Desactivado",
|
||||
"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.",
|
||||
"Optional group for the device. Can be different on each device.": "Grupo opcional para el dispositivo. Puede ser diferente en cada dispositivo.",
|
||||
"Optional group for the folder. Can be different on each device.": "Grupo opcional para la carpeta. Puede ser diferente en cada dispositivo.",
|
||||
"Options": "Opciones",
|
||||
"Out of Sync": "No sincronizado",
|
||||
"Out of Sync Items": "Elementos no sincronizados",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Gradual",
|
||||
"Staggered File Versioning": "Escalonado",
|
||||
"Start Browser": "Iniciar en navegador",
|
||||
"Starting": "Iniciando",
|
||||
"Statistics": "Estadísticas",
|
||||
"Stay logged in": "Permanecer conectado",
|
||||
"Stopped": "Detenido",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"A device with that ID is already added.": "Un appareil portant cet ID est déjà présent.",
|
||||
"A device with that ID is already added.": "Cet ID est déjà enregistré.",
|
||||
"A negative number of days doesn't make sense.": "Ce champ n'accepte qu'un entier positif ou nul.",
|
||||
"A new major version may not be compatible with previous versions.": "Une nouvelle version majeure peut présenter des incompatibilités avec les versions antérieures.",
|
||||
"API Key": "Clé d'API",
|
||||
@@ -9,11 +9,11 @@
|
||||
"Active filter rules": "Filtres enregistrés",
|
||||
"Add": "Ajouter",
|
||||
"Add Device": "Ajouter l'appareil",
|
||||
"Add Folder": "Ajouter un partage",
|
||||
"Add Remote Device": "Ajouter un appareil",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Ajouter des appareils de l'introducteur à notre liste d'appareils, pour les partages en commun.",
|
||||
"Add Folder": "Ajouter un partage...",
|
||||
"Add Remote Device": "Ajouter un appareil...",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Ajouter les appareils de l'introducteur à notre liste, pour les partages que nous avons en commun.",
|
||||
"Add filter entry": "Ajouter une entrée de filtrage",
|
||||
"Add ignore patterns": "Ajouter des masques d'exclusion",
|
||||
"Add ignore patterns": "Ajouter des modèles d'exclusion",
|
||||
"Add new folder?": "Ajouter ce partage ?",
|
||||
"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.": "Dans ce cas, l'intervalle de réanalyse complète sera augmenté (60 fois, c.-à-d. une nouvelle valeur par défaut de 1h). Vous pouvez également la configurer manuellement plus tard, pour chaque partage, après avoir choisi Non.",
|
||||
"Address": "Adresse",
|
||||
@@ -30,26 +30,27 @@
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Toujours activé pour le type de partage \"{{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.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers du répertoire partagé. Si le chemin contient des espaces, il doit être spécifié entre guillemets.",
|
||||
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Le format du rapport anonyme d'utilisation a changé. Voulez-vous passer au nouveau format ?",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Le format du rapport anonyme d'utilisation a changé. Voulez-vous adopter le nouveau format ?",
|
||||
"Applied to LAN": "Appliqué au LAN",
|
||||
"Apply": "Appliquer",
|
||||
"Are you sure you want to override all remote changes?": "Voulez-vous vraiment écraser tous les changements distants ?",
|
||||
"Are you sure you want to permanently delete all these files?": "Êtes-vous sûrs de vouloir définitivement supprimer tous ces fichiers ?",
|
||||
"Are you sure you want to remove device {%name%}?": "Êtes-vous sûr de vouloir supprimer l'appareil {{name}} ?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Êtes-vous sûr de vouloir supprimer le partage {{label}} ?",
|
||||
"Are you sure you want to restore {%count%} files?": "Êtes-vous sûr de vouloir restaurer {{count}} fichiers ?",
|
||||
"Are you sure you want to revert all local changes?": "Voulez-vous vraiment écraser tous les changements locaux ?",
|
||||
"Are you sure you want to upgrade?": "Voulez-vous vraiment mettre à jour ?",
|
||||
"Are you sure you want to override all remote changes?": "Voulez-vous vraiment écraser toutes les modifications distantes ?",
|
||||
"Are you sure you want to permanently delete all these files?": "Voulez-vous vraiment supprimer tous ces fichiers définitivement ?",
|
||||
"Are you sure you want to remove device {%name%}?": "Voulez-vous vraiment supprimer l’appareil {{name}} ?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Voulez-vous vraiment supprimer le partage {{label}} ?",
|
||||
"Are you sure you want to restore {%count%} files?": "Voulez-vous vraiment restaurer {{count}} fichiers ?",
|
||||
"Are you sure you want to revert all local changes?": "Voulez-vous vraiment annuler toutes les modifications locales ?",
|
||||
"Are you sure you want to upgrade?": "Voulez-vous vraiment mettre à jour ?",
|
||||
"Authentication Required": "Authentification nécessaire",
|
||||
"Authors": "Auteurs",
|
||||
"Auto Accept": "Accepter automatiquement",
|
||||
"Auto Accept": "Accepter automatiquement ses invitations",
|
||||
"Automatic Crash Reporting": "Rapports de plantage automatiques",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Le système de mise à jour automatique propose le choix entre versions stables et versions préliminaires.",
|
||||
"Automatic upgrades": "Mises à jour automatiques",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Les mises à jour automatiques sont toujours activées pour les versions préliminaires (-rc.N).",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatiquement créer ou partager les partages que cet appareil annonce sur le chemin par défaut.",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Les mises à jour automatiques sont toujours activées pour les versions mineures.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatiquement créer dans le chemin par défaut les partages auxquels cet appareil vous propose de participer, ou accepter leur partage s'ils pré-existent.",
|
||||
"Available debug logging facilities:": "Outils de débogage disponibles :",
|
||||
"Be careful!": "Faites attention !",
|
||||
"Block Indexing": "Indexation de blocs",
|
||||
"Body:": "Corps du message :",
|
||||
"Bugs": "Bogues",
|
||||
"Cancel": "Annuler",
|
||||
@@ -72,7 +73,7 @@
|
||||
"Connection Type": "Type de connexion",
|
||||
"Connections": "Connexions",
|
||||
"Connections via relays might be rate limited by the relay": "Les connexions via un relais sont généralement limitées en débit par les capacités du relais",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "La surveillance permanente des changements est maintenant disponible. C'est le disque qui signale les modifications à Syncthing qui lance alors une analyse uniquement sur les partages modifiés. Les avantages sont que les changements sont propagés plus rapidement et moins d'analyses complètes sont nécessaires.",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "La surveillance permanente des changements est maintenant disponible. C'est le disque qui signale les modifications à Syncthing qui lance alors une analyse uniquement sur les partages modifiés. Les avantages sont que les changements sont propagés plus rapidement et que moins d'analyses complètes sont nécessaires.",
|
||||
"Copied from elsewhere": "Copié d'ailleurs",
|
||||
"Copied from original": "Copié depuis l'original",
|
||||
"Copied!": "Copié dans le presse-papiers !",
|
||||
@@ -88,7 +89,7 @@
|
||||
"Default Configuration": "Préférences pour les créations (non rétroactif)",
|
||||
"Default Device": "Nouveaux appareils",
|
||||
"Default Folder": "Nouveaux partages",
|
||||
"Default Ignore Patterns": "Masques d'exclusion par défaut",
|
||||
"Default Ignore Patterns": "Modèles d'exclusion par défaut",
|
||||
"Defaults": "Personnalisation",
|
||||
"Delete": "Supprimer",
|
||||
"Delete Unexpected Items": "Supprimer les éléments inattendus",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Appareil",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "L'appareil \"{{name}}\" ({{device}} à l'adresse {{address}}) demande à se connecter. Acceptez-vous de l'ajouter à votre liste d'appareils connus ?",
|
||||
"Device Certificate": "Certificat de l'appareil",
|
||||
"Device Group": "Groupe d'appareils",
|
||||
"Device ID": "ID de l'appareil",
|
||||
"Device Identification": "Identifiant de l'appareil",
|
||||
"Device Name": "Nom convivial local de l'appareil",
|
||||
@@ -147,7 +149,7 @@
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Nombre positif (p.ex, \"2.35\") et unité. Pourcentage de l'espace disque total.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Entrez un numéro de port non-privilégié (1024 - 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Entrer les adresses (\"tcp://ip:port\" ou \"tcp://hôte:port\") séparées par une virgule, ou \"dynamic\" afin d'activer la recherche automatique de l'adresse.",
|
||||
"Enter ignore patterns, one per line.": "Entrez les masques d'exclusion, un par ligne.",
|
||||
"Enter ignore patterns, one per line.": "Entrez les modèles d'exclusion, un par ligne.",
|
||||
"Enter up to three octal digits.": "Entrez jusqu'à 3 chiffres octaux.",
|
||||
"Error": "Erreur",
|
||||
"Extended Attributes": "Attributs étendus",
|
||||
@@ -156,7 +158,7 @@
|
||||
"External File Versioning": "Gestion externe des versions de fichiers",
|
||||
"Failed Items": "Éléments en échec",
|
||||
"Failed to load file versions.": "Échec de chargement des versions de fichiers.",
|
||||
"Failed to load ignore patterns.": "Échec du chargement des masques d'exclusions.",
|
||||
"Failed to load ignore patterns.": "Échec du chargement des modèles d'exclusions.",
|
||||
"Failed to set up, retrying": "Échec, nouvel essai",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "La connexion aux serveurs en IPv6 va échouer s'il n'y a pas de connectivité IPv6.",
|
||||
"File Pull Order": "Ordre de récupération des fichiers",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Filtrer par date",
|
||||
"Filter by name": "Filtrer par nom",
|
||||
"Folder": "Partage",
|
||||
"Folder Group": "Groupe de partages",
|
||||
"Folder ID": "ID du partage",
|
||||
"Folder Label": "Nom du partage",
|
||||
"Folder Path": "Chemin racine du partage",
|
||||
@@ -203,7 +206,7 @@
|
||||
"Ignore": "Refuser",
|
||||
"Ignore Patterns": "Exclusions",
|
||||
"Ignore Permissions": "Ignorer les permissions",
|
||||
"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.": "L'ajout de masques d'exclusion ne peut se faire qu'après la création du partage. En cochant cette case il vous sera proposé de saisir (ou si vous avez déjà défini des valeurs par défaut, de compléter) une liste d'exclusions après l'enregistrement.",
|
||||
"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.": "L'ajout de modèles d'exclusion ne peut se faire qu'après la création du partage. En cochant cette case il vous sera proposé de saisir (ou si vous avez déjà défini des valeurs par défaut, de compléter) une liste d'exclusions après l'enregistrement.",
|
||||
"Ignored Devices": "Appareils refusés",
|
||||
"Ignored Folders": "Partages refusés",
|
||||
"Ignored at": "Refusé le",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Le défilement du journal est en pause. Faites défiler jusqu'en bas pour continuer.",
|
||||
"Login failed, see Syncthing logs for details.": "Échec de connexion, consultez les journaux de Syncthing pour les détails.",
|
||||
"Logs": "Journaux",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Entretient un index de tous les blocs du répertoire pour permettre leur réutilisation depuis d'autres fichiers lors de changements dans la synchronisation. Désactivez pour diminuer la taille de la base de donnée, au prix de téléchargements potentiellement plus long.",
|
||||
"Major Upgrade": "Mise à jour majeure",
|
||||
"Mass actions": "Actions multiples",
|
||||
"Maximum Age": "Ancienneté maximum",
|
||||
@@ -278,7 +282,9 @@
|
||||
"OK": "OK",
|
||||
"Off": "Désactivée",
|
||||
"Oldest First": "Les plus anciens en premier",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Nom local, convivial et optionnel du partage, à votre guise. Il peut être différent sur chaque appareil. Par notification initiale, il sera proposé tel quel aux nouveaux participants.\nAstuce : comme il est modifiable ultérieurement, soyez prévenant en indiquant un nom éventuellement plus parlant pour les invités, puis renommez-le pour votre propre usage quand ils l'auront accepté (exemple d'un partage à deux membres où l'initiateur commence par donner son propre nom au partage pour l'invitation, puis le renomme plus tard au nom du partenaire quand celui-ci l'a accepté - Pensez, pour vous, au chemin ~définitif~ du répertoire, modifiable dans \"Chemin racine...\" ci-dessous à la création). Évitez les erreurs d'orthographe dans le nom car il servira aussi suffixe au chemin proposé en création (local et distant) et ce chemin, côté local, n'est modifiable à posteriori que dans la configuration avancée.",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Nom local, convivial et optionnel du partage, à notre guise. Il peut être différent sur chaque appareil. Par une notification initiale d'invitation, il sera proposé tel quel aux nouveaux participants choisis dans l'onglet suivant (Liaisons).\nAstuce : comme il est modifiable ultérieurement, soyons prévenant en indiquant un nom bien parlant pour nos invités, puis quand ils l'auront accepté, renommons-le si besoin pour nous-même (exemple : je crée un partage personnel prévu pour deux membres seulement où je commence par donner mon propre nom au partage pour l'invitation, puis je le renomme plus tard au nom du partenaire quand celui-ci l'a accepté). Réfléchissons bien, dans notre intérêt, au chemin ~définitif~ du répertoire, automatiquement repris d'ici mais modifiable dans \"Chemin racine du partage\" ci-dessous lors de la création). Évitons les erreurs d'orthographe et de casse dans le nom car il servira de suffixe au chemin proposé en création (local et distant) et ce chemin, côté local, n'est modifiable à posteriori que via la configuration avancée.",
|
||||
"Optional group for the device. Can be different on each device.": "Groupe optionnel de cet appareil pour le listage en chapitres dans l'écran principal. Il peut être différent sur chaque appareil.",
|
||||
"Optional group for the folder. Can be different on each device.": "Groupe optionnel de ce partage pour le listage en chapitres dans l'écran principal. Il peut être différent sur chaque appareil.",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Désynchronisé",
|
||||
"Out of Sync Items": "Éléments non synchronisés",
|
||||
@@ -288,7 +294,7 @@
|
||||
"Ownership": "Propriétaire",
|
||||
"Password": "Mot de passe",
|
||||
"Path": "Chemin",
|
||||
"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": "En création/acceptation manuelle d'un nouveau partage, c'est le chemin complet du répertoire à partager dans l'appareil local. Il peut être différent du \"Nom du partage\" (voir l'astuce dans l'aide correspondante ci-dessus). Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable).\n\nDans l'écran de définition des valeurs par défaut \"Préférences pour les créations\"/\"Pour les nouveaux partages\" (menu Actions/Configuration), ce champ indique le chemin de base dans lequel seront créés les répertoires racine des partages que vous créez ou auxquels vous acceptez de participer. Le caractère tilde (~) est un raccourci pour votre répertoire personnel",
|
||||
"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": "En création/acceptation manuelle d'un nouveau partage, c'est le chemin complet du répertoire à partager dans l'appareil local. Il peut être différent du \"Nom du partage\" (voir l'astuce dans l'aide correspondante ci-dessus). Il sera créé s'il n'existe pas. Nous pouvons entrer un chemin absolu (p.ex. \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable).\n\nDans l'écran de définition des valeurs par défaut \"Préférences pour les créations\"/\"Pour les nouveaux partages\" (menu Actions/Configuration), ce champ indique le chemin de base dans lequel seront créés les répertoires racine des partages que nous créerons ultérieurement ou auxquels nous accepterons de participer. Le caractère tilde (~) est un raccourci signifiant \"mon répertoire personnel\"",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Chemin où les versions seront conservées (laisser vide pour utiliser le dossier par défaut .stversions dans le partage).",
|
||||
"Paths": "Chemins",
|
||||
"Pause": "Pause",
|
||||
@@ -311,13 +317,13 @@
|
||||
"QR code": "Code QR",
|
||||
"QUIC LAN": "LAN QUIC",
|
||||
"QUIC WAN": "WAN QUIC",
|
||||
"Quick guide to supported patterns": "Guide rapide des masques compatibles ci-dessous",
|
||||
"Quick guide to supported patterns": "Guide rapide des modèles compatibles ci-dessous",
|
||||
"Random": "Aléatoire",
|
||||
"Receive Encrypted": "Réception chiffrée",
|
||||
"Receive Only": "Réception seulement",
|
||||
"Received data is already encrypted": "Les données reçues sont déjà chiffrées",
|
||||
"Recent Changes": "Changements récents",
|
||||
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
|
||||
"Reduced by ignore patterns": "(Limité par des modèles d'exclusion)",
|
||||
"Relay LAN": "Relais LAN",
|
||||
"Relay WAN": "Relais WAN",
|
||||
"Release Notes": "Notes de version",
|
||||
@@ -327,7 +333,7 @@
|
||||
"Remove": "Enlever",
|
||||
"Remove Device": "Supprimer l'appareil",
|
||||
"Remove Folder": "Supprimer le partage",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur tous les appareils concernés (généré aléatoirement, mais modifiable à la création, par exemple pour faire entrer un appareil dans un partage pré-existant actuellement non connecté mais dont on connaît déjà l'ID, ou s'il n'y a personne à l'autre bout pour vous inviter à y participer).",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur tous les appareils concernés (généré aléatoirement, c'est pratique, mais il modifiable à notre gré à la création, par exemple pour faire entrer cet appareil dans un partage pré-existant actuellement non connecté mais dont on connaîtrait déjà l'identifiant, ou s'il n'y a personne à l'autre bout pour nous y inviter).",
|
||||
"Rescan": "Réanalyser",
|
||||
"Rescan All": "Tout réanalyser",
|
||||
"Rescans": "Réanalyses/Surveillance",
|
||||
@@ -348,7 +354,7 @@
|
||||
"See external versioning help for supported templated command line parameters.": "Consulter l'aide de la gestion externe des versions pour les paramètres supportés de modèles de lignes de commande.",
|
||||
"Select All": "Tout sélectionner",
|
||||
"Select a version": "Choisissez une version",
|
||||
"Select additional devices to share this folder with.": "Sélectionnez des appareils membres supplémentaires pour ce partage.",
|
||||
"Select additional devices to share this folder with.": "Invitez des appareils membres supplémentaires pour ce partage.",
|
||||
"Select additional folders to share with this device.": "Choisissez des partages supplémentaires pour cet appareil.",
|
||||
"Select latest version": "Restaurer la dernière version",
|
||||
"Select oldest version": "Restaurer la plus ancienne version",
|
||||
@@ -371,8 +377,8 @@
|
||||
"Show detailed discovery status": "Afficher l'état détaillé de découverte",
|
||||
"Show detailed listener status": "Afficher l'état détaillé de l'écouteur",
|
||||
"Show diff with previous version": "Afficher les différences avec la version précédente",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Sera diffusé aux autres appareils comme nom convivial optionnel par défaut.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Nom convivial local affiché à la place de l'ID de l'appareil dans la plupart des écrans. Si laissé vide, c'est le nom convivial local de l'appareil distant qui sera utilisé dès que celui-ci sera en ligne. (Modifiable ultérieurement).",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de l'appareil dans l'état de la grappe. Sera diffusé aux autres appareils comme nom convivial optionnel par défaut.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Nom convivial local affiché à la place de l'ID de l'appareil dans la plupart des écrans. Si laissé vide, il sera peuplé par le nom convivial choisi par le propriétaire de l'appareil distant dès que celui-ci sera en ligne. (Modifiable ultérieurement).",
|
||||
"Shut Down": "Arrêter",
|
||||
"Shutdown Complete": "Arrêt complet",
|
||||
"Simple": "Suivi simplifié",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Versions échelonnées",
|
||||
"Staggered File Versioning": "Versions échelonnées des fichiers",
|
||||
"Start Browser": "Lancer le navigateur web",
|
||||
"Starting": "Démarrage en cours",
|
||||
"Statistics": "Statistiques",
|
||||
"Stay logged in": "Rester connecté",
|
||||
"Stopped": "Arrêté",
|
||||
@@ -517,7 +524,7 @@
|
||||
"Watch for Changes": "Écouter les changements",
|
||||
"Watching for Changes": "Surveillance des changements",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "La surveillance des changements découvre la plupart des changements sans réanalyses périodiques.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lorsque vous ajoutez un appareil, gardez à l'esprit que le votre doit aussi être ajouté de l'autre coté.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lorsque vous ajoutez un appareil, vous lui envoyez une invitation. Gardez à l'esprit qu'elle doit être acceptée de l'autre coté, soyez patient.",
|
||||
"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.": "Lorsqu'un nouveau partage est ajouté, gardez à l'esprit que c'est cet ID qui est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur tous les appareils participant à ce partage.",
|
||||
"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.": "Au-delà de un sur chaque appareil, Syncthing tentera d'établir autant de connexions simultanées. Si les valeurs diffèrent, la plus grande sera utilisée. Laissez à zéro pour laisser Syncthing décider.",
|
||||
"Yes": "Oui",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Cruthaigh nó comhroinn fillteáin go huathoibríoch a fhógraíonn an gléas seo ag an gcosán réamhshocraithe.",
|
||||
"Available debug logging facilities:": "Áiseanna logála dífhabhtaithe atá ar fáil:",
|
||||
"Be careful!": "Bí cúramach!",
|
||||
"Block Indexing": "Innéacsú Bloc",
|
||||
"Body:": "Comhlacht:",
|
||||
"Bugs": "Fabhtanna",
|
||||
"Cancel": "Cuir ar ceal",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Gléas",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) ag iarraidh ceangal. Cuir gléas nua leis?",
|
||||
"Device Certificate": "Teastas Gléis",
|
||||
"Device Group": "Grúpa Gléasanna",
|
||||
"Device ID": "Aitheantas gléis",
|
||||
"Device Identification": "Aitheantas gléis",
|
||||
"Device Name": "Ainm an Ghléis",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Scag de réir dáta",
|
||||
"Filter by name": "Scag de réir ainm",
|
||||
"Folder": "Fillteán",
|
||||
"Folder Group": "Grúpa Fillteán",
|
||||
"Folder ID": "Aitheantas an fhillteáin",
|
||||
"Folder Label": "Lipéad Fillteáin",
|
||||
"Folder Path": "Conair an Fhillteáin",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Bhí eireaball loga ar sos. Scrollaigh go bun an leathanaigh chun leanúint ar aghaidh.",
|
||||
"Login failed, see Syncthing logs for details.": "Theip ar logáil isteach, féach logaí sioncronaithe le haghaidh sonraí.",
|
||||
"Logs": "Logchomhaid",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Coinnigh innéacs de na bloic uile sa fhillteán, rud a chuireann ar chumas athúsáid bloic ó chomhaid eile agus athruithe á sioncrónú. Díchumasaigh chun méid an bhunachair shonraí a laghdú ar chostas gan a bheith in ann bloic a athúsáid trasna comhad.",
|
||||
"Major Upgrade": "Uasghrádú Mór",
|
||||
"Mass actions": "Gníomhartha Aifrinn",
|
||||
"Maximum Age": "Aois Uasta",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "As",
|
||||
"Oldest First": "An Chéad duine is sine",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Lipéad tuairisciúil roghnach don fhillteán. Is féidir a bheith difriúil ar gach gléas.",
|
||||
"Optional group for the device. Can be different on each device.": "Grúpa roghnach don fheiste. Féadfaidh sé a bheith difriúil ar gach feiste.",
|
||||
"Optional group for the folder. Can be different on each device.": "Grúpa roghnach don fhillteán. Féadfaidh sé a bheith difriúil ar gach gléas.",
|
||||
"Options": "Roghanna",
|
||||
"Out of Sync": "As Sioncronú",
|
||||
"Out of Sync Items": "As Míreanna Sioncronaithe",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Tuislithe",
|
||||
"Staggered File Versioning": "Leagan Comhad Staggered",
|
||||
"Start Browser": "Tosaigh Brabhsálaí",
|
||||
"Starting": "Ag tosú",
|
||||
"Statistics": "Staitisticí",
|
||||
"Stay logged in": "Fan logáilte isteach",
|
||||
"Stopped": "Stoptha",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatski stvorite ili dijelite mape koje ovaj uređaj oglašava na zadanoj stazi.",
|
||||
"Available debug logging facilities:": "Dostupni alati za bilježenje grešaka:",
|
||||
"Be careful!": "Pazi!",
|
||||
"Block Indexing": "Indeksiranje blokova",
|
||||
"Body:": "Sadržaj:",
|
||||
"Bugs": "Greške",
|
||||
"Cancel": "Odustani",
|
||||
@@ -98,6 +99,7 @@
|
||||
"Device": "Uređaj",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Uređaj „{{name}}“ ({{device}} na {{address}}) se želi povezati. Dodati novi uređaj?",
|
||||
"Device Certificate": "Certifikat uređaja",
|
||||
"Device Group": "Grupa uređaja",
|
||||
"Device ID": "ID uređaja",
|
||||
"Device Identification": "Identifikacija uređaja",
|
||||
"Device Name": "Ime uređaja",
|
||||
@@ -168,6 +170,7 @@
|
||||
"Filter by date": "Filtriraj po datumu",
|
||||
"Filter by name": "Filtriraj po imenu",
|
||||
"Folder": "Mapa",
|
||||
"Folder Group": "Grupa mapa",
|
||||
"Folder ID": "ID mape",
|
||||
"Folder Label": "Oznaka mape",
|
||||
"Folder Path": "Staza mape",
|
||||
@@ -275,6 +278,8 @@
|
||||
"Off": "Isključeno",
|
||||
"Oldest First": "Najprije najstarije",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Neobavezna opisna etiketa za mapu. Može biti različita na svakom uređaju.",
|
||||
"Optional group for the device. Can be different on each device.": "Opcionalna grupa za uređaj. Može biti drugačija na svakom uređaju.",
|
||||
"Optional group for the folder. Can be different on each device.": "Opcionalna grupa za mapu. Može biti drugačija na svakom uređaju.",
|
||||
"Options": "Opcije",
|
||||
"Out of Sync": "Nesinkronizirano",
|
||||
"Out of Sync Items": "Nesinkronizirane stavke",
|
||||
@@ -386,6 +391,7 @@
|
||||
"Staggered": "U fazama",
|
||||
"Staggered File Versioning": "Upravljanje verzijama datoteka u fazama",
|
||||
"Start Browser": "Pokreni preglednik",
|
||||
"Starting": "Pokretanje",
|
||||
"Statistics": "Statistike",
|
||||
"Stay logged in": "Ostanite prijavljeni",
|
||||
"Stopped": "Zaustavljeno",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Jaringan Terizinkan",
|
||||
"Alphabetic": "Alfabet",
|
||||
"Altered by ignoring deletes.": "Diubah dengan mengabaikan penghapusan.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Selalu aktif ketika tipe folder adalah \"{{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.": "Perintah eksternal menangani pemversian. Ia harus menghapus berkas dari folder yang dibagi. Jika lokasi aplikasi terdapat spasi, itu harus dikutip.",
|
||||
"Anonymous Usage Reporting": "Pelaporan Penggunaan Anonim",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Format pelaporan penggunaan anonim telah berubah. Maukah anda pindah menggunakan format yang baru?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Badan:",
|
||||
"Bugs": "Bugs",
|
||||
"Cancel": "Batal",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Tidak dapat diaktifkan ketika tipe folder adalah \"{{foldertype}}\".",
|
||||
"Changelog": "Log Perubahan",
|
||||
"Clean out after": "Bersihkan setelah",
|
||||
"Cleaning Versions": "Versi Pembersihan",
|
||||
@@ -80,6 +82,7 @@
|
||||
"Custom Range": "Rentang Kustom",
|
||||
"Danger!": "Bahaya!",
|
||||
"Database Location": "Lokasi Database",
|
||||
"Debug": "Debug",
|
||||
"Debugging Facilities": "Fasilitas Debug",
|
||||
"Default": "Default",
|
||||
"Default Configuration": "Konfigurasi Bawaan",
|
||||
@@ -139,12 +142,17 @@
|
||||
"Enabled": "Aktif",
|
||||
"Enables sending extended attributes to other devices, and applying incoming extended attributes. May require running with elevated privileges.": "Mengaktifkan pengiriman atribut tambahan ke perangkat lain, serta penerapan atribut tambahan yang diterima. Mungkin memerlukan eksekusi dengan hak akses tingkat tinggi.",
|
||||
"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.": "Mengaktifkan pengiriman atribut tambahan ke perangkat lain, tetapi tidak menerapkan atribut tambahan yang diterima. Hal ini dapat berdampak signifikan terhadap kinerja. Selalu aktif ketika “Sync Extended Attributes” diaktifkan.",
|
||||
"Enables sending ownership information to other devices, and applying incoming ownership information. Typically requires running with elevated privileges.": "Mengaktifkan pengiriman informasi kepemilikan ke perangkat lain, dan menerapkan informasi kepemilikan yang masuk. Biasanya memerlukan menjalankan dengan hak istimewa yang ditinggikan.",
|
||||
"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.": "Mengaktifkan pengiriman informasi kepemilikan ke perangkat lain, tetapi tidak menerapkan informasi kepemilikan yang masuk. Ini dapat berdampak signifikan pada kinerja. Selalu diaktifkan ketika \"Sinkronisasi Kepemilikan\" diaktifkan.",
|
||||
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Masukkan nomor yang bukan negatif (contoh \"2.35\") dan pilih sebuah unit. Persentase adalah bagian dari total ukuran penyimpanan.",
|
||||
"Enter a non-privileged port number (1024 - 65535).": "Masukkan nomor port yang tidak \"istimewa\" (1024 - 65535).",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Masukkan alamat yang dipisah oleh koma (\"tcp://ip:port\", \"tcp://host:port\") atau \"dynamic\" untuk melakukan penemuan alamat secara otomatis.",
|
||||
"Enter ignore patterns, one per line.": "Masukkan pola pengabaian, satu per baris.",
|
||||
"Enter up to three octal digits.": "Masukkan hingga tiga digit oktal.",
|
||||
"Error": "Galat",
|
||||
"Extended Attributes": "Atribut yang Diperluas",
|
||||
"Extended Attributes Filter": "Filter Atribut yang Diperluas",
|
||||
"External": "Eksternal",
|
||||
"External File Versioning": "Pemversian Berkas Eksternal",
|
||||
"Failed Items": "Berkas yang gagal",
|
||||
"Failed to load file versions.": "Gagal memuat versi berkas.",
|
||||
@@ -160,20 +168,25 @@
|
||||
"Filesystem Watcher Errors": "Galat Filesystem Watcher",
|
||||
"Filter by date": "Saring menurut tanggal",
|
||||
"Filter by name": "Saring menurut nama",
|
||||
"Folder": "Folder",
|
||||
"Folder ID": "ID Folder",
|
||||
"Folder Label": "Label Folder",
|
||||
"Folder Path": "Lokasi Folder",
|
||||
"Folder Status": "Status Folder",
|
||||
"Folder Type": "Tipe Folder",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder tipe \"{{receiveEncrypted}}\" hanya bisa diatur ketika menambah folder baru.",
|
||||
"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.": "Folder tipe \"{{receiveEncrypted}}\" tidak dapat diubah setelah menambah folder. Anda perlu menghapus folder tersebut, hapus atau dekripsi data dalam penyimpanan, dan menambah folder kembali.",
|
||||
"Folders": "Folder",
|
||||
"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.": "Untuk folder berikut sebuah galat terjadi saat memulai fitur lihat perubahan. Ia akan dicoba setiap menit, maka galat mungkin hilang sebentar lagi. Jika mereka berthan, coba perbaiki masalah yang mendasar dan meminta bantuan jika anda tidak bisa.",
|
||||
"Forever": "Selamanya",
|
||||
"Full Rescan Interval (s)": "Interval Pindai Ulang Penuh (detik)",
|
||||
"GUI": "GUI",
|
||||
"GUI / API HTTPS Certificate": "Sertifikat HTTPS GUI / API",
|
||||
"GUI Authentication Password": "Sandi Otentikasi GUI",
|
||||
"GUI Authentication User": "Pengguna Otentikasi GUI",
|
||||
"GUI Authentication: Set User and Password": "Otentikasi GUI: Atur Pengguna dan Sandi",
|
||||
"GUI Listen Address": "Alamat Pendengaran GUI",
|
||||
"GUI Override Directory": "Direktori Penimpaan GUI",
|
||||
"GUI Theme": "Tema GUI",
|
||||
"General": "Umum",
|
||||
"Generate": "Generasi",
|
||||
@@ -181,6 +194,7 @@
|
||||
"Global Discovery Servers": "Server Penemuan Global",
|
||||
"Global State": "Status Global",
|
||||
"Help": "Panduan",
|
||||
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Petunjuk: hanya aturan-tolak yang terdeteksi sementara defaultnya adalah tolak. Pertimbangkan untuk menambahkan \"izinkan semua\" sebagai aturan terakhir.",
|
||||
"Home page": "Situs",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Namun, pengaturan anda sekarang mengindikasi anda mungkin tidak ingin fitur diaktifkan. Kami telah menonaktifkan pelaporan crash otomatis untuk anda.",
|
||||
"Identification": "Identifikasi",
|
||||
@@ -193,10 +207,15 @@
|
||||
"Ignored Devices": "Perangkat yang Diabaikan",
|
||||
"Ignored Folders": "Folder yang Diabaikan",
|
||||
"Ignored at": "Diabaikan di",
|
||||
"Included Software": "Perangkat Lunak yang Disertakan",
|
||||
"Incoming Rate Limit (KiB/s)": "Batas Kecepatan Unduh (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Konfigurasi salah dapat merusak isi folder dan membuat Syncthing tidak dapat dijalankan.",
|
||||
"Incorrect user name or password.": "Nama pengguna atau kata sandi salah.",
|
||||
"Info": "Info",
|
||||
"Internally used paths:": "Jalur yang digunakan secara internal:",
|
||||
"Introduced By": "Dikenalkan Oleh",
|
||||
"Introducer": "Pengenal",
|
||||
"Introduction": "Pendahuluan",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Inversi dari kondisi yang diberikan (yakni jangan dikecualikan)",
|
||||
"Keep Versions": "Jumlah Versi yang Disimpan",
|
||||
"LDAP": "LDAP",
|
||||
@@ -208,7 +227,9 @@
|
||||
"Last seen": "Terakhir dilihat",
|
||||
"Latest Change": "Perubahan Terbaru",
|
||||
"Learn more": "Pelajari lebih lanjut",
|
||||
"Learn more at {%url%}": "Pelajari lebih lanjut di {{url}}",
|
||||
"Limit": "Batas",
|
||||
"Limit Bandwidth in LAN": "Batasi Bandwidth di LAN",
|
||||
"Listener Failures": "Kegagalan Pendengar",
|
||||
"Listener Status": "Status Pendengar",
|
||||
"Listeners": "Pendengar",
|
||||
@@ -220,14 +241,26 @@
|
||||
"Local State (Total)": "Keadaan Lokal (Total)",
|
||||
"Locally Changed Items": "Berkas yang Diubah Secara Lokal",
|
||||
"Log": "Log",
|
||||
"Log File": "File Log",
|
||||
"Log In": "Masuk",
|
||||
"Log Out": "Keluar",
|
||||
"Log in to see paths information.": "Masuk untuk melihat informasi jalur.",
|
||||
"Log in to see version information.": "Masuk untuk melihat informasi versi.",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Pembuntutan log diberhentikan sementara. Gulir ke bawah untuk melanjutkan.",
|
||||
"Login failed, see Syncthing logs for details.": "Login gagal, lihat log Syncthing untuk detailnya.",
|
||||
"Logs": "Log",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Mass actions": "Aksi massal",
|
||||
"Maximum Age": "Usia Maksimum",
|
||||
"Maximum single entry size": "Ukuran entri tunggal maksimum",
|
||||
"Maximum total size": "Ukuran total maksimum",
|
||||
"Metadata Only": "Hanya Metadata",
|
||||
"Minimum Free Disk Space": "Ruang Penyimpanan Kosong Minimum",
|
||||
"Mod. Device": "Perangkat Pengubah",
|
||||
"Mod. Time": "Waktu Diubah",
|
||||
"More than a month ago": "Lebih dari sebulan yang lalu",
|
||||
"More than a week ago": "Lebih dari seminggu yang lalu",
|
||||
"More than a year ago": "Lebih dari setahun yang lalu",
|
||||
"Move to top of queue": "Pindah ke daftar tunggu teratas",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Wildcard multi tingkat (cocok dalam tingkat direktori apapun)",
|
||||
"Never": "Tidak Pernah",
|
||||
@@ -237,9 +270,11 @@
|
||||
"No": "Tidak",
|
||||
"No File Versioning": "Tidak Ada Pemversian Berkas",
|
||||
"No files will be deleted as a result of this operation.": "Tidak ada berkas yang akan dihapus akibat dari operasi ini.",
|
||||
"No rules set": "Tidak ada aturan yang ditetapkan",
|
||||
"No upgrades": "Tidak ada peningkatan",
|
||||
"Not shared": "Tidak terbagi",
|
||||
"Notice": "Pemberitahuan",
|
||||
"Number of Connections": "Jumlah Koneksi",
|
||||
"OK": "OK",
|
||||
"Off": "Mati",
|
||||
"Oldest First": "Tertua Dahulu",
|
||||
@@ -250,9 +285,12 @@
|
||||
"Outgoing Rate Limit (KiB/s)": "Batas Kecepatan Unggah (KiB/s)",
|
||||
"Override": "Timpa",
|
||||
"Override Changes": "Timpa Perubahan",
|
||||
"Ownership": "Kepemilikan",
|
||||
"Password": "Kata Sandi",
|
||||
"Path": "Lokasi",
|
||||
"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": "Lokasi folder dalam komputer lokal. Akan dibuat jika tidak ada. Karakter tilde (~) dapat digunakan sebagai pintasan untuk",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Lokasi berbagai versi berkas disimpan (tinggalkan kosong untuk menggunakan direktori .stversions bawaan dalam folder).",
|
||||
"Paths": "Jalur",
|
||||
"Pause": "Jeda",
|
||||
"Pause All": "Jeda Semua",
|
||||
"Paused": "Terjeda",
|
||||
@@ -270,6 +308,9 @@
|
||||
"Preparing to Sync": "Bersiap untuk Mensinkron",
|
||||
"Preview": "Pratinjau",
|
||||
"Preview Usage Report": "Laporan Pratinjau Penggunaan",
|
||||
"QR code": "Kode QR",
|
||||
"QUIC LAN": "QUIC LAN",
|
||||
"QUIC WAN": "QUIC WAN",
|
||||
"Quick guide to supported patterns": "Panduan cepat untuk pola yang didukung",
|
||||
"Random": "Acak",
|
||||
"Receive Encrypted": "Terima Dienkripsi",
|
||||
@@ -277,6 +318,8 @@
|
||||
"Received data is already encrypted": "Data yang diterima telah dienkripsi",
|
||||
"Recent Changes": "Perubahan Terkini",
|
||||
"Reduced by ignore patterns": "Dikurangi oleh pola pengabaian",
|
||||
"Relay LAN": "Relay LAN",
|
||||
"Relay WAN": "Relay WAN",
|
||||
"Release Notes": "Catatan Rilis",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Rilis kandidat memiliki fitur terbaru dan perbaikan. Mereka mirip dengan rilis tradisional Syncthing.",
|
||||
"Remote Devices": "Perangkat Jarak Jauh",
|
||||
@@ -299,6 +342,7 @@
|
||||
"Revert": "Kembalikan",
|
||||
"Revert Local Changes": "Kembalikan Perubahan Lokal",
|
||||
"Save": "Simpan",
|
||||
"Saving changes": "Menyimpan perubahan",
|
||||
"Scan Time Remaining": "Waktu Pindai Tersisa",
|
||||
"Scanning": "Pemindaian",
|
||||
"See external versioning help for supported templated command line parameters.": "Lihat panduan pemversian eksternal untuk contoh parameter baris perintah yang didukung.",
|
||||
@@ -309,11 +353,15 @@
|
||||
"Select latest version": "Pilih versi terbaru",
|
||||
"Select oldest version": "Pilih versi tertua",
|
||||
"Send & Receive": "Kirim & Terima",
|
||||
"Send Extended Attributes": "Kirim Atribut yang Diperluas",
|
||||
"Send Only": "Hanya Kirim",
|
||||
"Send Ownership": "Kirim Kepemilikan",
|
||||
"Set Ignores on Added Folder": "Atur Pengabaian dalam Folder yang Ditambahkan",
|
||||
"Settings": "Pengaturan",
|
||||
"Share": "Bagi",
|
||||
"Share Folder": "Bagi Folder",
|
||||
"Share by Email": "Bagikan melalui Email",
|
||||
"Share by SMS": "Bagikan melalui SMS",
|
||||
"Share this folder?": "Bagi Folder Ini?",
|
||||
"Shared Folders": "Folder Yang Dibagi",
|
||||
"Shared With": "Dibagi Dengan",
|
||||
@@ -327,6 +375,7 @@
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Ditampilkan sebagai ganti ID Perangkat dalam status gugus. Akan diubah menjadi nama yang perangkat umumkan jika tidak diisi.",
|
||||
"Shut Down": "Matikan",
|
||||
"Shutdown Complete": "Pematian Selesai",
|
||||
"Simple": "Sederhana",
|
||||
"Simple File Versioning": "Pemversian Berkas Sederhana",
|
||||
"Single level wildcard (matches within a directory only)": "Wildcard tingkat tunggal (cocok hanya dalam satu direktori saja)",
|
||||
"Size": "Ukuran",
|
||||
@@ -338,25 +387,36 @@
|
||||
"Stable releases and release candidates": "Rilis stabil dan rilis kandidat",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Rilis stabil tertunda sebanyak dua minggu. Selama masa ini, mereka dalam masa pengujian sebagai rilis kandidat.",
|
||||
"Stable releases only": "Hanya rilis stabil",
|
||||
"Staggered": "Bertahap",
|
||||
"Staggered File Versioning": "Pemversian Berkas Bergiliran",
|
||||
"Start Browser": "Mulai Peramban",
|
||||
"Statistics": "Statistik",
|
||||
"Stay logged in": "Tetap masuk",
|
||||
"Stopped": "Telah Berhenti",
|
||||
"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.": "Hanya menyimpan dan mensinkronisasi data yang dienkripsi. Folder dalam semua perangkat yang tersambung perlu diatur dengan sandi yang sama atau tipe \"{{receiveEncrypted}}\" juga.",
|
||||
"Subject:": "Subjek:",
|
||||
"Support": "Bantuan",
|
||||
"Support Bundle": "Paket Bantuan",
|
||||
"Sync Extended Attributes": "Sinkronisasi Atribut yang Diperluas",
|
||||
"Sync Ownership": "Sinkronisasi Kepemilikan",
|
||||
"Sync Protocol Listen Addresses": "Alamat Pendengar Protokol Sinkronisasi",
|
||||
"Sync Status": "Status Sinkronisasi",
|
||||
"Syncing": "Sinkronisasi",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "ID perangkat Syncthing untuk \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Syncthing telah dimatikan.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing adalah Software Gratis dan Open Source dengan lisensi 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 adalah program sinkronisasi file berkelanjutan. Program ini menyinkronkan file antara dua komputer atau lebih secara real time, terlindungi dengan aman dari mata-mata. Data Anda adalah milik Anda sendiri dan Anda berhak memilih di mana data tersebut disimpan, apakah dibagikan ke pihak ketiga, dan bagaimana data tersebut ditransmisikan melalui internet.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing sedang mendengar di alamat jaringan berikut untuk percobaan koneksi dari perangkat lain:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing sedang tidak mendengar dari percobaan koneksi dari perangkat lain dari semua alamat. Hanya koneksi keluar dari perangkat ini mungkin berhasil.",
|
||||
"Syncthing is restarting.": "Syncthing sedang memulai ulang.",
|
||||
"Syncthing is saving changes.": "Syncthing sedang menyimpan perubahan.",
|
||||
"Syncthing is upgrading.": "Syncthing sedang meningkatkan.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing sekarang mendukung melaporkan crash secara otomatis ke pengembang. Fitur ini diaktifkan secara bawaan.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.",
|
||||
"TCP LAN": "TCP LAN",
|
||||
"TCP WAN": "TCP WAN",
|
||||
"Take me back": "Bawa saya kembali",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Alamat GUI diambil alih oleh parameter memulai Syncthing. Perubahan disini tidak akan memiliki efek saat pengambilan alih aktif.",
|
||||
"The Syncthing Authors": "Pencipta-pencipta Syncthing",
|
||||
@@ -377,17 +437,22 @@
|
||||
"The following items could not be synchronized.": "Berkas berikut tidak dapat disinkron.",
|
||||
"The following items were changed locally.": "Berkas berikut telah diubah secara lokal.",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Metode berikut digunakan untuk menemukan perangkat lain di jaringan dan mengumumkan perangkat ini untuk dapat ditemukan oleh perangkat lain:",
|
||||
"The following text will automatically be inserted into a new message.": "Teks berikut akan secara otomatis disisipkan ke dalam pesan baru.",
|
||||
"The following unexpected items were found.": "Berkas tidak terduga berikut telah ditemukan.",
|
||||
"The interval must be a positive number of seconds.": "Interval harus berupa angka detik positif.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Interval, dalam detik, untuk melakukan pembersihan dalam direktori versi. Nol untuk menonaktifkan pembersihan periodik.",
|
||||
"The maximum age must be a number and cannot be blank.": "Usia maksimum harus berupa angka dan tidak dapat kosong.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Waktu maksimum untuk menyimpan sebuah versi (dalam hari, atur ke 0 untuk menyimpan versi selamanya).",
|
||||
"The number of connections must be a non-negative number.": "Jumlah koneksi harus berupa angka tidak negatif.",
|
||||
"The number of days must be a number and cannot be blank.": "Jumlah hari harus berupa angka dan tidak dapat kosong.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "Jumlah hari untuk menyimpan berkas dalam tempat sampah. Nol berarti selamanya.",
|
||||
"The number of old versions to keep, per file.": "Jumlah versi lama untuk disimpan, setiap berkas.",
|
||||
"The number of versions must be a number and cannot be blank.": "Jumlah versi harus berupa angka dan tidak dapat kosong.",
|
||||
"The path cannot be blank.": "Lokasi tidak dapat kosong.",
|
||||
"The rate limit is applied to the accumulated traffic of all connections to this device.": "Batas kecepatan diterapkan pada akumulasi lalu lintas dari semua koneksi ke perangkat ini.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Pembatasan kecepatan harus berupa angka positif (0: tidak terbatas)",
|
||||
"The remote device has not accepted sharing this folder.": "Perangkat jarak jauh belum menerima berbagi folder ini.",
|
||||
"The remote device has paused this folder.": "Perangkat jarak jauh telah menjeda folder ini.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Interval pemindaian ulang harus berupa angka positif.",
|
||||
"There are no devices to share this folder with.": "Tidak ada perangkat untuk membagikan folder ini.",
|
||||
"There are no file versions to restore.": "Tidak ada versi berkas untuk dipulihkan.",
|
||||
@@ -401,7 +466,10 @@
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Pengaturan ini mengontrol jumlah penyimpanan kosong yang dibutuhkan dalam penyimpanan utama (contoh database indeks).",
|
||||
"Time": "Waktu",
|
||||
"Time the item was last modified": "Waktu file terakhir dimodifikasi",
|
||||
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "Untuk terhubung dengan perangkat Syncthing bernama \"{{devicename}}\", tambahkan perangkat jarak jauh baru di pihak Anda dengan ID ini:",
|
||||
"To permit a rule, have the checkbox checked. To deny a rule, leave it unchecked.": "Untuk mengizinkan sebuah aturan, centang kotaknya. Untuk menolak sebuah aturan, biarkan tidak dicentang.",
|
||||
"Today": "Hari Ini",
|
||||
"Trash Can": "Tempat Sampah",
|
||||
"Trash Can File Versioning": "Pemversian Berkas Tempat Sampah",
|
||||
"Type": "Tipe",
|
||||
"UNIX Permissions": "Izin UNIX",
|
||||
@@ -417,6 +485,7 @@
|
||||
"Unshared Folders": "Folder Tidak Terbagi",
|
||||
"Untrusted": "Tidak Terpercaya",
|
||||
"Up to Date": "Tersinkron",
|
||||
"Updated {%file%}": "Memperbarui {{file}}",
|
||||
"Upgrade": "Tingkatkan",
|
||||
"Upgrade To {%version%}": "Tingkatkan Ke {{version}}",
|
||||
"Upgrading": "Meningkatkan",
|
||||
@@ -425,7 +494,13 @@
|
||||
"Usage reporting is always enabled for candidate releases.": "Pelaporan penggunaan selalu aktif untuk rilis kandidat.",
|
||||
"Use HTTPS for GUI": "Gunakan HTTPS untuk GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Gunakan notifikasi dari filesistem untuk melihat perubahan berkas.",
|
||||
"User": "Pengguna",
|
||||
"User Home": "Beranda Pengguna",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Nama pengguna/sandi belum diatur untuk otentikasi GUI. Harap pertimbangkan menyiapkannya.",
|
||||
"Using a QUIC connection over LAN": "Menggunakan koneksi QUIC melalui LAN",
|
||||
"Using a QUIC connection over WAN": "Menggunakan koneksi QUIC melalui WAN",
|
||||
"Using a direct TCP connection over LAN": "Menggunakan koneksi TCP langsung melalui LAN",
|
||||
"Using a direct TCP connection over WAN": "Menggunakan koneksi TCP langsung melalui WAN",
|
||||
"Version": "Versi",
|
||||
"Versions": "Versi",
|
||||
"Versions Path": "Lokasi Versi",
|
||||
@@ -444,8 +519,10 @@
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Memantau perubahan menemukan kebanyakan perubahan tanpa pemindaian periodik.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Ketika menambah perangkat baru, perlu diingat bahwa perangkat ini juga harus ditambahkan pada sisi lain juga.",
|
||||
"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.": "Ketika menambah sebuah folder baru, perlu diingat bahwa ID Folder digunakan untuk mengikat folder antar perangkat. Mereka peka terhadap kapitalisasi huruf dan harus sama pada semua perangkat.",
|
||||
"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.": "Ketika diatur lebih dari satu pada kedua perangkat, Syncthing akan mencoba membuat beberapa koneksi bersamaan. Jika nilainya berbeda, nilai tertinggi yang akan digunakan. Atur ke nol untuk membiarkan Syncthing memutuskan.",
|
||||
"Yes": "Iya",
|
||||
"Yesterday": "Kemarin",
|
||||
"You can also copy and paste the text into a new message manually.": "Anda juga dapat menyalin dan menempelkan teks ke dalam pesan baru secara manual.",
|
||||
"You can also select one of these nearby devices:": "Anda juga dapat memilih salah satu perangkat disekitar berikut:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Anda dapat mengubah pilihan anda dalam dialog Pengaturan.",
|
||||
"You can read more about the two release channels at the link below.": "Anda dapat membaca lebih lanjut tentang dua saluran rilis pada tautan di bawah.",
|
||||
@@ -454,11 +531,19 @@
|
||||
"You have unsaved changes. Do you really want to discard them?": "Anda mempunyai perubahan yang belum disimpan. Apakah anda ingin membuangnya?",
|
||||
"You must keep at least one version.": "Anda seharusnya menyimpan setidaknya satu versi.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "Anda sebaiknya tidak pernah menambah atau mengubah apapun secara lokal dalam folder \"{{receiveEncrypted}}\".",
|
||||
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Aplikasi SMS Anda akan terbuka untuk memungkinkan Anda memilih penerima dan mengirimkannya dari nomor Anda sendiri.",
|
||||
"Your email app should open to let you choose the recipient and send it from your own address.": "Aplikasi email Anda akan terbuka untuk memungkinkan Anda memilih penerima dan mengirimkannya dari alamat Anda sendiri.",
|
||||
"days": "hari",
|
||||
"deleted": "dihapus",
|
||||
"deny": "tolak",
|
||||
"directories": "direktori",
|
||||
"file": "file",
|
||||
"files": "berkas",
|
||||
"folder": "folder",
|
||||
"full documentation": "dokumentasi penuh",
|
||||
"items": "berkas",
|
||||
"modified": "dimodifikasi",
|
||||
"permit": "izinkan",
|
||||
"seconds": "detik",
|
||||
"theme": {
|
||||
"name": {
|
||||
@@ -468,6 +553,7 @@
|
||||
"light": "Terang"
|
||||
}
|
||||
},
|
||||
"unknown device": "perangkat tidak dikenal",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ingin berbagi folder \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ingin berbagi folder \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} mungkin memperkenalkan ulang perangkat ini."
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "이 기기가 통보하는 폴더를 기본 경로에서 자동으로 생성 또는 공유합나다.",
|
||||
"Available debug logging facilities:": "사용 가능한 디버그 기록 기능:",
|
||||
"Be careful!": "주의하십시오!",
|
||||
"Block Indexing": "블록 색인",
|
||||
"Body:": "내용:",
|
||||
"Bugs": "버그",
|
||||
"Cancel": "취소",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "기기",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{device}} 기기가 {{address}}) 주소에서 접속을 요청했습니다. 새 기기를 추가하시겠습니까?",
|
||||
"Device Certificate": "기기 인증서",
|
||||
"Device Group": "기기 그룹",
|
||||
"Device ID": "기기 아이디",
|
||||
"Device Identification": "기기 아이디",
|
||||
"Device Name": "기기명",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "날짜별 검색",
|
||||
"Filter by name": "이름별 검색",
|
||||
"Folder": "폴더",
|
||||
"Folder Group": "폴더 그룹",
|
||||
"Folder ID": "폴더 아이디",
|
||||
"Folder Label": "폴더명",
|
||||
"Folder Path": "폴더 경로",
|
||||
@@ -278,7 +281,9 @@
|
||||
"OK": "확인",
|
||||
"Off": "하지 않음",
|
||||
"Oldest First": "오랜 파일 순",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "폴더를 묘사하는 선택적 이름입니다. 기기마다 달리 설정해도 됩니다.",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "폴더를 묘사하는 선택적 이름입니다. 기기마다 달리 설정할 수 있습니다.",
|
||||
"Optional group for the device. Can be different on each device.": "기기의 선택적 그룹입니다. 기기마다 달리 설정할 수 있습니다.",
|
||||
"Optional group for the folder. Can be different on each device.": "펄더의 선택적 그룹입니다. 기기마다 달리 설정할 수 있습니다.",
|
||||
"Options": "옵션",
|
||||
"Out of Sync": "동기화 미완료",
|
||||
"Out of Sync Items": "동기화 미완료 항목",
|
||||
@@ -463,7 +468,7 @@
|
||||
"This can easily give hackers access to read and change any files on your computer.": "이로 인해서는 해커들이 현재 컴퓨터의 모든 파일을 손쉽게 읽고 변경할 수 있게 됩니다.",
|
||||
"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.": "이 기기는 다른 기기를 자동으로 탐지하거나 다른 기기로부터 발견되도록 자신의 주소를 통보할 수 없습니다. 고정 주소로 설정한 기기만이 현재 기기에 접속할 수 있습니다.",
|
||||
"This is a major version upgrade.": "주요 버전 업데이트입니다.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "이 설정은 시스템(즉, 인덱스 데이터베이스가 있는) 저장 장치의 여유 공간을 관리합니다.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "이 설정은 시스템(즉, 색인 데이터베이스가 위치한) 저장 장치의 여유 공간을 관리합니다.",
|
||||
"Time": "시간",
|
||||
"Time the item was last modified": "항목이 가장 최근에 수정된 시간",
|
||||
"To connect with the Syncthing device named \"{%devicename%}\", add a new remote device on your end with this ID:": "\"{{devicename}}\" 기기와 연동하려면 아래의 아이디를 이용해 본인의 기기에서 새로운 기기를 추가하십시오.",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Automatycznie współdziel lub utwórz w domyślnej ścieżce foldery anonsowane przez to urządzenie.",
|
||||
"Available debug logging facilities:": "Dostępne narzędzia logujące do debugowania:",
|
||||
"Be careful!": "Ostrożnie!",
|
||||
"Block Indexing": "Indeksowanie bloków",
|
||||
"Body:": "Treść:",
|
||||
"Bugs": "Błędy",
|
||||
"Cancel": "Anuluj",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Urządzenie",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie o nazwie \"{{name}}\" {{device}} pod adresem ({{address}}) chce się połączyć. Czy dodać nowe urządzenie?",
|
||||
"Device Certificate": "Certyfikat urządzenia",
|
||||
"Device Group": "Grupa urządzenia",
|
||||
"Device ID": "Identyfikator urządzenia",
|
||||
"Device Identification": "Identyfikator urządzenia",
|
||||
"Device Name": "Nazwa urządzenia",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Filtruj według daty",
|
||||
"Filter by name": "Filtruj według nazwy",
|
||||
"Folder": "Folder",
|
||||
"Folder Group": "Grupa folderu",
|
||||
"Folder ID": "Identyfikator folderu",
|
||||
"Folder Label": "Etykieta folderu",
|
||||
"Folder Path": "Ścieżka folderu",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Zatrzymano wypisywanie logów. Przewiń w dół, aby je wznowić.",
|
||||
"Login failed, see Syncthing logs for details.": "Logowanie nie powiodło się. Sprawdź szczegóły w dzienniku zdarzeń programu.",
|
||||
"Logs": "Dziennik zdarzeń",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Utrzymuj indeks wszystkich bloków w folderze, umożliwiając ponowne wykorzystanie bloków z innych plików podczas synchronizowania zmian. Wyłącz tę opcję, aby zmniejszyć rozmiar bazy danych, ale kosztem braku możliwości ponownego wykorzystania bloków w różnych plikach.",
|
||||
"Major Upgrade": "Duża aktualizacja",
|
||||
"Mass actions": "Działania masowe",
|
||||
"Maximum Age": "Maksymalny wiek",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Wyłączona",
|
||||
"Oldest First": "Od najstarszych",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Opcjonalna opisowa etykieta dla folderu. Może być inna na każdym urządzeniu.",
|
||||
"Optional group for the device. Can be different on each device.": "Opcjonalna grupa dla urządzenia. Może być inna na każdym urządzeniu.",
|
||||
"Optional group for the folder. Can be different on each device.": "Opcjonalna grupa dla folderu. Może być inna na każdym urządzeniu.",
|
||||
"Options": "Opcje",
|
||||
"Out of Sync": "Niezsynchronizowane",
|
||||
"Out of Sync Items": "Elementy niezsynchronizowane",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Rozbudowane",
|
||||
"Staggered File Versioning": "Rozbudowane wersjonowanie plików",
|
||||
"Start Browser": "Uruchom przeglądarkę",
|
||||
"Starting": "Rozpoczynanie",
|
||||
"Statistics": "Statystyki",
|
||||
"Stay logged in": "Pozostań zalogowany",
|
||||
"Stopped": "Zatrzymany",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Criar ou compartilhar automaticamente pastas que este dispositivo anuncia no caminho padrão.",
|
||||
"Available debug logging facilities:": "Facilidades de depuração disponíveis:",
|
||||
"Be careful!": "Tenha cuidado!",
|
||||
"Block Indexing": "Indexação de blocos",
|
||||
"Body:": "Corpo:",
|
||||
"Bugs": "Erros",
|
||||
"Cancel": "Cancelar",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Dispositivo",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositivo \"{{name}}\" ({{device}} em {{address}}) quer se conectar. Adicionar novo dispositivo?",
|
||||
"Device Certificate": "Certificado do dispositivo",
|
||||
"Device Group": "Grupo de dispositivos",
|
||||
"Device ID": "ID do dispositivo",
|
||||
"Device Identification": "Identificação do dispositivo",
|
||||
"Device Name": "Nome do dispositivo",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Filtrar por data",
|
||||
"Filter by name": "Filtrar por nome",
|
||||
"Folder": "Pasta",
|
||||
"Folder Group": "Grupo de pastas",
|
||||
"Folder ID": "ID da pasta",
|
||||
"Folder Label": "Rótulo da pasta",
|
||||
"Folder Path": "Caminho da pasta",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing pausado. Role até o final para continuar.",
|
||||
"Login failed, see Syncthing logs for details.": "Falha no login. Veja os logs do Syncthing para mais detalhes.",
|
||||
"Logs": "Registros",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Mantenha um índice de todos os blocos da pasta, permitindo a reutilização de blocos de outros arquivos ao sincronizar alterações. Desative para reduzir o tamanho do banco de dados ao custo de não poder reutilizar blocos entre arquivos.",
|
||||
"Major Upgrade": "Atualização \"major\"",
|
||||
"Mass actions": "Ações em massa",
|
||||
"Maximum Age": "Idade máxima",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Desligada",
|
||||
"Oldest First": "Mais antigo primeiro",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Descrição opcional da pasta. Pode ser diferente em cada dispositivo.",
|
||||
"Optional group for the device. Can be different on each device.": "Grupo opcional para o dispositivo. Pode ser diferente em cada dispositivo.",
|
||||
"Optional group for the folder. Can be different on each device.": "Grupo opcional para a pasta. Pode ser diferente em cada dispositivo.",
|
||||
"Options": "Opções",
|
||||
"Out of Sync": "Fora de sincronia",
|
||||
"Out of Sync Items": "Fora de sincronia",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Escalonado",
|
||||
"Staggered File Versioning": "Escalonado",
|
||||
"Start Browser": "Iniciar navegador",
|
||||
"Starting": "Iniciando",
|
||||
"Statistics": "Estatísticas",
|
||||
"Stay logged in": "Ficar conectado",
|
||||
"Stopped": "Parado",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Allowed Networks": "Rețele permise",
|
||||
"Alphabetic": "Alfabetic",
|
||||
"Altered by ignoring deletes.": "Modificat prin ignorarea ștergerilor.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Întotdeauna activat când tipul de folder este „{{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.": "O comandă externă gestionează versiunea. Trebuie să elimine fișierul din mapa partajat. Dacă calea către aplicație conține spații, ar trebui să fie pusă între ghilimele.",
|
||||
"Anonymous Usage Reporting": "Raport Anonim despre Folosirea Aplicației",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formatul raportului de utilizare anonim s-a schimbat. Doriți să vă mutați în noul format?",
|
||||
@@ -52,6 +53,7 @@
|
||||
"Body:": "Corp:",
|
||||
"Bugs": "Bug-uri",
|
||||
"Cancel": "Renunță",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Nu poate fi activat când tipul de folder este „{{foldertype}}”.",
|
||||
"Changelog": "Noutăți",
|
||||
"Clean out after": "Curăță după",
|
||||
"Cleaning Versions": "Curățare versiuni",
|
||||
@@ -218,6 +220,9 @@
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
"Mod. Device": "Mod. Device",
|
||||
"Mod. Time": "Mod. Time",
|
||||
"More than a month ago": "Acum mai mult de o lună",
|
||||
"More than a week ago": "Acum mai mult de o săptămână",
|
||||
"More than a year ago": "Acum mai bine de un an",
|
||||
"Move to top of queue": "Mută la începutul listei",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Asterisc de nivel multiplu (corespunde fișierelor și sub-fișierelor)",
|
||||
"Never": "Niciodată",
|
||||
@@ -242,6 +247,7 @@
|
||||
"Path": "Path",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Localizarea fișierului în acest computer. Dacă nu există, va fi creat. Tilda (~) înlocuiește ",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).",
|
||||
"Paths": "Căi",
|
||||
"Pause": "Pauză",
|
||||
"Pause All": "Pause All",
|
||||
"Paused": "Paused",
|
||||
@@ -259,6 +265,7 @@
|
||||
"Preparing to Sync": "Preparing to Sync",
|
||||
"Preview": "Previzualizează",
|
||||
"Preview Usage Report": "Vezi raportul de utilizare",
|
||||
"QR code": "Cod QR",
|
||||
"Quick guide to supported patterns": "Ghid rapid pentru regulile suportate",
|
||||
"Random": "Random",
|
||||
"Receive Encrypted": "Receive Encrypted",
|
||||
@@ -301,6 +308,8 @@
|
||||
"Settings": "Setări",
|
||||
"Share": "Împarte",
|
||||
"Share Folder": "Împarte Mapa",
|
||||
"Share by Email": "Partajare prin e-mail",
|
||||
"Share by SMS": "Partajare prin SMS",
|
||||
"Share this folder?": "Împarte această mapă?",
|
||||
"Shared Folders": "Shared Folders",
|
||||
"Shared With": "Împarte Cu",
|
||||
@@ -330,6 +339,7 @@
|
||||
"Statistics": "Statistici",
|
||||
"Stopped": "Oprit",
|
||||
"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.": "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.",
|
||||
"Subject:": "Suibiect:",
|
||||
"Support": "Suport Tehnic",
|
||||
"Support Bundle": "Support Bundle",
|
||||
"Sync Protocol Listen Addresses": "Adresa protocolului de sincronizare",
|
||||
@@ -377,15 +387,18 @@
|
||||
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Intervalul de rescanare trebuie să nu fie un număr negativ de secunde. ",
|
||||
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
|
||||
"There are no file versions to restore.": "Nu există versiuni de fișiere de restaurat.",
|
||||
"There are no folders to share with this device.": "There are no folders to share with this device.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This Device": "This Device",
|
||||
"This Month": "Luna aceasta",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "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.",
|
||||
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "This setting controls the free space required on the home (i.e., index database) disk.",
|
||||
"Time": "Time",
|
||||
"Time the item was last modified": "Time the item was last modified",
|
||||
"Today": "Azi",
|
||||
"Trash Can File Versioning": "Trash Can File Versioning",
|
||||
"Type": "Type",
|
||||
"UNIX Permissions": "UNIX Permissions",
|
||||
@@ -401,6 +414,7 @@
|
||||
"Unshared Folders": "Unshared Folders",
|
||||
"Untrusted": "Untrusted",
|
||||
"Up to Date": "La Zi",
|
||||
"Updated {%file%}": "{{file}} actualizat",
|
||||
"Upgrade": "Upgrade",
|
||||
"Upgrade To {%version%}": "Actualizează La Versiunea {{version}}",
|
||||
"Upgrading": "Se Actualizează",
|
||||
@@ -408,6 +422,7 @@
|
||||
"Uptime": "Uptime",
|
||||
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
|
||||
"Use HTTPS for GUI": "Foloseşte HTTPS pentru interfaţă",
|
||||
"Use notifications from the filesystem to detect changed items.": "Folosește notificările din filesystem pentru a detecta elementele modificate.",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.",
|
||||
"Version": "Versiune",
|
||||
"Versions": "Versions",
|
||||
@@ -428,6 +443,8 @@
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Când adaugi un dispozitiv nou, trebuie să adaugi şi dispozitivul curent în dispozitivul nou.",
|
||||
"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.": "Cînd adăugaţi un fişier nou, nu uitaţi că ID-ul fişierului va rămîne acelaşi pe toate dispozitivele. Iar literele mari sînt diferite de literele mici. ",
|
||||
"Yes": "Da",
|
||||
"Yesterday": "Ieri",
|
||||
"You can also copy and paste the text into a new message manually.": "De asemenea, puteți copia și lipi manual textul într-un mesaj nou.",
|
||||
"You can also select one of these nearby devices:": "You can also select one of these nearby devices:",
|
||||
"You can change your choice at any time in the Settings dialog.": "You can change your choice at any time in the Settings dialog.",
|
||||
"You can read more about the two release channels at the link below.": "You can read more about the two release channels at the link below.",
|
||||
@@ -436,7 +453,10 @@
|
||||
"You have unsaved changes. Do you really want to discard them?": "You have unsaved changes. Do you really want to discard them?",
|
||||
"You must keep at least one version.": "Trebuie să păstrezi cel puţin o versiune.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
|
||||
"Your SMS app should open to let you choose the recipient and send it from your own number.": "Aplicația SMS ar trebui să se deschidă pentru a vă permite să alegeți destinatarul și să trimiteți mesajul de pe propriul număr.",
|
||||
"Your email app should open to let you choose the recipient and send it from your own address.": "Aplicația de e-mail ar trebui să se deschidă pentru a vă permite să alegeți destinatarul și să trimiteți mesajul de la propria adresă.",
|
||||
"days": "Zile",
|
||||
"deleted": "șters",
|
||||
"directories": "directories",
|
||||
"files": "files",
|
||||
"full documentation": "toată documentaţia",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"Advanced": "Avancerat",
|
||||
"Advanced Configuration": "Avancerad konfiguration",
|
||||
"All Data": "Alla data",
|
||||
"All Time": "Någonsin",
|
||||
"All Time": "All tid",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alla mappar som delas med denna enhet måste skyddas av ett lösenord, så att alla skickade data är oläsliga utan det angivna lösenordet.",
|
||||
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistiksrapportering?",
|
||||
"Allowed Networks": "Tillåtna nätverk",
|
||||
@@ -31,7 +31,7 @@
|
||||
"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.": "Ett externt kommando hanterar versionen. Det måste ta bort filen från den delade mappen. Om sökvägen till applikationen innehåller mellanslag bör den citeras.",
|
||||
"Anonymous Usage Reporting": "Anonym användarstatistiksrapportering",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymt användningsrapportformat har ändrats. Vill du flytta till det nya formatet?",
|
||||
"Applied to LAN": "Applicerad på LAN",
|
||||
"Applied to LAN": "Tillämpas på LAN",
|
||||
"Apply": "Tillämpa",
|
||||
"Are you sure you want to override all remote changes?": "Är du säker på att du vill åsidosätta alla fjärrändringar?",
|
||||
"Are you sure you want to permanently delete all these files?": "Är du säker på att du vill ta bort alla dessa filer permanent?",
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Skapa eller dela automatiskt mappar som denna enhet annonserar på standardsökvägen.",
|
||||
"Available debug logging facilities:": "Tillgängliga felsökningsfunktioner:",
|
||||
"Be careful!": "Var aktsam!",
|
||||
"Block Indexing": "Blockindexering",
|
||||
"Body:": "Meddelande:",
|
||||
"Bugs": "Felrapporter",
|
||||
"Cancel": "Avbryt",
|
||||
@@ -75,10 +76,10 @@
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Kontinuerlig bevakning av ändringar är nu tillgängligt i Syncthing. Detta kommer att upptäcka ändringar på disken och utfärda en skanning på endast de ändrade sökvägarna. Fördelarna är att ändringar delas snabbare och att mindre fullständiga skanningar krävs.",
|
||||
"Copied from elsewhere": "Kopierat från annanstans",
|
||||
"Copied from original": "Kopierat från original",
|
||||
"Copied!": "Kopierad!",
|
||||
"Copied!": "Kopierat!",
|
||||
"Copy": "Kopiera",
|
||||
"Copy failed! Try to select and copy manually.": "Kopieringen misslyckades! Försök att markera och kopiera manuellt.",
|
||||
"Currently Shared With Devices": "För närvarande delas med enheter",
|
||||
"Currently Shared With Devices": "Delas för närvarande med enheter",
|
||||
"Custom Range": "Anpassat intervall",
|
||||
"Danger!": "Fara!",
|
||||
"Database Location": "Databasplats",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Enhet",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
|
||||
"Device Certificate": "Enhetscertifikat",
|
||||
"Device Group": "Enhetsgrupp",
|
||||
"Device ID": "Enhets-ID",
|
||||
"Device Identification": "Enhetens identifikation",
|
||||
"Device Name": "Enhetsnamn",
|
||||
@@ -161,14 +163,15 @@
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Det går inte att ansluta till IPv6-servrar om det inte finns någon IPv6-anslutning.",
|
||||
"File Pull Order": "Filhämtningsprioritering",
|
||||
"File Versioning": "Filversionshantering",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen vid byte eller tas bort av Syncthing.",
|
||||
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till .stversions-mappen när de ersätts eller tas bort av Syncthing.",
|
||||
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Filer flyttas till datumstämplade versioner i en .stversions-mapp när de ersätts eller tas bort av Syncthing.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på denna nod skickas till de andra klustermedlemmarna.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Filer är skyddade från ändringar som görs på andra enheter, men ändringar som görs på den här enheten skickas till resten av klustret.",
|
||||
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Filer synkroniseras från klustret, men alla ändringar som görs lokalt skickas inte till andra enheter.",
|
||||
"Filesystem Watcher Errors": "Filsystem bevakarfel",
|
||||
"Filesystem Watcher Errors": "Fel i filsystembevakaren",
|
||||
"Filter by date": "Filtrera efter datum",
|
||||
"Filter by name": "Filtrera efter namn",
|
||||
"Folder": "Mapp",
|
||||
"Folder Group": "Mappgrupp",
|
||||
"Folder ID": "Mapp-ID",
|
||||
"Folder Label": "Mappetikett",
|
||||
"Folder Path": "Mappsökväg",
|
||||
@@ -179,7 +182,7 @@
|
||||
"Folders": "Mappar",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "För följande mappar uppstod ett fel när du började bevaka ändringar. Det kommer att omförsökas varje minut, så felen kan försvinna snart. Om de fortsätter, försök att åtgärda det underliggande problemet och fråga om hjälp om du inte kan.",
|
||||
"Forever": "För alltid",
|
||||
"Full Rescan Interval (s)": "Fullständigt återkommande skanningsintervall (s)",
|
||||
"Full Rescan Interval (s)": "Intervall för fullständig omskanning (s)",
|
||||
"GUI": "Grafiskt användargränssnitt",
|
||||
"GUI / API HTTPS Certificate": "HTTPS-certifikat för gränssnitt / API",
|
||||
"GUI Authentication Password": "Autentiseringslösenord för gränssnitt",
|
||||
@@ -199,7 +202,7 @@
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Dina aktuella inställningar indikerar dock att du kanske inte vill att ha det aktiverat. Vi har inaktiverat automatisk kraschrapportering för dig.",
|
||||
"Identification": "Identifiering",
|
||||
"If untrusted, enter encryption password": "Om opålitlig, ange krypteringslösenord",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Om du vill förhindra att andra användare på denna dator får åtkomst till Syncthing och genom det dina filer, överväg att ställa in autentisering.",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Om du vill förhindra att andra användare på denna dator får åtkomst till Syncthing och dina filer via det, överväg att konfigurera autentisering.",
|
||||
"Ignore": "Ignorera",
|
||||
"Ignore Patterns": "Ignoreringsmönster",
|
||||
"Ignore Permissions": "Ignorera rättigheter",
|
||||
@@ -209,7 +212,7 @@
|
||||
"Ignored at": "Ignorerad vid",
|
||||
"Included Software": "Inkluderad programvara",
|
||||
"Incoming Rate Limit (KiB/s)": "Ingående hastighetsgräns (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Inkorrekt konfiguration kan skada innehållet i mappen och få Syncthing att sluta fungera.",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Felaktig konfiguration kan skada innehållet i mappen och få Syncthing att sluta fungera.",
|
||||
"Incorrect user name or password.": "Felaktigt användarnamn eller lösenord.",
|
||||
"Info": "Info",
|
||||
"Internally used paths:": "Internt använda sökvägar:",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Loggning pausad. Bläddra till botten för att fortsätta.",
|
||||
"Login failed, see Syncthing logs for details.": "Inloggningen misslyckades, se Syncthing-loggarna för detaljer.",
|
||||
"Logs": "Loggar",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Upprätthåll ett index över alla block i mappen, vilket möjliggör återanvändning av block från andra filer vid synkronisering av ändringar. Inaktivera för att minska databasens storlek på bekostnad av att inte kunna återanvända block mellan filer.",
|
||||
"Major Upgrade": "Större uppgradering",
|
||||
"Mass actions": "Massåtgärder",
|
||||
"Maximum Age": "Högsta ålder",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Av",
|
||||
"Oldest First": "Äldsta först",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Valfri beskrivande etikett för mappen. Kan vara olika på varje enhet.",
|
||||
"Optional group for the device. Can be different on each device.": "Valfri grupp för enheten. Kan vara olika på varje enhet.",
|
||||
"Optional group for the folder. Can be different on each device.": "Valfri grupp för mappen. Kan vara olika på varje enhet.",
|
||||
"Options": "Alternativ",
|
||||
"Out of Sync": "Osynkroniserad",
|
||||
"Out of Sync Items": "Osynkroniserade objekt",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Förskjuten",
|
||||
"Staggered File Versioning": "Filversionshantering i intervall",
|
||||
"Start Browser": "Starta webbläsaren",
|
||||
"Starting": "Börjar",
|
||||
"Statistics": "Statistik",
|
||||
"Stay logged in": "Förbli inloggad",
|
||||
"Stopped": "Stoppad",
|
||||
@@ -402,11 +409,11 @@
|
||||
"Sync Protocol Listen Addresses": "Lyssnaradresser för synkroniseringsprotokoll",
|
||||
"Sync Status": "Synkroniseringsstatus",
|
||||
"Syncing": "Synkroniserar",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "Synkronisera enhets-ID för \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Synkronisering har stängts av.",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "Syncthing enhets-ID för \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Syncthing har stängts av.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing har fri och öppen källkod licensierad som 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 är ett program för kontinuerlig filsynkronisering. Den synkroniserar filer mellan två eller flera datorer i realtid, säkert skyddade från nyfikna ögon. Dina data är enbart din data och du förtjänar att välja var den lagras, om den delas med någon tredje part och hur den överförs över internet.",
|
||||
"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 är ett program för kontinuerlig filsynkronisering. Den synkroniserar filer mellan två eller flera datorer i realtid, säkert skyddade från nyfikna ögon. Dina data är dina data och du har rätt att välja var de lagras, om de delas med någon tredje part och hur de överförs över internet.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing lyssnar på följande nätverksadresser för anslutningsförsök från andra enheter:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing lyssnar inte efter anslutningsförsök från andra enheter på någon adress. Endast utgående anslutningar från denna enhet kanske fungerar.",
|
||||
"Syncthing is restarting.": "Syncthing startar om.",
|
||||
@@ -425,11 +432,11 @@
|
||||
"The cleanup interval cannot be blank.": "Rensningsintervallet kan inte vara tomt.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
|
||||
"The device ID cannot be blank.": "Enhets-ID kan inte vara tomt.",
|
||||
"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).": "Enhets-ID som du anger här finns i dialogrutan \"Åtgärder > Visa ID\" på denna enhet. Mellanslag och bindestreck är valfria (ignoreras).",
|
||||
"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).": "Enhets-ID som du anger här finns i dialogrutan \"Åtgärder > Visa ID\" på den andra enheten. Mellanslag och bindestreck är valfria (ignoreras).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes, and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användningsrapporten skickas dagligen. Det används för att spåra vanliga plattformar, mappstorlekar och appversioner. Om den rapporterade datauppsättningen ändras kommer du att uppmanas med denna dialogruta igen.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets-ID:t verkar inte vara korrekt. Det ska vara en 52 eller 56 teckensträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
|
||||
"The folder ID cannot be blank.": "Mapp-ID får inte vara tomt.",
|
||||
"The folder ID must be unique.": "Mapp-ID måste vara unik.",
|
||||
"The folder ID must be unique.": "Mapp-ID måste vara unikt.",
|
||||
"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.": "Mappinnehållet på andra enheter skrivs över så att det blir identiskt med denna enhet. Filer som inte finns här kommer att tas bort på andra enheter.",
|
||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "Mappinnehållet på denna enhet skrivs över för att bli identiskt med andra enheter. Filer som nyligen har lagts till här kommer att tas bort.",
|
||||
"The folder path cannot be blank.": "Mappsökvägen kan inte vara tom.",
|
||||
@@ -450,10 +457,10 @@
|
||||
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas tomt.",
|
||||
"The path cannot be blank.": "Sökvägen kan inte vara tom.",
|
||||
"The rate limit is applied to the accumulated traffic of all connections to this device.": "Hastighetsgränsen tillämpas på den ackumulerade trafiken för alla anslutningar till denna enhet.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Frekvensgränsen måste vara ett icke-negativt tal (0: ingen gräns)",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Hastighetsgränsen måste vara ett icke-negativt tal (0: ingen gräns)",
|
||||
"The remote device has not accepted sharing this folder.": "Fjärrenheten har inte accepterat delning av denna mapp.",
|
||||
"The remote device has paused this folder.": "Fjärrenheten har pausat denna mapp.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Omskanningsintervallet måste vara ett positivt antal sekunder.",
|
||||
"There are no devices to share this folder with.": "Det finns inga enheter att dela denna mapp med.",
|
||||
"There are no file versions to restore.": "Det finns inga filversioner att återställa.",
|
||||
"There are no folders to share with this device.": "Det finns inga mappar att dela med denna enhet.",
|
||||
@@ -475,7 +482,7 @@
|
||||
"UNIX Permissions": "UNIX-behörigheter",
|
||||
"Unavailable": "Otillgänglig",
|
||||
"Unavailable/Disabled by administrator or maintainer": "Otillgängligt/inaktiverat av administratör eller underhållare",
|
||||
"Undecided (will prompt)": "Obeslutad (kommer att skriva)",
|
||||
"Undecided (will prompt)": "Obeslutad (kommer att fråga)",
|
||||
"Unexpected Items": "Oväntade objekt",
|
||||
"Unexpected items have been found in this folder.": "Oväntade objekt har hittats i denna mapp.",
|
||||
"Unignore": "Sluta ignorera",
|
||||
@@ -499,12 +506,12 @@
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Användarnamn/lösenord har inte ställts in för autentisering av det grafiska gränssnittet. Överväg att ställa in det.",
|
||||
"Using a QUIC connection over LAN": "Använder en QUIC-anslutning över LAN",
|
||||
"Using a QUIC connection over WAN": "Använder en QUIC-anslutning över WAN",
|
||||
"Using a direct TCP connection over LAN": "Använda en direkt TCP-anslutning över LAN",
|
||||
"Using a direct TCP connection over WAN": "Använda en direkt TCP-anslutning över WAN",
|
||||
"Using a direct TCP connection over LAN": "Använder en direkt TCP-anslutning över LAN",
|
||||
"Using a direct TCP connection over WAN": "Använder en direkt TCP-anslutning över WAN",
|
||||
"Version": "Version",
|
||||
"Versions": "Versioner",
|
||||
"Versions Path": "Sökväg för versioner",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner tas bort automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i intervallet.",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner tas automatiskt bort om de är äldre än den maximala åldern eller överskrider antalet filer som tillåts i ett intervall.",
|
||||
"Waiting to Clean": "Väntar på att rensa",
|
||||
"Waiting to Scan": "Väntar på att skanna",
|
||||
"Waiting to Sync": "Väntar på att synkronisera",
|
||||
@@ -518,7 +525,7 @@
|
||||
"Watching for Changes": "Bevakar ändringar",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Bevakning av ändringar upptäcker de flesta ändringar utan periodisk skanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet, kom ihåg att denna enhet måste läggas till på den andra enheten också.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny mapp, tänk på att mapp-ID knyter ihop mappar mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny mapp, tänk på att mapp-ID knyter ihop mappar mellan olika enheter. De är skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
|
||||
"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.": "När den är inställd på mer än en på båda enheterna kommer Syncthing att försöka upprätta flera samtidiga anslutningar. Om värdena skiljer sig kommer det högsta att användas. Ställ in på noll för att låta Syncthing bestämma.",
|
||||
"Yes": "Ja",
|
||||
"Yesterday": "Igår",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Bu cihazın duyurduğu klasörleri otomatik olarak varsayılan yolda oluşturun veya paylaşın.",
|
||||
"Available debug logging facilities:": "Mevcut hata ayıklama günlüklemesi olanakları:",
|
||||
"Be careful!": "Dikkatli olun!",
|
||||
"Block Indexing": "Blok İndeksleme",
|
||||
"Body:": "Gövde:",
|
||||
"Bugs": "Hatalar",
|
||||
"Cancel": "İptal",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "Cihaz",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{address}} adresindeki {{device}}) cihazı bağlanmak istiyor. Yeni cihaz eklensin mi?",
|
||||
"Device Certificate": "Cihaz Sertifikası",
|
||||
"Device Group": "Cihaz Grubu",
|
||||
"Device ID": "Cihaz Kimliği",
|
||||
"Device Identification": "Cihaz Kimliği",
|
||||
"Device Name": "Cihaz Adı",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "Tarihe göre süz",
|
||||
"Filter by name": "İsme göre süz",
|
||||
"Folder": "Klasör",
|
||||
"Folder Group": "Klasör Grubu",
|
||||
"Folder ID": "Klasör Kimliği",
|
||||
"Folder Label": "Klasör Etiketi",
|
||||
"Folder Path": "Klasör Yolu",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Günlük kuyruğu duraklatıldı. Devam etmek için aşağı kaydırın.",
|
||||
"Login failed, see Syncthing logs for details.": "Oturum açma başarısız oldu, ayrıntılar için Syncthing günlüklerine bakın.",
|
||||
"Logs": "Günlükler",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Değişiklikleri eşitlerken diğer dosyalardaki blokların yeniden kullanılmasını sağlamak için klasördeki tüm blokların indeksini koruyun. Blokları dosyalar arasında yeniden kullanamama pahasına veritabanı boyutunu azaltmak için etkisizleştirin.",
|
||||
"Major Upgrade": "Büyük Yükseltme",
|
||||
"Mass actions": "Toplu eylemler",
|
||||
"Maximum Age": "En Fazla Yaş",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "Kapalı",
|
||||
"Oldest First": "Önce En Eski Olan",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Klasör için isteğe bağlı açıklayıcı etiket. Her cihazda farklı olabilir.",
|
||||
"Optional group for the device. Can be different on each device.": "Cihaz için isteğe bağlı grup. Her cihazda farklı olabilir.",
|
||||
"Optional group for the folder. Can be different on each device.": "Klasör için isteğe bağlı grup. Her cihazda farklı olabilir.",
|
||||
"Options": "Seçenekler",
|
||||
"Out of Sync": "Eşitlenmemiş",
|
||||
"Out of Sync Items": "Eşitlenmemiş Öğeler",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "Aşamalı",
|
||||
"Staggered File Versioning": "Aşamalı Dosya Sürümlendirme",
|
||||
"Start Browser": "Tarayıcıyı başlat",
|
||||
"Starting": "Başlıyor",
|
||||
"Statistics": "İstatistikler",
|
||||
"Stay logged in": "Oturum açık kalsın",
|
||||
"Stopped": "Durduruldu",
|
||||
|
||||
@@ -1,39 +1,58 @@
|
||||
{
|
||||
"A device with that ID is already added.": "ID thiết bị này đã được thêm",
|
||||
"A device with that ID is already added.": "ID thiết bị này đã được thêm.",
|
||||
"A negative number of days doesn't make sense.": "Số ngày không được âm.",
|
||||
"A new major version may not be compatible with previous versions.": "Phiên bản chính mới có thể sẽ không tương thích với các bản cũ.",
|
||||
"API Key": "Khoá API",
|
||||
"About": "Thông tin",
|
||||
"Action": "Hành động",
|
||||
"Actions": "Thao tác",
|
||||
"Active filter rules": "Quy tắc lọc đang hoạt động",
|
||||
"Add": "Thêm",
|
||||
"Add Device": "Thêm thiết bị",
|
||||
"Add Folder": "Thêm thư mục",
|
||||
"Add Remote Device": "Thêm thiết bị từ xa",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Thêm thiết bị từ người giới thiệu vào danh sách thiết bị của chúng ta để chia sẻ thư mục với nhau",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Thêm thiết bị từ người giới thiệu vào danh sách thiết bị của chúng ta để chia sẻ thư mục với nhau.",
|
||||
"Add filter entry": "Thêm mục lọc",
|
||||
"Add ignore patterns": "Thêm các mẫu bỏ qua",
|
||||
"Add new folder?": "Thêm thư mục mới?",
|
||||
"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.": "Khoảng thời gian quét lại toàn bộ sẽ được tăng lên (60 lần, nghĩa là mặc định là 1 giờ). Bạn cũng có thể cấu hình thủ công lại cho mọi thư mục sau khi chọn Không",
|
||||
"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.": "Khoảng thời gian quét lại toàn bộ sẽ được tăng lên (60 lần, nghĩa là mặc định là 1 giờ). Bạn cũng có thể cấu hình thủ công lại cho mọi thư mục sau khi chọn Không.",
|
||||
"Address": "Địa chỉ",
|
||||
"Addresses": "Các địa chỉ",
|
||||
"Advanced": "Nâng cao",
|
||||
"Advanced Configuration": "Cấu hình nâng cao",
|
||||
"All Data": "Tất cả dữ liệu",
|
||||
"All Time": "Toàn thời gian",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Tất cả các thư mục chia sẻ với thiết bị này phải được bảo vệ bằng mật khẩu, sao cho tất cả dữ liệu gửi đi sẽ không thể đọc được nếu không có mật khẩu đã cung cấp.",
|
||||
"Allow Anonymous Usage Reporting?": "Cho phép báo cáo sử dụng ẩn danh?",
|
||||
"Allowed Networks": "Các mạng được phép",
|
||||
"Alphabetic": "A-Z",
|
||||
"Alphabetic": "Theo thứ tự chữ cái",
|
||||
"Altered by ignoring deletes.": "Đã thay đổi nhưng bỏ qua việc xóa.",
|
||||
"Always turned on when the folder type is \"{%foldertype%}\".": "Luôn bật khi loại thư mục là \"{{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.": "Một tập lệnh ngoại vi chịu trách nhiệm phiên bản hóa. Nó phải xóa tệp tin khỏi thư mục được chia sẻ. Nếu đường dẫn đến tập tin chứa khoảng trắng, nó sẽ được trích dẫn.",
|
||||
"Anonymous Usage Reporting": "Báo cáo sử dụng ẩn danh",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Định dạng báo cáo sử dụng ẩn danh đã thay đổi. Bạn có muốn chuyển sang định dạng mới?",
|
||||
"Are you sure you want to remove device {%name%}?": "Bạn có chắc chắn muốn xóa thiết bị này không [[Tên thiết bị]]?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Bạn có chắc chắn muốn xóa thư mục này không [[nhãn]]?",
|
||||
"Are you sure you want to restore {%count%} files?": "Bạn có chắc chắn muốn khôi phục [[số lượng]] tệp tin?",
|
||||
"Applied to LAN": "Áp dụng cho mạng LAN",
|
||||
"Apply": "Áp dụng",
|
||||
"Are you sure you want to override all remote changes?": "Bạn có chắc chắn muốn ghi đè lên tất cả các thay đổi từ xa không?",
|
||||
"Are you sure you want to permanently delete all these files?": "Bạn có chắc chắn muốn xóa vĩnh viễn tất cả các tệp này không?",
|
||||
"Are you sure you want to remove device {%name%}?": "Bạn có chắc chắn muốn xóa thiết bị {{name}} không?",
|
||||
"Are you sure you want to remove folder {%label%}?": "Bạn có chắc chắn muốn xóa thư mục {{label}} không?",
|
||||
"Are you sure you want to restore {%count%} files?": "Bạn có chắc chắn muốn khôi phục {{count}} tệp tin?",
|
||||
"Are you sure you want to revert all local changes?": "Bạn có chắc chắn muốn hoàn tác tất cả các thay đổi cục bộ không?",
|
||||
"Are you sure you want to upgrade?": "Bạn có chắc chắn muốn nâng cấp không?",
|
||||
"Authentication Required": "Yêu cầu xác thực",
|
||||
"Authors": "Tác giả",
|
||||
"Auto Accept": "Tự động chấp nhận",
|
||||
"Automatic Crash Reporting": "Báo cáo sự cố tự động",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Nâng cấp tự động hiện đang cung cấp lựa chọn giữa các bản phát hành ổn định và bản phát hành thử nghiệm.",
|
||||
"Automatic upgrades": "Cập nhật tự động",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Tự động tạo hoặc chia sẻ các thư mục mà thiết bị này giới thiệu ở đường dẫn mặc định.",
|
||||
"Available debug logging facilities:": "Phương tiện ghi nhật ký gỡ lỗi có sẵn:",
|
||||
"Be careful!": "Cẩn thận!",
|
||||
"Body:": "Nội dung:",
|
||||
"Bugs": "Lỗi",
|
||||
"Cancel": "Hủy",
|
||||
"Cannot be enabled when the folder type is \"{%foldertype%}\".": "Không thể kích hoạt khi loại thư mục là \"{{foldertype}}\".",
|
||||
"Changelog": "Lịch sử thay đổi",
|
||||
"Clean out after": "Dọn dẹp sau",
|
||||
"Close": "Đóng",
|
||||
@@ -82,23 +101,40 @@
|
||||
"Folder ID": "ID thư mục",
|
||||
"Folder Label": "Nhãn thư mục",
|
||||
"Folder Path": "Đ.dẫn đến th.mục",
|
||||
"Folder Type": "Loại thu mục",
|
||||
"Folders": "Các th.mục",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Mật khẩu xác minh GUI",
|
||||
"GUI Authentication User": "Người dùng xác minh GUI",
|
||||
"Forever": "Mãi mãi",
|
||||
"Full Rescan Interval (s)": "Khoảng thời gian quét lại toàn bộ (giây)",
|
||||
"GUI": "Giao diện người dùng (GUI)",
|
||||
"GUI / API HTTPS Certificate": "",
|
||||
"GUI Authentication Password": "Mật khẩu xác thực GUI",
|
||||
"GUI Authentication User": "Người dùng xác thực GUI",
|
||||
"GUI Authentication: Set User and Password": "Xác thực GUI: Đặt người dùng và mật khẩu",
|
||||
"GUI Listen Address": "Địa chỉ lắng nghe GUI",
|
||||
"GUI Override Directory": "",
|
||||
"GUI Theme": "Chủ đề giao diện GUI",
|
||||
"General": "Chung",
|
||||
"Generate": "Tạo mới",
|
||||
"Global Discovery": "Dò tìm toàn cầu",
|
||||
"Global Discovery Servers": "Các m.chủ dò tìm toàn cầu",
|
||||
"Global Discovery Servers": "Các máy chủ khám phá toàn cầu",
|
||||
"Global State": "Tr.thái toàn cầu",
|
||||
"Help": "Trợ giúp",
|
||||
"Hint: only deny-rules detected while the default is deny. Consider adding \"permit any\" as last rule.": "Gợi ý: chỉ phát hiện các rule từ chối (deny-rules) trong khi mặc định là deny. Hãy cân nhắc thêm “permit any” làm quy tắc cuối cùng.",
|
||||
"Home page": "Trang chủ",
|
||||
"Identification": "Nhận dạng",
|
||||
"Ignore": "Bỏ qua",
|
||||
"Ignore Patterns": "Bỏ qua các quy luật",
|
||||
"Ignore Permissions": "Bỏ qua các giấy phép",
|
||||
"Ignored Devices": "Các thiết bị bỏ qua",
|
||||
"Ignored Folders": "Các thư mục bị bỏ qua",
|
||||
"Ignored at": "Bỏ qua tại",
|
||||
"Included Software": "Phần mềm đi kèm",
|
||||
"Incoming Rate Limit (KiB/s)": "Giới hạn t.độ đầu vào (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Cấu hình không đúng có thể làm mất mát dữ liệu và khiến Syncthing ngừng hoạt động.",
|
||||
"Incorrect user name or password.": "Tên người dùng hoặc mật khẩu không chính xác.",
|
||||
"Info": "Thông tin",
|
||||
"Introducer": "Th.bị giới thiệu",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Đảo ngược điều kiện cho trước (VD: không được loại trừ)",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Đảo ngược điều kiện cho trước (Ví dụ: không được loại trừ)",
|
||||
"Keep Versions": "Giữ các ph.bản",
|
||||
"LDAP": "LDAP",
|
||||
"Largest First": "Lớn nhất đầu tiên",
|
||||
@@ -174,11 +210,12 @@
|
||||
"Support": "Hỗ trợ",
|
||||
"Sync Protocol Listen Addresses": "Đồng bộ các đ.chỉ l.nghe giao thức",
|
||||
"Syncing": "Đang đ.bộ",
|
||||
"Syncthing has been shut down.": "Đã tắt Syncthing.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing bao gồm các ph.mềm hoặc ph.tử của các ph.mềm sau:",
|
||||
"Syncthing device ID for \"{%devicename%}\"": "Mã định danh thiết bị Syncthing cho \"{{devicename}}\"",
|
||||
"Syncthing has been shut down.": "Syncthing đã bị tắt.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing bao gồm các phần mềm sau hoặc một phần của chúng:",
|
||||
"Syncthing is restarting.": "Syncthing đang kh.động lại.",
|
||||
"Syncthing is upgrading.": "Syncthing đang n.cấp.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Có vẻ như Syncthing đang bị nghẽn hoặc là mạng của bạn có vấn đề. Đang thử lại...",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing dường như đang gặp sự cố hoặc có vấn đề với kết nối Internet của bạn. Đang thử lại…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Có vẻ như Syncthing đang gặp phải v.đề khi x.lý yêu cầu của bạn. Xin làm mới trang hoặc kh.động lại Syncthing nếu v.đề vẫn còn tiếp diễn.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Giao diện q.trị của Syncthing được c.hình nhằm cho phép tr.cập từ xa không cần mật khẩu.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Cấu hình đã được lưu nhưng chưa được kích hoạt. Syncthing phải khởi động lại để kích hoạt cấu hình mới. ",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"Automatically create or share folders that this device advertises at the default path.": "自动创建或共享此设备在默认路径上显示的文件夹。",
|
||||
"Available debug logging facilities:": "可用的调试日志功能:",
|
||||
"Be careful!": "小心!",
|
||||
"Block Indexing": "块索引",
|
||||
"Body:": "正文:",
|
||||
"Bugs": "问题反馈",
|
||||
"Cancel": "取消",
|
||||
@@ -99,6 +100,7 @@
|
||||
"Device": "设备",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "设备“{{name}}”({{address}} 处的 {{device}})想要连接。添加新设备?",
|
||||
"Device Certificate": "设备证书",
|
||||
"Device Group": "设备组",
|
||||
"Device ID": "设备 ID",
|
||||
"Device Identification": "设备标识",
|
||||
"Device Name": "设备名",
|
||||
@@ -169,6 +171,7 @@
|
||||
"Filter by date": "按日期筛选",
|
||||
"Filter by name": "按名称筛选",
|
||||
"Folder": "文件夹",
|
||||
"Folder Group": "文件夹组",
|
||||
"Folder ID": "文件夹 ID",
|
||||
"Folder Label": "文件夹标签",
|
||||
"Folder Path": "文件夹路径",
|
||||
@@ -249,6 +252,7 @@
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "日志跟踪已暂停。滚动到底部继续。",
|
||||
"Login failed, see Syncthing logs for details.": "登录失败,有关详细信息,请参阅 Syncthing 日志。",
|
||||
"Logs": "日志",
|
||||
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "维护文件夹中所有块的索引,以便在同步更改时复用来自其他文件的块。禁用可减小数据库大小,但代价是无法跨文件复用块。",
|
||||
"Major Upgrade": "重大更新",
|
||||
"Mass actions": "批量操作",
|
||||
"Maximum Age": "最长保留时间",
|
||||
@@ -279,6 +283,8 @@
|
||||
"Off": "关闭",
|
||||
"Oldest First": "最旧优先",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "文件夹的可选描述性标签。每个设备上可能不同。",
|
||||
"Optional group for the device. Can be different on each device.": "设备的可选分组。各设备可设置不同分组。",
|
||||
"Optional group for the folder. Can be different on each device.": "文件夹的可选分组。各设备可设置不同分组。",
|
||||
"Options": "选项",
|
||||
"Out of Sync": "未同步",
|
||||
"Out of Sync Items": "未同步的项目",
|
||||
@@ -390,6 +396,7 @@
|
||||
"Staggered": "阶段",
|
||||
"Staggered File Versioning": "阶段文件版本控制",
|
||||
"Start Browser": "启动浏览器",
|
||||
"Starting": "正在启动",
|
||||
"Statistics": "统计",
|
||||
"Stay logged in": "保持登录",
|
||||
"Stopped": "已停止",
|
||||
|
||||
@@ -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","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)"}
|
||||
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)","eo":"Esperanto","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)"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
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"]
|
||||
var validLangs = ["ar","bg","ca","ca@valencia","cs","da","de","el","en","en-GB","eo","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"]
|
||||
|
||||
+478
-461
@@ -381,264 +381,276 @@
|
||||
|
||||
<div class="col-md-6" aria-labelledby="folder_list" role="region" >
|
||||
<h3 id="folder_list"><span translate>Folders</span><span ng-if="folderList().length > 1"> ({{folderList().length}})</span></h3>
|
||||
<div class="panel-group" id="folders">
|
||||
<div class="panel panel-default" ng-repeat="folder in folderList()">
|
||||
<button class="btn panel-heading" data-toggle="collapse" data-parent="#folders" data-target="#folder-{{$index}}" aria-expanded="false">
|
||||
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id) | percent}}"></div>
|
||||
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id) | percent}}"></div>
|
||||
<h4 class="panel-title">
|
||||
<div class="panel-icon hidden-xs">
|
||||
<span ng-if="folder.type == 'sendreceive'" class="fas fa-fw fa-folder"></span>
|
||||
<span ng-if="folder.type == 'sendonly'" class="fas fa-fw fa-upload"></span>
|
||||
<span ng-if="folder.type == 'receiveonly'" class="fas fa-fw fa-download"></span>
|
||||
<span ng-if="folder.type == 'receiveencrypted'" class="fas fa-fw fa-lock"></span>
|
||||
<div ng-repeat="(folderGroupName, groupedFolders) in foldersGrouped">
|
||||
<h4 ng-if="folderGroupName !== ''">{{ folderGroupName }}
|
||||
<span ng-if="groupedFolders.length > 1 && folderGroupName.length > 0"> ({{groupedFolders.length}})</span>
|
||||
</h4>
|
||||
<div class="panel-group" id="folders-{{ $index }}">
|
||||
<div class="panel panel-default" ng-repeat="folder in groupedFolders">
|
||||
<button class="btn panel-heading" data-toggle="collapse" data-parent="#folders-{{$index}}" data-target="#folder-{{$parent.$index}}-{{$index}}" aria-expanded="false">
|
||||
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id) | percent}}"></div>
|
||||
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id) | percent}}"></div>
|
||||
<h4 class="panel-title">
|
||||
<div class="panel-icon hidden-xs">
|
||||
<span ng-if="folder.type == 'sendreceive'" class="fas fa-fw fa-folder"></span>
|
||||
<span ng-if="folder.type == 'sendonly'" class="fas fa-fw fa-upload"></span>
|
||||
<span ng-if="folder.type == 'receiveonly'" class="fas fa-fw fa-download"></span>
|
||||
<span ng-if="folder.type == 'receiveencrypted'" class="fas fa-fw fa-lock"></span>
|
||||
</div>
|
||||
<div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
|
||||
<span class="hidden-xs">{{folderStatusText(folder)}}</span>
|
||||
<span ng-switch-when="scanning" ng-if="scanPercentage(folder.id) != undefined">({{scanPercentage(folder.id) | percent}})</span>
|
||||
<span ng-switch-when="syncing">({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>
|
||||
<span class="inline-icon">
|
||||
<span class="visible-xs fa fa-fw {{folderStatusIcon(folder)}}" aria-label="{{folderStatusText(folder)}}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-title-text">
|
||||
<span tooltip data-original-title="{{folder.label.length != 0 ? folder.id : ''}}">{{folder.label.length != 0 ? folder.label : folder.id}}</span>
|
||||
</div>
|
||||
</h4>
|
||||
</button>
|
||||
<div id="folder-{{$parent.$index}}-{{$index}}" class="panel-collapse collapse">
|
||||
<div class="panel-body less-padding">
|
||||
<table class="table table-condensed table-striped table-auto">
|
||||
<tbody>
|
||||
<tr class="visible-xs">
|
||||
<th><span class="fa fa-fw {{folderStatusIcon(folder)}}"></span> <span translate>Folder Status</span></th>
|
||||
<td class="text-right">{{folderStatusText(folder)}}</td>
|
||||
</tr>
|
||||
<tr ng-show="folder.label != undefined && folder.label.length > 0">
|
||||
<th><span class="fas fa-fw fa-info-circle"></span> <span translate>Folder ID</span></th>
|
||||
<td class="text-right no-overflow-ellipse">{{folder.id}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-folder-open"></span> <span translate>Folder Path</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{folder.path}}">{{folder.path}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.paused && (model[folder.id].invalid || model[folder.id].error)">
|
||||
<th><span class="fas fa-fw fa-exclamation-triangle"></span> <span translate>Error</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{model[folder.id].invalid || model[folder.id].error}}">{{model[folder.id].invalid || model[folder.id].error}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.paused">
|
||||
<th><span class="fas fa-fw fa-globe"></span> <span translate>Global State</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber | localeNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
|
||||
<span class="far fa-copy"></span> {{model[folder.id].globalFiles | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-folder"></span> {{model[folder.id].globalDirectories | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-hdd"></span> ~{{model[folder.id].globalBytes | binary}}B
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.paused">
|
||||
<th><span class="fas fa-fw fa-home"></span> <span translate>Local State</span></th>
|
||||
<td class="text-right">
|
||||
<div>
|
||||
<span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber | localeNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
|
||||
<span class="far fa-copy"></span> {{model[folder.id].localFiles | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-folder"></span> {{model[folder.id].localDirectories | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-hdd"></span> ~{{model[folder.id].localBytes | binary}}B
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="model[folder.id].ignorePatterns">
|
||||
<a href="" ng-click="editFolderExisting(folder, '#folder-ignores')"><i class="small" translate>Reduced by ignore patterns</i></a>
|
||||
</div>
|
||||
<div ng-if="folder.ignoreDelete">
|
||||
<i class="small">
|
||||
<span translate>Altered by ignoring deletes.</span>
|
||||
<a href="{{docsURL('advanced/folder-ignoredelete')}}" target="_blank">
|
||||
<span class="fas fa-question-circle"></span> <span translate>Help</span>
|
||||
</a>
|
||||
</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].needTotalItems > 0">
|
||||
<th><span class="fas fa-fw fa-cloud-download-alt"></span> <span translate>Out of Sync Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0">
|
||||
<th><span class="fas fa-fw fa-hourglass-half"></span> <span translate>Scan Time Remaining</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="hasFailedFiles(folder.id)">
|
||||
<th><span class="fas fa-fw fa-exclamation-circle"></span> <span translate>Failed Items</span></th>
|
||||
<!-- Show the number of failed items as a link to bring up the list. -->
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showFailed(folder.id)">{{model[folder.id].pullErrors | alwaysNumber | localeNumber}} <span translate>items</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="hasReceiveOnlyChanged(folder)">
|
||||
<th><span class="fas fa-fw fa-exclamation-circle"></span> <span translate>Locally Changed Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-folder"></span> <span translate>Folder Type</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="folder.type == 'sendreceive'" translate>Send & Receive</span>
|
||||
<span ng-if="folder.type == 'sendonly'" translate>Send Only</span>
|
||||
<span ng-if="folder.type == 'receiveonly'" translate>Receive Only</span>
|
||||
<span ng-if="folder.type == 'receiveencrypted'" translate>Receive Encrypted</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="far fa-fw fa-book"></span> <span translate>Block Indexing</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="folder.blockIndexing" translate>Yes</span>
|
||||
<span ng-if="!folder.blockIndexing" translate>No</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.ignorePerms">
|
||||
<th><span class="far fa-fw fa-minus-square"></span> <span translate>Ignore Permissions</span></th>
|
||||
<td class="text-right">
|
||||
<span translate>Yes</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-refresh"></span> <span translate>Rescans</span></th>
|
||||
<td class="text-right">
|
||||
<div ng-if="folder.rescanIntervalS > 0">
|
||||
<span ng-if="!folder.fsWatcherEnabled" tooltip data-original-title="{{'Periodic scanning at given interval and disabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Disabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && (!model[folder.id].watchError || folder.paused || folderStatus(folder) === 'stopped')" tooltip data-original-title="{{'Periodic scanning at given interval and enabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye"></span> <span translate>Enabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to set up, retrying</span>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="folder.rescanIntervalS <= 0">
|
||||
<span ng-if="!folder.fsWatcherEnabled" tooltip data-original-title="{{'Disabled periodic scanning and disabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Disabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && (!model[folder.id].watchError || folder.paused || folderStatus(folder) === 'stopped')" tooltip data-original-title="{{'Disabled periodic scanning and enabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye"></span> <span translate>Enabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to set up, retrying</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.type != 'sendonly'">
|
||||
<th><span class="fas fa-fw fa-sort"></span> <span translate>File Pull Order</span></th>
|
||||
<td class="text-right" ng-switch="folder.order">
|
||||
<span ng-switch-when="random" translate>Random</span>
|
||||
<span ng-switch-when="alphabetic" translate>Alphabetic</span>
|
||||
<span ng-switch-when="smallestFirst" translate>Smallest First</span>
|
||||
<span ng-switch-when="largestFirst" translate>Largest First</span>
|
||||
<span ng-switch-when="oldestFirst" translate>Oldest First</span>
|
||||
<span ng-switch-when="newestFirst" translate>Newest First</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.versioning.type">
|
||||
<th><span class="fa fa-fw fa-files-o"></span> <span translate>File Versioning</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-switch="folder.versioning.type">
|
||||
<span ng-switch-when="trashcan" translate>Trash Can</span>
|
||||
<span ng-switch-when="simple" translate>Simple</span>
|
||||
<span ng-switch-when="staggered" translate>Staggered</span>
|
||||
<span ng-switch-when="external" tooltip data-original-title="{{folder.versioning.params.command}}" translate>External</span>
|
||||
</span>
|
||||
<span ng-if="folder.versioning.type != 'external'">
|
||||
<span ng-if="(folder.versioning.type == 'trashcan' || folder.versioning.type == 'simple')" tooltip data-original-title="{{'Clean out after' | translate}}">
|
||||
 <span class="fa fa-calendar"></span> <span ng-if="folder.versioning.params.cleanoutDays == 0" translate>Disabled</span><span ng-if="folder.versioning.params.cleanoutDays > 0">{{folder.versioning.params.cleanoutDays * 86400 | duration:"d"}}</span>
|
||||
</span>
|
||||
<span ng-if="folder.versioning.type == 'simple'" tooltip data-original-title="{{'Keep Versions' | translate}}">
|
||||
 <span class="fa fa-file-archive-o"></span> {{folder.versioning.params.keep}}
|
||||
</span>
|
||||
<span ng-if="folder.versioning.type == 'staggered'" tooltip data-original-title="{{'Maximum Age' | translate}}">
|
||||
 <span class="fa fa-calendar"></span> <span ng-if="folder.versioning.params.maxAge == 0" translate>Forever</span><span ng-if="folder.versioning.params.maxAge > 0">{{folder.versioning.params.maxAge | duration}}</span>
|
||||
</span>
|
||||
<span tooltip data-original-title="{{'Cleanup Interval' | translate}}">
|
||||
 <span class="fa fa-recycle"></span> <span ng-if="folder.versioning.cleanupIntervalS == 0" translate>Disabled</span><span ng-if="folder.versioning.cleanupIntervalS > 0">{{folder.versioning.cleanupIntervalS | duration}}</span>
|
||||
</span>
|
||||
<!-- Keep the path last, so that it truncates without pushing other information out of the screen. -->
|
||||
<span tooltip data-original-title="{{folder.versioning.fsPath === '' ? '.stversions' : folder.versioning.fsPath}}">
|
||||
 <span class="fa fa-folder-open-o"></span> {{folder.versioning.fsPath === '' ? '.stversions' : folder.versioning.fsPath}}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-share-alt"></span> <span translate>Shared With</span></th>
|
||||
<td class="text-right no-overflow-ellipse overflow-break-word">
|
||||
<span ng-repeat="device in otherDevices(folder.devices)">
|
||||
<span ng-if="folder.type !== 'receiveencrypted' && device.encryptionPassword" class="text-nowrap">
|
||||
<span class="fa fa-lock"></span> <!-- Avoid stray space...
|
||||
--></span><!-- Avoid stray space...
|
||||
--><span ng-switch="completion[device.deviceID][folder.id].remoteState"><!-- Avoid stray space...
|
||||
--><a ng-switch-when="notSharing" href="" ng-click="editDeviceExisting(devices[device.deviceID])" data-original-title="{{'The remote device has not accepted sharing this folder.' | translate}}" tooltip>{{deviceName(devices[device.deviceID])}}<sup>1</sup></a><!-- Avoid stray space...
|
||||
--><a ng-switch-when="paused" href="" ng-click="editDeviceExisting(devices[device.deviceID])" data-original-title="{{'The remote device has paused this folder.' | translate}}" tooltip>{{deviceName(devices[device.deviceID])}}<sup>2</sup></a><!-- Avoid stray space...
|
||||
--><a ng-switch-default href="" ng-click="editDeviceExisting(devices[device.deviceID])">{{deviceName(devices[device.deviceID])}}</a><!-- Avoid stray space...
|
||||
--><span ng-if="!$last">,</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folderStats[folder.id].lastScan">
|
||||
<th><span class="far fa-fw fa-clock"></span> <span translate>Last Scan</span></th>
|
||||
<td translate ng-if="folderStats[folder.id].lastScanDays >= 365" class="text-right">Never</td>
|
||||
<td ng-if="folderStats[folder.id].lastScanDays < 365" class="text-right">
|
||||
<span>{{folderStats[folder.id].lastScan | date:'yyyy-MM-dd HH:mm:ss'}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.type != 'sendonly' && folder.type != 'receiveencrypted' && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename">
|
||||
<th><span class="fas fa-fw fa-exchange-alt"></span> <span translate>Latest Change</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
|
||||
<span translate translate-value-file="{{folderStats[folder.id].lastFile.filename | basename}}" ng-if="!folderStats[folder.id].lastFile.deleted">Updated {%file%}</span>
|
||||
<span translate translate-value-file="{{folderStats[folder.id].lastFile.filename | basename}}" ng-if="folderStats[folder.id].lastFile.deleted">Deleted {%file%}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-status pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
|
||||
<span class="hidden-xs">{{folderStatusText(folder)}}</span>
|
||||
<span ng-switch-when="scanning" ng-if="scanPercentage(folder.id) != undefined">({{scanPercentage(folder.id) | percent}})</span>
|
||||
<span ng-switch-when="syncing">({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>
|
||||
<span class="inline-icon">
|
||||
<span class="visible-xs fa fa-fw {{folderStatusIcon(folder)}}" aria-label="{{folderStatusText(folder)}}"></span>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('override', folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.type == 'sendonly'">
|
||||
<span class="fas fa-arrow-circle-up"></span> <span translate>Override Changes</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('revert', folder.id)" ng-if="hasReceiveOnlyChanged(folder) && ['outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) >= 0">
|
||||
<span class="fa fa-arrow-circle-down"></span> <span translate>Revert Local Changes</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('deleteEnc', folder.id)" ng-if="hasReceiveEncryptedItems(folder) && ['outofsync', 'faileditems', 'localunencrypted'].indexOf(folderStatus(folder)) >= 0">
|
||||
<span class="fa fa-minus-circle"></span> <span translate>Delete Unexpected Items</span>
|
||||
</button>
|
||||
<span class="pull-right">
|
||||
<button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
|
||||
<span class="fas fa-pause"></span> <span translate>Pause</span>
|
||||
</button>
|
||||
<button ng-if="folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, false)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="restoreVersions.show(folder.id)" ng-if="folder.versioning.type && folder.versioning.type != 'external'" ng-disabled="folder.paused">
|
||||
<span class="fas fa-undo"></span> <span translate>Versions</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
|
||||
<span class="fas fa-refresh"></span> <span translate>Rescan</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editFolderExisting(folder)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="panel-title-text">
|
||||
<span tooltip data-original-title="{{folder.label.length != 0 ? folder.id : ''}}">{{folder.label.length != 0 ? folder.label : folder.id}}</span>
|
||||
</div>
|
||||
</h4>
|
||||
</button>
|
||||
<div id="folder-{{$index}}" class="panel-collapse collapse">
|
||||
<div class="panel-body less-padding">
|
||||
<table class="table table-condensed table-striped table-auto">
|
||||
<tbody>
|
||||
<tr class="visible-xs">
|
||||
<th><span class="fa fa-fw {{folderStatusIcon(folder)}}"></span> <span translate>Folder Status</span></th>
|
||||
<td class="text-right">{{folderStatusText(folder)}}</td>
|
||||
</tr>
|
||||
<tr ng-show="folder.label != undefined && folder.label.length > 0">
|
||||
<th><span class="fas fa-fw fa-info-circle"></span> <span translate>Folder ID</span></th>
|
||||
<td class="text-right no-overflow-ellipse">{{folder.id}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-folder-open"></span> <span translate>Folder Path</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{folder.path}}">{{folder.path}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.paused && (model[folder.id].invalid || model[folder.id].error)">
|
||||
<th><span class="fas fa-fw fa-exclamation-triangle"></span> <span translate>Error</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{model[folder.id].invalid || model[folder.id].error}}">{{model[folder.id].invalid || model[folder.id].error}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.paused">
|
||||
<th><span class="fas fa-fw fa-globe"></span> <span translate>Global State</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber | localeNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
|
||||
<span class="far fa-copy"></span> {{model[folder.id].globalFiles | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-folder"></span> {{model[folder.id].globalDirectories | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-hdd"></span> ~{{model[folder.id].globalBytes | binary}}B
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!folder.paused">
|
||||
<th><span class="fas fa-fw fa-home"></span> <span translate>Local State</span></th>
|
||||
<td class="text-right">
|
||||
<div>
|
||||
<span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber | localeNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber | localeNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
|
||||
<span class="far fa-copy"></span> {{model[folder.id].localFiles | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-folder"></span> {{model[folder.id].localDirectories | alwaysNumber | localeNumber}} 
|
||||
<span class="far fa-hdd"></span> ~{{model[folder.id].localBytes | binary}}B
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="model[folder.id].ignorePatterns">
|
||||
<a href="" ng-click="editFolderExisting(folder, '#folder-ignores')"><i class="small" translate>Reduced by ignore patterns</i></a>
|
||||
</div>
|
||||
<div ng-if="folder.ignoreDelete">
|
||||
<i class="small">
|
||||
<span translate>Altered by ignoring deletes.</span>
|
||||
<a href="{{docsURL('advanced/folder-ignoredelete')}}" target="_blank">
|
||||
<span class="fas fa-question-circle"></span> <span translate>Help</span>
|
||||
</a>
|
||||
</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].needTotalItems > 0">
|
||||
<th><span class="fas fa-fw fa-cloud-download-alt"></span> <span translate>Out of Sync Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0">
|
||||
<th><span class="fas fa-fw fa-hourglass-half"></span> <span translate>Scan Time Remaining</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="hasFailedFiles(folder.id)">
|
||||
<th><span class="fas fa-fw fa-exclamation-circle"></span> <span translate>Failed Items</span></th>
|
||||
<!-- Show the number of failed items as a link to bring up the list. -->
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showFailed(folder.id)">{{model[folder.id].pullErrors | alwaysNumber | localeNumber}} <span translate>items</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="hasReceiveOnlyChanged(folder)">
|
||||
<th><span class="fas fa-fw fa-exclamation-circle"></span> <span translate>Locally Changed Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-folder"></span> <span translate>Folder Type</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="folder.type == 'sendreceive'" translate>Send & Receive</span>
|
||||
<span ng-if="folder.type == 'sendonly'" translate>Send Only</span>
|
||||
<span ng-if="folder.type == 'receiveonly'" translate>Receive Only</span>
|
||||
<span ng-if="folder.type == 'receiveencrypted'" translate>Receive Encrypted</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.ignorePerms">
|
||||
<th><span class="far fa-fw fa-minus-square"></span> <span translate>Ignore Permissions</span></th>
|
||||
<td class="text-right">
|
||||
<span translate>Yes</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-refresh"></span> <span translate>Rescans</span></th>
|
||||
<td class="text-right">
|
||||
<div ng-if="folder.rescanIntervalS > 0">
|
||||
<span ng-if="!folder.fsWatcherEnabled" tooltip data-original-title="{{'Periodic scanning at given interval and disabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Disabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && (!model[folder.id].watchError || folder.paused || folderStatus(folder) === 'stopped')" tooltip data-original-title="{{'Periodic scanning at given interval and enabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye"></span> <span translate>Enabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
|
||||
<span class="far fa-clock"></span> {{folder.rescanIntervalS | duration}} 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to set up, retrying</span>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="folder.rescanIntervalS <= 0">
|
||||
<span ng-if="!folder.fsWatcherEnabled" tooltip data-original-title="{{'Disabled periodic scanning and disabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Disabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && (!model[folder.id].watchError || folder.paused || folderStatus(folder) === 'stopped')" tooltip data-original-title="{{'Disabled periodic scanning and enabled watching for changes' | translate}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye"></span> <span translate>Enabled</span>
|
||||
</span>
|
||||
<span ng-if="folder.fsWatcherEnabled && !folder.paused && folderStatus(folder) !== 'stopped' && model[folder.id].watchError" tooltip data-original-title="{{'Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:' | translate}}<br/>{{model[folder.id].watchError}}">
|
||||
<span class="far fa-clock"></span> <span translate>Disabled</span> 
|
||||
<span class="fas fa-eye-slash"></span> <span translate>Failed to set up, retrying</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.type != 'sendonly'">
|
||||
<th><span class="fas fa-fw fa-sort"></span> <span translate>File Pull Order</span></th>
|
||||
<td class="text-right" ng-switch="folder.order">
|
||||
<span ng-switch-when="random" translate>Random</span>
|
||||
<span ng-switch-when="alphabetic" translate>Alphabetic</span>
|
||||
<span ng-switch-when="smallestFirst" translate>Smallest First</span>
|
||||
<span ng-switch-when="largestFirst" translate>Largest First</span>
|
||||
<span ng-switch-when="oldestFirst" translate>Oldest First</span>
|
||||
<span ng-switch-when="newestFirst" translate>Newest First</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.versioning.type">
|
||||
<th><span class="fa fa-fw fa-files-o"></span> <span translate>File Versioning</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-switch="folder.versioning.type">
|
||||
<span ng-switch-when="trashcan" translate>Trash Can</span>
|
||||
<span ng-switch-when="simple" translate>Simple</span>
|
||||
<span ng-switch-when="staggered" translate>Staggered</span>
|
||||
<span ng-switch-when="external" tooltip data-original-title="{{folder.versioning.params.command}}" translate>External</span>
|
||||
</span>
|
||||
<span ng-if="folder.versioning.type != 'external'">
|
||||
<span ng-if="(folder.versioning.type == 'trashcan' || folder.versioning.type == 'simple')" tooltip data-original-title="{{'Clean out after' | translate}}">
|
||||
 <span class="fa fa-calendar"></span> <span ng-if="folder.versioning.params.cleanoutDays == 0" translate>Disabled</span><span ng-if="folder.versioning.params.cleanoutDays > 0">{{folder.versioning.params.cleanoutDays * 86400 | duration:"d"}}</span>
|
||||
</span>
|
||||
<span ng-if="folder.versioning.type == 'simple'" tooltip data-original-title="{{'Keep Versions' | translate}}">
|
||||
 <span class="fa fa-file-archive-o"></span> {{folder.versioning.params.keep}}
|
||||
</span>
|
||||
<span ng-if="folder.versioning.type == 'staggered'" tooltip data-original-title="{{'Maximum Age' | translate}}">
|
||||
 <span class="fa fa-calendar"></span> <span ng-if="folder.versioning.params.maxAge == 0" translate>Forever</span><span ng-if="folder.versioning.params.maxAge > 0">{{folder.versioning.params.maxAge | duration}}</span>
|
||||
</span>
|
||||
<span tooltip data-original-title="{{'Cleanup Interval' | translate}}">
|
||||
 <span class="fa fa-recycle"></span> <span ng-if="folder.versioning.cleanupIntervalS == 0" translate>Disabled</span><span ng-if="folder.versioning.cleanupIntervalS > 0">{{folder.versioning.cleanupIntervalS | duration}}</span>
|
||||
</span>
|
||||
<!-- Keep the path last, so that it truncates without pushing other information out of the screen. -->
|
||||
<span tooltip data-original-title="{{folder.versioning.fsPath === '' ? '.stversions' : folder.versioning.fsPath}}">
|
||||
 <span class="fa fa-folder-open-o"></span> {{folder.versioning.fsPath === '' ? '.stversions' : folder.versioning.fsPath}}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-share-alt"></span> <span translate>Shared With</span></th>
|
||||
<td class="text-right no-overflow-ellipse overflow-break-word">
|
||||
<span ng-repeat="device in otherDevices(folder.devices)">
|
||||
<span ng-if="folder.type !== 'receiveencrypted' && device.encryptionPassword" class="text-nowrap">
|
||||
<span class="fa fa-lock"></span> <!-- Avoid stray space...
|
||||
--></span><!-- Avoid stray space...
|
||||
--><span ng-switch="completion[device.deviceID][folder.id].remoteState"><!-- Avoid stray space...
|
||||
--><a ng-switch-when="notSharing" href="" ng-click="editDeviceExisting(devices[device.deviceID])" data-original-title="{{'The remote device has not accepted sharing this folder.' | translate}}" tooltip>{{deviceName(devices[device.deviceID])}}<sup>1</sup></a><!-- Avoid stray space...
|
||||
--><a ng-switch-when="paused" href="" ng-click="editDeviceExisting(devices[device.deviceID])" data-original-title="{{'The remote device has paused this folder.' | translate}}" tooltip>{{deviceName(devices[device.deviceID])}}<sup>2</sup></a><!-- Avoid stray space...
|
||||
--><a ng-switch-default href="" ng-click="editDeviceExisting(devices[device.deviceID])">{{deviceName(devices[device.deviceID])}}</a><!-- Avoid stray space...
|
||||
--><span ng-if="!$last">,</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folderStats[folder.id].lastScan">
|
||||
<th><span class="far fa-fw fa-clock"></span> <span translate>Last Scan</span></th>
|
||||
<td translate ng-if="folderStats[folder.id].lastScanDays >= 365" class="text-right">Never</td>
|
||||
<td ng-if="folderStats[folder.id].lastScanDays < 365" class="text-right">
|
||||
<span>{{folderStats[folder.id].lastScan | date:'yyyy-MM-dd HH:mm:ss'}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.type != 'sendonly' && folder.type != 'receiveencrypted' && folderStats[folder.id].lastFile && folderStats[folder.id].lastFile.filename">
|
||||
<th><span class="fas fa-fw fa-exchange-alt"></span> <span translate>Latest Change</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm:ss'}}">
|
||||
<span translate translate-value-file="{{folderStats[folder.id].lastFile.filename | basename}}" ng-if="!folderStats[folder.id].lastFile.deleted">Updated {%file%}</span>
|
||||
<span translate translate-value-file="{{folderStats[folder.id].lastFile.filename | basename}}" ng-if="folderStats[folder.id].lastFile.deleted">Deleted {%file%}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('override', folder.id)" ng-if="folderStatus(folder) == 'outofsync' && folder.type == 'sendonly'">
|
||||
<span class="fas fa-arrow-circle-up"></span> <span translate>Override Changes</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('revert', folder.id)" ng-if="hasReceiveOnlyChanged(folder) && ['outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) >= 0">
|
||||
<span class="fa fa-arrow-circle-down"></span> <span translate>Revert Local Changes</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger pull-left" ng-click="revertOverrideConfirmationModal('deleteEnc', folder.id)" ng-if="hasReceiveEncryptedItems(folder) && ['outofsync', 'faileditems', 'localunencrypted'].indexOf(folderStatus(folder)) >= 0">
|
||||
<span class="fa fa-minus-circle"></span> <span translate>Delete Unexpected Items</span>
|
||||
</button>
|
||||
<span class="pull-right">
|
||||
<button ng-if="!folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, true)">
|
||||
<span class="fas fa-pause"></span> <span translate>Pause</span>
|
||||
</button>
|
||||
<button ng-if="folder.paused" type="button" class="btn btn-sm btn-default" ng-click="setFolderPause(folder.id, false)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-sm" ng-click="restoreVersions.show(folder.id)" ng-if="folder.versioning.type && folder.versioning.type != 'external'" ng-disabled="folder.paused">
|
||||
<span class="fas fa-undo"></span> <span translate>Versions</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="rescanFolder(folder.id)" ng-disabled="['idle', 'stopped', 'unshared', 'outofsync', 'faileditems', 'localadditions'].indexOf(folderStatus(folder)) < 0">
|
||||
<span class="fas fa-refresh"></span> <span translate>Rescan</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editFolderExisting(folder)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -770,215 +782,220 @@
|
||||
|
||||
<!-- Remote devices -->
|
||||
<h3><span translate>Remote Devices</span> <span ng-if="otherDevices().length > 1"> ({{otherDevices().length}})</span></h3>
|
||||
<div class="panel-group" id="devices">
|
||||
<div class="panel panel-default" ng-repeat="deviceCfg in otherDevices()">
|
||||
<button class="btn panel-heading" data-toggle="collapse" data-parent="#devices" data-target="#device-{{$index}}" aria-expanded="false">
|
||||
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | percent}}"></div>
|
||||
<h4 class="panel-title">
|
||||
<identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon>
|
||||
<div class="panel-status pull-right text-{{deviceClass(deviceCfg)}}" ng-switch="deviceStatus(deviceCfg)">
|
||||
<span class="hidden-xs">{{deviceStatusText(deviceCfg)}}</span>
|
||||
<span ng-switch-when="syncing">({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)</span>
|
||||
<span class="inline-icon">
|
||||
<span class="visible-xs fa fa-fw {{deviceStatusIcon(deviceCfg)}}" aria-label="{{deviceStatusText(deviceCfg)}}"></span>
|
||||
</span>
|
||||
<span class="inline-icon">
|
||||
<span ng-class="rdConnTypeIcon(rdConnType(deviceCfg.deviceID))" class="reception reception-theme"></span>
|
||||
</span>
|
||||
<div ng-repeat="(deviceGroupName, groupedDevices) in devicesGrouped">
|
||||
<h4>{{ deviceGroupName }}
|
||||
<span ng-if="groupedDevices.length > 1 && deviceGroupName.length > 0"> ({{groupedDevices.length}})</span>
|
||||
</h4>
|
||||
<div class="panel-group" id="devices-{{ $index }}">
|
||||
<div class="panel panel-default" ng-repeat="deviceCfg in groupedDevices">
|
||||
<button class="btn panel-heading" data-toggle="collapse" data-parent="#devices-{{ $parent.$index }}" data-target="#device-{{$parent.$index}}-{{$index}}" aria-expanded="false">
|
||||
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | percent}}"></div>
|
||||
<h4 class="panel-title">
|
||||
<identicon class="panel-icon" data-value="deviceCfg.deviceID"></identicon>
|
||||
<div class="panel-status pull-right text-{{deviceClass(deviceCfg)}}" ng-switch="deviceStatus(deviceCfg)">
|
||||
<span class="hidden-xs">{{deviceStatusText(deviceCfg)}}</span>
|
||||
<span ng-switch-when="syncing">({{completion[deviceCfg.deviceID]._total | percent}}, {{completion[deviceCfg.deviceID]._needBytes | binary}}B)</span>
|
||||
<span class="inline-icon">
|
||||
<span class="visible-xs fa fa-fw {{deviceStatusIcon(deviceCfg)}}" aria-label="{{deviceStatusText(deviceCfg)}}"></span>
|
||||
</span>
|
||||
<span class="inline-icon">
|
||||
<span ng-class="rdConnTypeIcon(rdConnType(deviceCfg.deviceID))" class="reception reception-theme"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-title-text">{{deviceName(deviceCfg)}}</div>
|
||||
</h4>
|
||||
</button>
|
||||
<div id="device-{{$parent.$index}}-{{$index}}" class="panel-collapse collapse">
|
||||
<div class="panel-body less-padding">
|
||||
<table class="table table-condensed table-striped table-auto">
|
||||
<tbody>
|
||||
<tr class="visible-xs">
|
||||
<th><span class="fa fa-fw {{deviceStatusIcon(deviceCfg)}}"></span> <span translate>Device Status</span></th>
|
||||
<td class="text-right">{{deviceStatusText(deviceCfg)}}</td>
|
||||
</tr>
|
||||
<tr ng-if="!connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-eye"></span> <span translate>Last seen</span></th>
|
||||
<td class="text-right">
|
||||
<div ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays" translate>
|
||||
Never
|
||||
</div>
|
||||
<div ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays">
|
||||
<div>
|
||||
{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}
|
||||
</div>
|
||||
<div ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays >= 7">
|
||||
<i ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 30" translate>More than a week ago</i>
|
||||
<i class="text-warning" ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays >= 30 && deviceStats[deviceCfg.deviceID].lastSeenDays < 365" translate>More than a month ago</i>
|
||||
<i class="text-danger" ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" translate>More than a year ago</i>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!connections[deviceCfg.deviceID].connected && deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="fas fa-fw fa-cloud"></span> <span translate>Sync Status</span></th>
|
||||
<td translate ng-if="completion[deviceCfg.deviceID]._total == 100" class="text-right">Up to Date</td>
|
||||
<td ng-if="completion[deviceCfg.deviceID]._total < 100" class="text-right">
|
||||
<span class="hidden-xs" translate>Out of Sync</span> ({{completion[deviceCfg.deviceID]._total | percent}})
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-cloud-download-alt"></span> <span translate>Download Rate</span></th>
|
||||
<td class="text-right">
|
||||
<a href="#" class="toggler" ng-click="toggleUnits()">
|
||||
<span ng-if="!metricRates">{{connections[deviceCfg.deviceID].inbps | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{connections[deviceCfg.deviceID].inbps*8 | metric}}bps</span>
|
||||
({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)
|
||||
<small ng-if="deviceCfg.maxRecvKbps > 0"><br/>
|
||||
<i class="text-muted"><span translate>Limit</span>:
|
||||
<span ng-if="!metricRates">{{deviceCfg.maxRecvKbps*1024 | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{deviceCfg.maxRecvKbps*1024*8 | metric}}bps</span>
|
||||
</i>
|
||||
</small>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-cloud-upload-alt"></span> <span translate>Upload Rate</span></th>
|
||||
<td class="text-right">
|
||||
<a href="#" class="toggler" ng-click="toggleUnits()">
|
||||
<span ng-if="!metricRates">{{connections[deviceCfg.deviceID].outbps | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{connections[deviceCfg.deviceID].outbps*8 | metric}}bps</span>
|
||||
({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)
|
||||
<small ng-if="deviceCfg.maxSendKbps > 0"><br/>
|
||||
<i class="text-muted"><span translate>Limit</span>:
|
||||
<span ng-if="!metricRates">{{deviceCfg.maxSendKbps*1024 | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{deviceCfg.maxSendKbps*1024*8 | metric}}bps</span>
|
||||
</i>
|
||||
</small>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="completion[deviceCfg.deviceID]._needItems">
|
||||
<th><span class="fas fa-fw fa-exchange-alt"></span> <span translate>Out of Sync Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showRemoteNeed(deviceCfg)">{{completion[deviceCfg.deviceID]._needItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{completion[deviceCfg.deviceID]._needBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-link"></span> <span translate>Address</span></th>
|
||||
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }} {{ connections[deviceCfg.deviceID].crypto }}">
|
||||
{{deviceAddr(deviceCfg)}}
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="!connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
<span ng-repeat="addr in deviceCfg.addresses">
|
||||
<span tooltip data-original-title="{{'Configured' | translate}}">{{addr}}</span><br>
|
||||
<small ng-if="system.lastDialStatus[addr].error && !deviceCfg.paused" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
</span>
|
||||
<span ng-repeat="addr in discoveryCache[deviceCfg.deviceID].addresses">
|
||||
<span tooltip data-original-title="{{'Discovered' | translate}}">{{addr}}</span><br>
|
||||
<small ng-if="system.lastDialStatus[addr].error && !deviceCfg.paused" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="reception reception-4 reception-theme"></span> <span translate>Connection Type</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{rdConnDetails(rdConnType(deviceCfg.deviceID))}}">
|
||||
{{rdConnTypeString(rdConnType(deviceCfg.deviceID))}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-random"></span> <span translate>Number of Connections</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="connections[deviceCfg.deviceID].secondary.length">1 + {{connections[deviceCfg.deviceID].secondary.length | alwaysNumber}}</span>
|
||||
<span ng-if="!connections[deviceCfg.deviceID].secondary.length">1</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.allowedNetworks.length > 0">
|
||||
<th><span class="fas fa-fw fa-filter"></span> <span translate>Allowed Networks</span></th>
|
||||
<td class="text-right">
|
||||
<span>{{deviceCfg.allowedNetworks.join(", ")}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-compress"></span> <span translate>Compression</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="deviceCfg.compression == 'always'" translate>All Data</span>
|
||||
<span ng-if="deviceCfg.compression == 'metadata'" translate>Metadata Only</span>
|
||||
<span ng-if="deviceCfg.compression == 'never'" translate>Off</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.introducer">
|
||||
<th><span class="far fa-fw fa-thumbs-up"></span> <span translate>Introducer</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.introducedBy">
|
||||
<th><span class="far fa-fw fa-handshake-o"></span> <span translate>Introduced By</span></th>
|
||||
<td class="text-right">{{ deviceName(devices[deviceCfg.introducedBy]) || deviceShortID(deviceCfg.introducedBy) }}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.autoAcceptFolders">
|
||||
<th><span class="fa fa-fw fa-level-down"></span> <span translate>Auto Accept</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-qrcode"></span> <span translate>Identification</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{'Click to see full identification string and QR code.' | translate}}">
|
||||
<a href="" ng-click="showDeviceIdentification(deviceCfg)">{{deviceShortID(deviceCfg.deviceID)}}</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.untrusted">
|
||||
<th><span class="fa fa-fw fa-user-secret"></span> <span translate>Untrusted</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
|
||||
<th><span class="fas fa-fw fa-tag"></span> <span translate>Version</span></th>
|
||||
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="fas fa-fw fa-folder"></span> <span translate>Folders</span></th>
|
||||
<td class="text-right no-overflow-ellipse overflow-break-word">
|
||||
<span ng-repeat="folderID in deviceFolders(deviceCfg)">
|
||||
<span ng-if="folderIsSharedEncrypted(folderID, deviceCfg.deviceID)" class="text-nowrap">
|
||||
<span class="fa fa-lock"></span> <!-- Avoid stray space...
|
||||
--></span><!-- Avoid stray space...
|
||||
--><span ng-switch="completion[deviceCfg.deviceID][folderID].remoteState"><!-- Avoid stray space...
|
||||
--><span ng-switch-when="notSharing" data-original-title="{{'The remote device has not accepted sharing this folder.' | translate}}" tooltip>{{folderLabel(folderID)}}<sup>1</sup></span><!-- Avoid stray space...
|
||||
--><span ng-switch-when="paused" data-original-title="{{'The remote device has paused this folder.' | translate}}" tooltip>{{folderLabel(folderID)}}<sup>2</sup></span><!-- Avoid stray space...
|
||||
--><span ng-switch-default>{{folderLabel(folderID)}}</span><!-- Avoid stray space...
|
||||
--><span ng-if="!$last">,</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.remoteGUIPort > 0">
|
||||
<th><span class="fas fa-fw fa-desktop"></span> <span translate>Remote GUI</span></th>
|
||||
<td class="text-right" ng-attr-title="Port {{deviceCfg.remoteGUIPort}}">
|
||||
<!-- Apply RFC6874 encoding for IPv6 link-local zone identifier -->
|
||||
<a ng-if="hasRemoteGUIAddress(deviceCfg)" href="{{remoteGUIAddress(deviceCfg).replace('%', '%25')}}">{{remoteGUIAddress(deviceCfg)}}</a>
|
||||
<span translate ng-if="!hasRemoteGUIAddress(deviceCfg)">Unknown</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="pull-right">
|
||||
<button ng-if="!deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, true)">
|
||||
<span class="fas fa-pause"></span> <span translate>Pause</span>
|
||||
</button>
|
||||
<button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editDeviceExisting(deviceCfg)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-title-text">{{deviceName(deviceCfg)}}</div>
|
||||
</h4>
|
||||
</button>
|
||||
<div id="device-{{$index}}" class="panel-collapse collapse">
|
||||
<div class="panel-body less-padding">
|
||||
<table class="table table-condensed table-striped table-auto">
|
||||
<tbody>
|
||||
<tr class="visible-xs">
|
||||
<th><span class="fa fa-fw {{deviceStatusIcon(deviceCfg)}}"></span> <span translate>Device Status</span></th>
|
||||
<td class="text-right">{{deviceStatusText(deviceCfg)}}</td>
|
||||
</tr>
|
||||
<tr ng-if="!connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-eye"></span> <span translate>Last seen</span></th>
|
||||
<td class="text-right">
|
||||
<div ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays" translate>
|
||||
Never
|
||||
</div>
|
||||
<div ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays">
|
||||
<div>
|
||||
{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm:ss"}}
|
||||
</div>
|
||||
<div ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays >= 7">
|
||||
<i ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 30" translate>More than a week ago</i>
|
||||
<i class="text-warning" ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays >= 30 && deviceStats[deviceCfg.deviceID].lastSeenDays < 365" translate>More than a month ago</i>
|
||||
<i class="text-danger" ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" translate>More than a year ago</i>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!connections[deviceCfg.deviceID].connected && deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="fas fa-fw fa-cloud"></span> <span translate>Sync Status</span></th>
|
||||
<td translate ng-if="completion[deviceCfg.deviceID]._total == 100" class="text-right">Up to Date</td>
|
||||
<td ng-if="completion[deviceCfg.deviceID]._total < 100" class="text-right">
|
||||
<span class="hidden-xs" translate>Out of Sync</span> ({{completion[deviceCfg.deviceID]._total | percent}})
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-cloud-download-alt"></span> <span translate>Download Rate</span></th>
|
||||
<td class="text-right">
|
||||
<a href="#" class="toggler" ng-click="toggleUnits()">
|
||||
<span ng-if="!metricRates">{{connections[deviceCfg.deviceID].inbps | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{connections[deviceCfg.deviceID].inbps*8 | metric}}bps</span>
|
||||
({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)
|
||||
<small ng-if="deviceCfg.maxRecvKbps > 0"><br/>
|
||||
<i class="text-muted"><span translate>Limit</span>:
|
||||
<span ng-if="!metricRates">{{deviceCfg.maxRecvKbps*1024 | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{deviceCfg.maxRecvKbps*1024*8 | metric}}bps</span>
|
||||
</i>
|
||||
</small>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-cloud-upload-alt"></span> <span translate>Upload Rate</span></th>
|
||||
<td class="text-right">
|
||||
<a href="#" class="toggler" ng-click="toggleUnits()">
|
||||
<span ng-if="!metricRates">{{connections[deviceCfg.deviceID].outbps | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{connections[deviceCfg.deviceID].outbps*8 | metric}}bps</span>
|
||||
({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)
|
||||
<small ng-if="deviceCfg.maxSendKbps > 0"><br/>
|
||||
<i class="text-muted"><span translate>Limit</span>:
|
||||
<span ng-if="!metricRates">{{deviceCfg.maxSendKbps*1024 | binary}}B/s</span>
|
||||
<span ng-if="metricRates">{{deviceCfg.maxSendKbps*1024*8 | metric}}bps</span>
|
||||
</i>
|
||||
</small>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="completion[deviceCfg.deviceID]._needItems">
|
||||
<th><span class="fas fa-fw fa-exchange-alt"></span> <span translate>Out of Sync Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showRemoteNeed(deviceCfg)">{{completion[deviceCfg.deviceID]._needItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{completion[deviceCfg.deviceID]._needBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-link"></span> <span translate>Address</span></th>
|
||||
<td ng-if="connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
<span tooltip data-original-title="{{ connections[deviceCfg.deviceID].type.indexOf('Relay') > -1 ? '' : connections[deviceCfg.deviceID].type }} {{ connections[deviceCfg.deviceID].crypto }}">
|
||||
{{deviceAddr(deviceCfg)}}
|
||||
</span>
|
||||
</td>
|
||||
<td ng-if="!connections[deviceCfg.deviceID].connected" class="text-right">
|
||||
<span ng-repeat="addr in deviceCfg.addresses">
|
||||
<span tooltip data-original-title="{{'Configured' | translate}}">{{addr}}</span><br>
|
||||
<small ng-if="system.lastDialStatus[addr].error && !deviceCfg.paused" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
</span>
|
||||
<span ng-repeat="addr in discoveryCache[deviceCfg.deviceID].addresses">
|
||||
<span tooltip data-original-title="{{'Discovered' | translate}}">{{addr}}</span><br>
|
||||
<small ng-if="system.lastDialStatus[addr].error && !deviceCfg.paused" tooltip data-original-title="{{system.lastDialStatus[addr].error}}" class="text-danger">{{abbreviatedError(addr)}}<br></small>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="reception reception-4 reception-theme"></span> <span translate>Connection Type</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{rdConnDetails(rdConnType(deviceCfg.deviceID))}}">
|
||||
{{rdConnTypeString(rdConnType(deviceCfg.deviceID))}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].connected">
|
||||
<th><span class="fas fa-fw fa-random"></span> <span translate>Number of Connections</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="connections[deviceCfg.deviceID].secondary.length">1 + {{connections[deviceCfg.deviceID].secondary.length | alwaysNumber}}</span>
|
||||
<span ng-if="!connections[deviceCfg.deviceID].secondary.length">1</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.allowedNetworks.length > 0">
|
||||
<th><span class="fas fa-fw fa-filter"></span> <span translate>Allowed Networks</span></th>
|
||||
<td class="text-right">
|
||||
<span>{{deviceCfg.allowedNetworks.join(", ")}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-compress"></span> <span translate>Compression</span></th>
|
||||
<td class="text-right">
|
||||
<span ng-if="deviceCfg.compression == 'always'" translate>All Data</span>
|
||||
<span ng-if="deviceCfg.compression == 'metadata'" translate>Metadata Only</span>
|
||||
<span ng-if="deviceCfg.compression == 'never'" translate>Off</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.introducer">
|
||||
<th><span class="far fa-fw fa-thumbs-up"></span> <span translate>Introducer</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.introducedBy">
|
||||
<th><span class="far fa-fw fa-handshake-o"></span> <span translate>Introduced By</span></th>
|
||||
<td class="text-right">{{ deviceName(devices[deviceCfg.introducedBy]) || deviceShortID(deviceCfg.introducedBy) }}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.autoAcceptFolders">
|
||||
<th><span class="fa fa-fw fa-level-down"></span> <span translate>Auto Accept</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span class="fas fa-fw fa-qrcode"></span> <span translate>Identification</span></th>
|
||||
<td class="text-right">
|
||||
<span tooltip data-original-title="{{'Click to see full identification string and QR code.' | translate}}">
|
||||
<a href="" ng-click="showDeviceIdentification(deviceCfg)">{{deviceShortID(deviceCfg.deviceID)}}</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.untrusted">
|
||||
<th><span class="fa fa-fw fa-user-secret"></span> <span translate>Untrusted</span></th>
|
||||
<td translate class="text-right">Yes</td>
|
||||
</tr>
|
||||
<tr ng-if="connections[deviceCfg.deviceID].clientVersion">
|
||||
<th><span class="fas fa-fw fa-tag"></span> <span translate>Version</span></th>
|
||||
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceFolders(deviceCfg).length > 0">
|
||||
<th><span class="fas fa-fw fa-folder"></span> <span translate>Folders</span></th>
|
||||
<td class="text-right no-overflow-ellipse overflow-break-word">
|
||||
<span ng-repeat="folderID in deviceFolders(deviceCfg)">
|
||||
<span ng-if="folderIsSharedEncrypted(folderID, deviceCfg.deviceID)" class="text-nowrap">
|
||||
<span class="fa fa-lock"></span> <!-- Avoid stray space...
|
||||
--></span><!-- Avoid stray space...
|
||||
--><span ng-switch="completion[deviceCfg.deviceID][folderID].remoteState"><!-- Avoid stray space...
|
||||
--><span ng-switch-when="notSharing" data-original-title="{{'The remote device has not accepted sharing this folder.' | translate}}" tooltip>{{folderLabel(folderID)}}<sup>1</sup></span><!-- Avoid stray space...
|
||||
--><span ng-switch-when="paused" data-original-title="{{'The remote device has paused this folder.' | translate}}" tooltip>{{folderLabel(folderID)}}<sup>2</sup></span><!-- Avoid stray space...
|
||||
--><span ng-switch-default>{{folderLabel(folderID)}}</span><!-- Avoid stray space...
|
||||
--><span ng-if="!$last">,</span>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="deviceCfg.remoteGUIPort > 0">
|
||||
<th><span class="fas fa-fw fa-desktop"></span> <span translate>Remote GUI</span></th>
|
||||
<td class="text-right" ng-attr-title="Port {{deviceCfg.remoteGUIPort}}">
|
||||
<!-- Apply RFC6874 encoding for IPv6 link-local zone identifier -->
|
||||
<a ng-if="hasRemoteGUIAddress(deviceCfg)" href="{{remoteGUIAddress(deviceCfg).replace('%', '%25')}}">{{remoteGUIAddress(deviceCfg)}}</a>
|
||||
<span translate ng-if="!hasRemoteGUIAddress(deviceCfg)">Unknown</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="pull-right">
|
||||
<button ng-if="!deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, true)">
|
||||
<span class="fas fa-pause"></span> <span translate>Pause</span>
|
||||
</button>
|
||||
<button ng-if="deviceCfg.paused" type="button" class="btn btn-sm btn-default" ng-click="setDevicePause(deviceCfg.deviceID, false)">
|
||||
<span class="fas fa-play"></span> <span translate>Resume</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="editDeviceExisting(deviceCfg)">
|
||||
<span class="fas fa-pencil-alt"></span> <span translate>Edit</span>
|
||||
</button>
|
||||
</span>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="pull-right">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="setAllDevicesPause(true)" ng-if="isAtleastOneDevicePausedStateSetTo(false)">
|
||||
@@ -1034,7 +1051,7 @@
|
||||
<ng-include src="'syncthing/device/shareDeviceIdDialogView.html'"></ng-include>
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script type="text/javascript" src="vendor/jquery/jquery-2.2.2.js"></script>
|
||||
<script type="text/javascript" src="vendor/jquery/jquery-3.7.1.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script>
|
||||
<script type="text/javascript" src="vendor/angular/angular-translate.js"></script>
|
||||
|
||||
@@ -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, 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, Epifeny, epifeny, 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, Maxwell G, Michael Jephcote, Michael Rienstra, Michael Wang 汪東陽, 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, Prathik P Kulkarni, 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, Shivam Kumar, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Tao, 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, Val Markovic, 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 Norcombe, 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_, cui, 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, Epifeny, epifeny, 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, JRNitre, 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, Luiz Angelo Daros de Luca, 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, Maxwell G, Michael Jephcote, Michael Rienstra, Michael Wang 汪東陽, 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, Prathik P Kulkarni, pullmerge, Quentin Hibon, Rahmi Pruitt, RealCharlesChia, 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, Shivam Kumar, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Tao, 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, Umer-Azaz, Unrud, Val Markovic, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, vvaswani, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
<span ng-switch-default ng-switch="selected[id]">
|
||||
<span ng-switch-when='true' ng-switch="untrusted">
|
||||
<span ng-switch-when='true' ng-class="{'has-error': !encryptionPasswords[id]}">
|
||||
<input class="form-control input-sm" type="{{plain ? 'text' : 'password'}}" ng-model="encryptionPasswords[id]" required placeholder="{{'Device is untrusted, enter encryption password' | translate}}" />
|
||||
<input class="form-control input-sm" type="{{plain ? 'text' : 'password'}}" ng-model="encryptionPasswords[id]" autocomplete="off" required placeholder="{{'Device is untrusted, enter encryption password' | translate}}" />
|
||||
</span>
|
||||
<span ng-switch-default>
|
||||
<input class="form-control input-sm" type="{{plain ? 'text' : 'password'}}" ng-model="encryptionPasswords[id]" placeholder="{{'If untrusted, enter encryption password' | translate}}" />
|
||||
<input class="form-control input-sm" type="{{plain ? 'text' : 'password'}}" ng-model="encryptionPasswords[id]" autocomplete="off" placeholder="{{'If untrusted, enter encryption password' | translate}}" />
|
||||
</span>
|
||||
</span>
|
||||
<span ng-switch-default>
|
||||
|
||||
@@ -81,12 +81,14 @@ angular.module('syncthing.core')
|
||||
$scope.model = {};
|
||||
$scope.myID = '';
|
||||
$scope.devices = {};
|
||||
$scope.devicesGrouped = {};
|
||||
$scope.discoveryCache = {};
|
||||
$scope.protocolChanged = false;
|
||||
$scope.reportData = {};
|
||||
$scope.reportDataPreview = '';
|
||||
$scope.reportPreview = false;
|
||||
$scope.folders = {};
|
||||
$scope.foldersGrouped = {};
|
||||
$scope.seenError = '';
|
||||
$scope.upgradeInfo = null;
|
||||
$scope.deviceStats = {};
|
||||
@@ -565,15 +567,43 @@ angular.module('syncthing.core')
|
||||
_needBytes: 0,
|
||||
_needItems: 0
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// myID is watched as $scope.otherDevices() relies on this
|
||||
// and it can potenitally not be loaded due to this function
|
||||
// scope being called in an undetermistic manner
|
||||
$scope.$watch('myID', function(myID) {
|
||||
if (myID) {
|
||||
$scope.devicesGrouped = {};
|
||||
const otherDevices = $scope.otherDevices();
|
||||
for (var id in otherDevices) {
|
||||
if ($scope.devicesGrouped[otherDevices[id].group] === undefined) {
|
||||
$scope.devicesGrouped[otherDevices[id].group] = [];
|
||||
}
|
||||
$scope.devicesGrouped[otherDevices[id].group].push(otherDevices[id]);
|
||||
};
|
||||
|
||||
$scope.devicesGrouped = sortByKeyThenProperty($scope.devicesGrouped, "name", "deviceID");
|
||||
}
|
||||
});
|
||||
|
||||
$scope.folders = folderMap($scope.config.folders);
|
||||
$scope.foldersGrouped = {};
|
||||
Object.keys($scope.folders).forEach(function (folder) {
|
||||
refreshFolder(folder);
|
||||
$scope.folders[folder].devices.forEach(function (deviceCfg) {
|
||||
refreshCompletion(deviceCfg.deviceID, folder);
|
||||
});
|
||||
|
||||
if ($scope.foldersGrouped[$scope.folders[folder].group] === undefined) {
|
||||
$scope.foldersGrouped[$scope.folders[folder].group] = [];
|
||||
}
|
||||
$scope.foldersGrouped[$scope.folders[folder].group].push($scope.folders[folder]);
|
||||
});
|
||||
|
||||
$scope.foldersGrouped = sortByKeyThenProperty($scope.foldersGrouped, "label", "id");
|
||||
|
||||
refreshNoAuthWarning();
|
||||
setDefaultTheme();
|
||||
|
||||
@@ -582,6 +612,31 @@ angular.module('syncthing.core')
|
||||
}
|
||||
}
|
||||
|
||||
// Sort firstly by the top level key of the object and then by
|
||||
// prop name provided for the array of objects for each key.
|
||||
// If the prop returns has an empty value, then use the
|
||||
// fallback prop provided.
|
||||
function sortByKeyThenProperty(obj, prop, fallbackProp) {
|
||||
const sorted = {};
|
||||
Object.keys(obj)
|
||||
.sort()
|
||||
.forEach((key) => {
|
||||
sorted[key] = obj[key].sort((a, b) => {
|
||||
let aProp = prop;
|
||||
let bProp = prop;
|
||||
if (!a[aProp]) {
|
||||
aProp = fallbackProp;
|
||||
}
|
||||
if (!b[bProp]) {
|
||||
bProp = fallbackProp;
|
||||
}
|
||||
return a[aProp].localeCompare(b[bProp]);
|
||||
});
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function refreshSystem() {
|
||||
return $http.get(urlbase + '/system/status').success(function (data) {
|
||||
$scope.myID = data.myID;
|
||||
@@ -1067,7 +1122,7 @@ angular.module('syncthing.core')
|
||||
if (status == 'paused') {
|
||||
return 'default';
|
||||
}
|
||||
if (status === 'syncing' || status === 'sync-preparing' || status === 'scanning' || status === 'cleaning') {
|
||||
if (status === 'syncing' || status === 'sync-preparing' || status === 'scanning' || status === 'cleaning' || status === 'starting') {
|
||||
return 'primary';
|
||||
}
|
||||
if (status === 'unknown') {
|
||||
@@ -1265,6 +1320,7 @@ angular.module('syncthing.core')
|
||||
case 'scan-waiting':
|
||||
case 'sync-preparing':
|
||||
case 'sync-waiting':
|
||||
case 'starting':
|
||||
return 'fa-hourglass-half';
|
||||
case 'cleaning':
|
||||
return 'fa-recycle';
|
||||
@@ -1300,6 +1356,8 @@ angular.module('syncthing.core')
|
||||
return $translate.instant('Failed Items');
|
||||
case 'idle':
|
||||
return $translate.instant('Up to Date');
|
||||
case 'starting':
|
||||
return $translate.instant('Starting');
|
||||
case 'localadditions':
|
||||
return $translate.instant('Local Additions');
|
||||
case 'localunencrypted':
|
||||
@@ -2279,6 +2337,12 @@ angular.module('syncthing.core')
|
||||
} else {
|
||||
$scope.currentFolder.fsWatcherEnabled = true;
|
||||
}
|
||||
var type = $scope.currentFolder.type;
|
||||
if ($scope.currentFolder._editing !== 'existing') {
|
||||
// Never automatically change block indexing, only suggest
|
||||
// the value on new folder creation.
|
||||
$scope.currentFolder.blockIndexing = (type === 'sendreceive' || type === 'receiveonly');
|
||||
}
|
||||
$scope.setFSWatcherIntervalDefault();
|
||||
};
|
||||
|
||||
@@ -2658,6 +2722,27 @@ angular.module('syncthing.core')
|
||||
}
|
||||
}, $scope.emitHTTPError);
|
||||
};
|
||||
|
||||
$scope.isFolderTabDisabled = function (tab) {
|
||||
if (!$scope.currentFolder) {
|
||||
return false;
|
||||
}
|
||||
if ($scope.currentFolder._editing === "new-ignores") {
|
||||
return tab !== "ignores";
|
||||
}
|
||||
if (tab === "ignores" && $scope.currentFolder._recvEnc) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.onFolderTabClick = function ($event, tab) {
|
||||
if ($scope.isFolderTabDisabled(tab)) {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function saveFolderIgnoresExisting() {
|
||||
if ($scope.ignores.disabled) {
|
||||
|
||||
@@ -57,6 +57,16 @@
|
||||
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
|
||||
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': deviceEditor.deviceGroup.$invalid && deviceEditor.deviceGroup.$dirty && !editingDeviceDefaults()}">
|
||||
<label for="deviceGroup"><span translate>Device Group</span></label>
|
||||
<input name="deviceGroup" id="deviceGroup" class="form-control" type="text" ng-model="currentDevice.group" value="{{currentDevice.group}}" list="device-group-list"/>
|
||||
<datalist id="device-group-list">
|
||||
<option ng-repeat="(group, device) in devicesGrouped" value="{{group}}" />
|
||||
</datalist>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="deviceEditor.deviceGroup.$valid || deviceEditor.deviceGroup.$pristine">Optional group for the device. Can be different on each device.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="!editingDeviceDefaults()" id="device-sharing" class="tab-pane">
|
||||
<div class="row">
|
||||
|
||||
@@ -2,11 +2,46 @@
|
||||
<div class="modal-body">
|
||||
<form role="form" name="folderEditor">
|
||||
<ul class="nav nav-tabs" ng-init="loadFormIntoScope(folderEditor)">
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'new-ignores'}" class="active"><a data-toggle="tab" href="{{currentFolder._editing == 'new-ignores' ? '' : '#folder-general'}}"><span class="fas fa-cog"></span> <span translate>General</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'new-ignores'}"><a data-toggle="tab" href="{{currentFolder._editing == 'new-ignores' ? '' : '#folder-sharing'}}"><span class="fas fa-share-alt"></span> <span translate>Sharing</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'new-ignores'}"><a data-toggle="tab" href="{{currentFolder._editing == 'new-ignores' ? '' : '#folder-versioning'}}"><span class="fa fa-files-o"></span> <span translate>File Versioning</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._recvEnc}"><a data-toggle="tab" href="{{currentFolder._recvEnc ? '' : '#folder-ignores'}}"><span class="fas fa-filter"></span> <span translate>Ignore Patterns</span></a></li>
|
||||
<li ng-class="{'disabled': currentFolder._editing == 'new-ignores'}"><a data-toggle="tab" href="{{currentFolder._editing == 'new-ignores' ? '' : '#folder-advanced'}}"><span class="fas fa-cogs"></span> <span translate>Advanced</span></a></li>
|
||||
<li ng-class="{disabled: isFolderTabDisabled('general')}">
|
||||
<a
|
||||
ng-attr-data-toggle="{{isFolderTabDisabled('general') ? undefined : 'tab'}}"
|
||||
ng-attr-href="{{isFolderTabDisabled('general') ? undefined : '#folder-general'}}"
|
||||
ng-click="onFolderTabClick($event, 'general')">
|
||||
<span class="fas fa-cog"></span> <span translate>General</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{disabled: isFolderTabDisabled('sharing')}">
|
||||
<a
|
||||
ng-attr-data-toggle="{{isFolderTabDisabled('sharing') ? undefined : 'tab'}}"
|
||||
ng-attr-href="{{isFolderTabDisabled('sharing') ? undefined : '#folder-sharing'}}"
|
||||
ng-click="onFolderTabClick($event, 'sharing')">
|
||||
<span class="fas fa-share-alt"></span> <span translate>Sharing</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{disabled: isFolderTabDisabled('versioning')}">
|
||||
<a
|
||||
ng-attr-data-toggle="{{isFolderTabDisabled('versioning') ? undefined : 'tab'}}"
|
||||
ng-attr-href="{{isFolderTabDisabled('versioning') ? undefined : '#folder-versioning'}}"
|
||||
ng-click="onFolderTabClick($event, 'versioning')">
|
||||
<span class="fa fa-files-o"></span> <span translate>File Versioning</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{disabled: isFolderTabDisabled('ignores')}">
|
||||
<a
|
||||
ng-attr-data-toggle="{{isFolderTabDisabled('ignores') ? undefined : 'tab'}}"
|
||||
ng-attr-href="{{isFolderTabDisabled('ignores') ? undefined : '#folder-ignores'}}"
|
||||
ng-click="onFolderTabClick($event, 'ignores')">
|
||||
<span class="fas fa-filter"></span> <span translate>Ignore Patterns</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{disabled: isFolderTabDisabled('advanced')}">
|
||||
<a
|
||||
ng-attr-data-toggle="{{isFolderTabDisabled('advanced') ? undefined : 'tab'}}"
|
||||
ng-attr-href="{{isFolderTabDisabled('advanced') ? undefined : '#folder-advanced'}}"
|
||||
ng-click="onFolderTabClick($event, 'advanced')">
|
||||
<span class="fas fa-cogs"></span> <span translate>Advanced</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
|
||||
@@ -18,6 +53,16 @@
|
||||
<span translate ng-if="folderEditor.folderLabel.$valid || folderEditor.folderLabel.$pristine">Optional descriptive label for the folder. Can be different on each device.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{'has-error': folderEditor.folderGroup.$invalid && folderEditor.folderGroup.$dirty && !editingFolderDefaults()}">
|
||||
<label for="folderGroup"><span translate>Folder Group</span></label>
|
||||
<input name="folderGroup" id="folderGroup" class="form-control" type="text" ng-model="currentFolder.group" value="{{currentFolder.group}}" list="folder-group-list"/>
|
||||
<datalist id="folder-group-list">
|
||||
<option ng-repeat="(group, folders) in foldersGrouped" value="{{group}}" />
|
||||
</datalist>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.folderGroup.$valid || folderEditor.folderGroup.$pristine">Optional group for the folder. Can be different on each device.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div ng-if="!editingFolderDefaults()" class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
|
||||
<label for="folderID"><span translate>Folder ID</span></label>
|
||||
<input name="folderID" ng-readonly="has(['existing', 'new-pending'], currentFolder._editing)" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required="" aria-required="true" unique-folder value="{{currentFolder.id}}" />
|
||||
@@ -342,6 +387,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-group">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="currentFolder.blockIndexing" /> <span translate>Block Indexing</span>
|
||||
</label>
|
||||
<p translate class="help-block">
|
||||
Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-if="currentFolder.syncXattrs || currentFolder.sendXattrs">
|
||||
<div class="col-md-12">
|
||||
<p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<modal id="remove-folder-confirmation" status="warning" icon="fas fa-question-circle" heading="{{'Remove Folder' | translate}}" large="no" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<p translate translate-value-label="{{currentFolder.label}}">
|
||||
<p translate translate-value-label="{{currentFolder.label || currentFolder.id}}">
|
||||
Are you sure you want to remove folder {%label%}?
|
||||
</p>
|
||||
<p translate>
|
||||
|
||||
+1086
-298
File diff suppressed because it is too large
Load Diff
+3641
-2785
File diff suppressed because it is too large
Load Diff
@@ -27,13 +27,29 @@ type DBService interface {
|
||||
LastMaintenanceTime() time.Time
|
||||
}
|
||||
|
||||
// UpdateOption modifies the behavior of a DB Update call.
|
||||
type UpdateOption func(*UpdateOptions)
|
||||
|
||||
// UpdateOptions holds options for a DB Update call.
|
||||
type UpdateOptions struct {
|
||||
SkipBlockIndex bool
|
||||
}
|
||||
|
||||
// WithSkipBlockIndex skips inserting individual blocks into the block
|
||||
// index (the "blocks" table). Blocklists are still stored.
|
||||
func WithSkipBlockIndex() UpdateOption {
|
||||
return func(o *UpdateOptions) {
|
||||
o.SkipBlockIndex = true
|
||||
}
|
||||
}
|
||||
|
||||
type DB interface {
|
||||
// Create a service that performs database maintenance periodically (no
|
||||
// more often than the requested interval)
|
||||
Service(maintenanceInterval time.Duration) DBService
|
||||
|
||||
// Basics
|
||||
Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error
|
||||
Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo, opts ...UpdateOption) error
|
||||
Close() error
|
||||
|
||||
// Single files
|
||||
@@ -57,6 +73,10 @@ type DB interface {
|
||||
AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error)
|
||||
AllLocalBlocksWithHash(folder string, hash []byte) (iter.Seq[BlockMapEntry], func() error)
|
||||
|
||||
// Block index management
|
||||
DropBlockIndex(folder string) error
|
||||
PopulateBlockIndex(folder string) error
|
||||
|
||||
// Cleanup
|
||||
DropAllFiles(folder string, device protocol.DeviceID) error
|
||||
DropDevice(device protocol.DeviceID) error
|
||||
|
||||
@@ -198,10 +198,10 @@ func (m metricsDB) SetIndexID(folder string, device protocol.DeviceID, id protoc
|
||||
return m.DB.SetIndexID(folder, device, id)
|
||||
}
|
||||
|
||||
func (m metricsDB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error {
|
||||
func (m metricsDB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo, opts ...UpdateOption) error {
|
||||
defer m.account(folder, "Update")()
|
||||
defer metricTotalFilesUpdatedCount.WithLabelValues(folder).Add(float64(len(fs)))
|
||||
return m.DB.Update(folder, device, fs)
|
||||
return m.DB.Update(folder, device, fs, opts...)
|
||||
}
|
||||
|
||||
func (m metricsDB) GetKV(key string) ([]byte, error) {
|
||||
|
||||
@@ -92,12 +92,35 @@ func (s *DB) getFolderDB(folder string, create bool) (*folderDB, error) {
|
||||
return fdb, nil
|
||||
}
|
||||
|
||||
func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo) error {
|
||||
func (s *DB) Update(folder string, device protocol.DeviceID, fs []protocol.FileInfo, opts ...db.UpdateOption) error {
|
||||
fdb, err := s.getFolderDB(folder, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fdb.Update(device, fs)
|
||||
var options db.UpdateOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return fdb.Update(device, fs, options)
|
||||
}
|
||||
|
||||
func (s *DB) DropBlockIndex(folder string) error {
|
||||
fdb, err := s.getFolderDB(folder, false)
|
||||
if errors.Is(err, errNoSuchFolder) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fdb.DropBlockIndex()
|
||||
}
|
||||
|
||||
func (s *DB) PopulateBlockIndex(folder string) error {
|
||||
fdb, err := s.getFolderDB(folder, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fdb.PopulateBlockIndex()
|
||||
}
|
||||
|
||||
func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) {
|
||||
|
||||
@@ -9,6 +9,7 @@ package sqlite
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syncthing/syncthing/internal/itererr"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
@@ -138,6 +139,211 @@ func TestBlocksDeleted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropBlockIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sdb, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { sdb.Close() })
|
||||
|
||||
// Insert files with blocks
|
||||
files := []protocol.FileInfo{
|
||||
genFile("a", 3, 0),
|
||||
genFile("b", 2, 0),
|
||||
}
|
||||
if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify blocks exist
|
||||
hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, files[0].Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) == 0 {
|
||||
t.Fatal("expected block hits before drop")
|
||||
}
|
||||
|
||||
// Drop the block index
|
||||
if err := sdb.DropBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify blocks are gone
|
||||
hits, err = itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, files[0].Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) != 0 {
|
||||
t.Fatal("expected no block hits after drop")
|
||||
}
|
||||
|
||||
// Dropping again should be a no-op (already empty)
|
||||
if err := sdb.DropBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Dropping a nonexistent folder should be fine
|
||||
if err := sdb.DropBlockIndex("nonexistent"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopulateBlockIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sdb, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { sdb.Close() })
|
||||
|
||||
// Insert files with blocks
|
||||
files := []protocol.FileInfo{
|
||||
genFile("a", 3, 0),
|
||||
genFile("b", 2, 0),
|
||||
}
|
||||
if err := sdb.Update(folderID, protocol.LocalDeviceID, files); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Collect the original block entries for comparison
|
||||
origHitsA, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, files[0].Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(origHitsA) != 1 {
|
||||
t.Fatal("expected one hit for block a[0]")
|
||||
}
|
||||
|
||||
// Drop the block index
|
||||
if err := sdb.DropBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Populate it back from existing blocklists
|
||||
if err := sdb.PopulateBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify all blocks are back
|
||||
for i, f := range files {
|
||||
for j, b := range f.Blocks {
|
||||
hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, b.Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) == 0 {
|
||||
t.Errorf("file %d block %d: expected hits after populate", i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populating again should be a no-op (not empty)
|
||||
if err := sdb.PopulateBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopulateBlockIndexSkipsRemoteFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sdb, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { sdb.Close() })
|
||||
|
||||
// Insert a local file (blocks indexed) and a remote file (blocks not indexed)
|
||||
localFile := genFile("local", 2, 0)
|
||||
if err := sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{localFile}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
remoteFile := genFile("remote", 2, 1)
|
||||
if err := sdb.Update(folderID, protocol.DeviceID{42}, []protocol.FileInfo{remoteFile}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Drop and repopulate
|
||||
if err := sdb.DropBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sdb.PopulateBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Local file blocks should be present
|
||||
hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, localFile.Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) == 0 {
|
||||
t.Error("expected hits for local file blocks")
|
||||
}
|
||||
|
||||
// Remote file blocks should not be present (blocks are only
|
||||
// indexed for local files)
|
||||
hits, err = itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, remoteFile.Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) != 0 {
|
||||
t.Error("expected no hits for remote file blocks")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipBlockIndexOnUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sdb, err := Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { sdb.Close() })
|
||||
|
||||
// Insert a file with SkipBlockIndex
|
||||
file := genFile("a", 3, 0)
|
||||
if err := sdb.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file}, db.WithSkipBlockIndex()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Blocks should not be indexed
|
||||
hits, err := itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, file.Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) != 0 {
|
||||
t.Fatal("expected no block hits with SkipBlockIndex")
|
||||
}
|
||||
|
||||
// The blocklist should still be stored (file info is retrievable with blocks)
|
||||
fi, ok, err := sdb.GetDeviceFile(folderID, protocol.LocalDeviceID, "a")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("file not found")
|
||||
}
|
||||
if len(fi.Blocks) != 3 {
|
||||
t.Fatalf("expected 3 blocks in file info, got %d", len(fi.Blocks))
|
||||
}
|
||||
|
||||
// Populate should fill in the blocks
|
||||
if err := sdb.PopulateBlockIndex(folderID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hits, err = itererr.Collect(sdb.AllLocalBlocksWithHash(folderID, file.Blocks[0].Hash))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hits) != 1 {
|
||||
t.Fatal("expected one hit after populate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteSequence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/syncthing/syncthing/internal/db"
|
||||
"github.com/syncthing/syncthing/internal/gen/dbproto"
|
||||
"github.com/syncthing/syncthing/internal/itererr"
|
||||
"github.com/syncthing/syncthing/internal/slogutil"
|
||||
@@ -30,7 +31,7 @@ const (
|
||||
updatePointsThreshold = 250_000
|
||||
)
|
||||
|
||||
func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) error {
|
||||
func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo, options db.UpdateOptions) error {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
|
||||
@@ -151,7 +152,7 @@ func (s *folderDB) Update(device protocol.DeviceID, fs []protocol.FileInfo) erro
|
||||
}
|
||||
if _, err := insertBlockListStmt.Exec(f.BlocksHash, bs); err != nil {
|
||||
return wrap(err, "insert blocklist")
|
||||
} else if device == protocol.LocalDeviceID {
|
||||
} else if device == protocol.LocalDeviceID && !options.SkipBlockIndex {
|
||||
// Insert all blocks
|
||||
if err := s.insertBlocksLocked(txp, f.BlocksHash, f.Blocks); err != nil {
|
||||
return wrap(err, "insert blocks")
|
||||
@@ -303,6 +304,86 @@ func (s *folderDB) DropFilesNamed(device protocol.DeviceID, names []string) erro
|
||||
return wrap(tx.Commit())
|
||||
}
|
||||
|
||||
func (s *folderDB) blockIndexEmpty() (bool, error) {
|
||||
var exists bool
|
||||
err := s.sql.Get(&exists, `SELECT EXISTS (SELECT 1 FROM blocks LIMIT 1)`)
|
||||
if err != nil {
|
||||
return false, wrap(err)
|
||||
}
|
||||
return !exists, nil
|
||||
}
|
||||
|
||||
func (s *folderDB) DropBlockIndex() error {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
|
||||
empty, err := s.blockIndexEmpty()
|
||||
if err != nil || empty {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := s.sql.Exec(`DELETE FROM blocks`); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
|
||||
return s.vacuumAndOptimize()
|
||||
}
|
||||
|
||||
func (s *folderDB) PopulateBlockIndex() error {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
|
||||
empty, err := s.blockIndexEmpty()
|
||||
if err != nil || !empty {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := s.sql.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
txp := &txPreparedStmts{Tx: tx}
|
||||
|
||||
// Iterate all local files that have a blocklist
|
||||
rows, err := tx.Queryx(`
|
||||
SELECT f.blocklist_hash, bl.blprotobuf FROM files f
|
||||
INNER JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
|
||||
WHERE f.device_idx = ? AND f.blocklist_hash IS NOT NULL
|
||||
`, s.localDeviceIdx)
|
||||
if err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var blocklistHash []byte
|
||||
var blProtobuf []byte
|
||||
if err := rows.Scan(&blocklistHash, &blProtobuf); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
|
||||
var bl dbproto.BlockList
|
||||
if err := proto.Unmarshal(blProtobuf, &bl); err != nil {
|
||||
return wrap(err, "unmarshal blocklist")
|
||||
}
|
||||
|
||||
blocks := make([]protocol.BlockInfo, len(bl.Blocks))
|
||||
for i, b := range bl.Blocks {
|
||||
blocks[i] = protocol.BlockInfoFromWire(b)
|
||||
}
|
||||
|
||||
if err := s.insertBlocksLocked(txp, blocklistHash, blocks); err != nil {
|
||||
return wrap(err, "insert blocks")
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return wrap(err)
|
||||
}
|
||||
|
||||
return wrap(tx.Commit())
|
||||
}
|
||||
|
||||
func (*folderDB) insertBlocksLocked(tx *txPreparedStmts, blocklistHash []byte, blocks []protocol.BlockInfo) error {
|
||||
if len(blocks) == 0 {
|
||||
return nil
|
||||
|
||||
+169
-181
@@ -968,9 +968,6 @@ type FileInfo struct {
|
||||
// The version_hash is an implementation detail and not part of the wire
|
||||
// format. It is used in the old database format.
|
||||
VersionHash []byte `protobuf:"bytes,1001,opt,name=version_hash,json=versionHash,proto3" json:"version_hash,omitempty"`
|
||||
// The time when the inode was last changed (i.e., permissions, xattrs
|
||||
// etc changed). This is host-local, not sent over the wire.
|
||||
InodeChangeNs int64 `protobuf:"varint,1002,opt,name=inode_change_ns,json=inodeChangeNs,proto3" json:"inode_change_ns,omitempty"`
|
||||
// The size of the data appended to the encrypted file on disk. This is
|
||||
// host-local, not sent over the wire.
|
||||
EncryptionTrailerSize int32 `protobuf:"varint,1003,opt,name=encryption_trailer_size,json=encryptionTrailerSize,proto3" json:"encryption_trailer_size,omitempty"`
|
||||
@@ -1135,13 +1132,6 @@ func (x *FileInfo) GetVersionHash() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FileInfo) GetInodeChangeNs() int64 {
|
||||
if x != nil {
|
||||
return x.InodeChangeNs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FileInfo) GetEncryptionTrailerSize() int32 {
|
||||
if x != nil {
|
||||
return x.EncryptionTrailerSize
|
||||
@@ -2092,7 +2082,7 @@ var file_bep_bep_proto_rawDesc = []byte{
|
||||
0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x23, 0x0a,
|
||||
0x0d, 0x70, 0x72, 0x65, 0x76, 0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x76, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e,
|
||||
0x63, 0x65, 0x22, 0xb0, 0x06, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12,
|
||||
0x63, 0x65, 0x22, 0x8f, 0x06, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x69, 0x66,
|
||||
@@ -2131,180 +2121,178 @@ var file_bep_bep_proto_rawDesc = []byte{
|
||||
0x73, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46,
|
||||
0x6c, 0x61, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
|
||||
0x68, 0x61, 0x73, 0x68, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x6f, 0x64,
|
||||
0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0xea, 0x07, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x0d, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e,
|
||||
0x73, 0x12, 0x37, 0x0a, 0x17, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
|
||||
0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0xeb, 0x07, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54,
|
||||
0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c,
|
||||
0x65, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x25,
|
||||
0x0a, 0x0e, 0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x6f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x51, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69,
|
||||
0x7a, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x32, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x12, 0x28, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x52, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x07,
|
||||
0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xfd, 0x01,
|
||||
0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x21,
|
||||
0x0a, 0x04, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62,
|
||||
0x65, 0x70, 0x2e, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x75, 0x6e, 0x69,
|
||||
0x78, 0x12, 0x2a, 0x0a, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73,
|
||||
0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x12, 0x24, 0x0a,
|
||||
0x05, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62,
|
||||
0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x6c, 0x69,
|
||||
0x6e, 0x75, 0x78, 0x12, 0x26, 0x0a, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44,
|
||||
0x61, 0x74, 0x61, 0x52, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x12, 0x28, 0x0a, 0x07, 0x66,
|
||||
0x72, 0x65, 0x65, 0x62, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62,
|
||||
0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x66, 0x72,
|
||||
0x65, 0x65, 0x62, 0x73, 0x64, 0x12, 0x26, 0x0a, 0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74,
|
||||
0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x22, 0x6c, 0x0a,
|
||||
0x08, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x37, 0x0a, 0x17, 0x65, 0x6e, 0x63, 0x72,
|
||||
0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x5f, 0x73,
|
||||
0x69, 0x7a, 0x65, 0x18, 0xeb, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x65, 0x6e, 0x63, 0x72,
|
||||
0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x53, 0x69, 0x7a,
|
||||
0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69,
|
||||
0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e,
|
||||
0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x6d,
|
||||
0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e,
|
||||
0x6f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x06, 0x08, 0xea,
|
||||
0x07, 0x10, 0xeb, 0x07, 0x22, 0x51, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66,
|
||||
0x6f, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a,
|
||||
0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x32, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f,
|
||||
0x72, 0x12, 0x28, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x52, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x07, 0x43,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xfd, 0x01, 0x0a,
|
||||
0x0c, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a,
|
||||
0x04, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x65,
|
||||
0x70, 0x2e, 0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x78,
|
||||
0x12, 0x2a, 0x0a, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x10, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44,
|
||||
0x61, 0x74, 0x61, 0x52, 0x07, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x12, 0x24, 0x0a, 0x05,
|
||||
0x6c, 0x69, 0x6e, 0x75, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65,
|
||||
0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x05, 0x6c, 0x69, 0x6e,
|
||||
0x75, 0x78, 0x12, 0x26, 0x0a, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61,
|
||||
0x74, 0x61, 0x52, 0x06, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x12, 0x28, 0x0a, 0x07, 0x66, 0x72,
|
||||
0x65, 0x65, 0x62, 0x73, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65,
|
||||
0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x66, 0x72, 0x65,
|
||||
0x65, 0x62, 0x73, 0x64, 0x12, 0x26, 0x0a, 0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72,
|
||||
0x44, 0x61, 0x74, 0x61, 0x52, 0x06, 0x6e, 0x65, 0x74, 0x62, 0x73, 0x64, 0x22, 0x6c, 0x0a, 0x08,
|
||||
0x55, 0x6e, 0x69, 0x78, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65,
|
||||
0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77,
|
||||
0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f,
|
||||
0x75, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x67, 0x69, 0x64, 0x22, 0x52, 0x0a, 0x0b, 0x57, 0x69,
|
||||
0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e,
|
||||
0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f,
|
||||
0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75,
|
||||
0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72,
|
||||
0x6f, 0x75, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x67, 0x69, 0x64, 0x22, 0x52, 0x0a, 0x0b, 0x57,
|
||||
0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77,
|
||||
0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||
0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x77, 0x6e,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x73, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22,
|
||||
0x2f, 0x0a, 0x09, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x06,
|
||||
0x78, 0x61, 0x74, 0x74, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x62,
|
||||
0x65, 0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x52, 0x06, 0x78, 0x61, 0x74, 0x74, 0x72, 0x73,
|
||||
0x22, 0x31, 0x0a, 0x05, 0x58, 0x61, 0x74, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x22, 0xcd, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f,
|
||||
0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66,
|
||||
0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
|
||||
0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x66,
|
||||
0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x18, 0x07, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61,
|
||||
0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x6f, 0x18, 0x09,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x4a, 0x04, 0x08,
|
||||
0x08, 0x10, 0x09, 0x22, 0x52, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64,
|
||||
0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64,
|
||||
0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x65, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c,
|
||||
0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66,
|
||||
0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c,
|
||||
0x64, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44,
|
||||
0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0xe5,
|
||||
0x01, 0x0a, 0x1a, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50,
|
||||
0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x44, 0x0a,
|
||||
0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0e, 0x32, 0x23, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77,
|
||||
0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x56,
|
||||
0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27,
|
||||
0x0a, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18,
|
||||
0x04, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x00, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
|
||||
0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
|
||||
0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x6c, 0x6f,
|
||||
0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x1f,
|
||||
0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
|
||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2a,
|
||||
0xed, 0x01, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
|
||||
0x1f, 0x0a, 0x1b, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x43, 0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00,
|
||||
0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,
|
||||
0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x45, 0x53, 0x53,
|
||||
0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55,
|
||||
0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45, 0x53, 0x53, 0x41,
|
||||
0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10,
|
||||
0x03, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50,
|
||||
0x45, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x04, 0x12, 0x22, 0x0a, 0x1e,
|
||||
0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x57,
|
||||
0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x05,
|
||||
0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,
|
||||
0x5f, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41,
|
||||
0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x07, 0x2a,
|
||||
0x4f, 0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45,
|
||||
0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e,
|
||||
0x45, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x43,
|
||||
0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, 0x5a, 0x34, 0x10, 0x01,
|
||||
0x2a, 0x56, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12,
|
||||
0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d,
|
||||
0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d,
|
||||
0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x45, 0x56, 0x45, 0x52, 0x10, 0x01,
|
||||
0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f,
|
||||
0x41, 0x4c, 0x57, 0x41, 0x59, 0x53, 0x10, 0x02, 0x2a, 0x86, 0x01, 0x0a, 0x0a, 0x46, 0x6f, 0x6c,
|
||||
0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x4f, 0x4c, 0x44, 0x45,
|
||||
0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x43, 0x45,
|
||||
0x49, 0x56, 0x45, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f,
|
||||
0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01,
|
||||
0x12, 0x1c, 0x0a, 0x18, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x52, 0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x12, 0x21,
|
||||
0x0a, 0x1d, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45,
|
||||
0x43, 0x45, 0x49, 0x56, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x45, 0x44, 0x10,
|
||||
0x03, 0x2a, 0x51, 0x0a, 0x10, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x70, 0x52,
|
||||
0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x1a, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f,
|
||||
0x53, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x55, 0x4e, 0x4e,
|
||||
0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f,
|
||||
0x53, 0x54, 0x4f, 0x50, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x55, 0x53,
|
||||
0x45, 0x44, 0x10, 0x01, 0x2a, 0xb0, 0x01, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66,
|
||||
0x6f, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e,
|
||||
0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x1c,
|
||||
0x0a, 0x18, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45,
|
||||
0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1b,
|
||||
0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53,
|
||||
0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x02, 0x1a, 0x02, 0x08,
|
||||
0x01, 0x12, 0x28, 0x0a, 0x20, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54,
|
||||
0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x44, 0x49, 0x52, 0x45,
|
||||
0x43, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x03, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46,
|
||||
0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x77, 0x6e, 0x65,
|
||||
0x72, 0x5f, 0x69, 0x73, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x0c, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x73, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x2f,
|
||||
0x0a, 0x09, 0x58, 0x61, 0x74, 0x74, 0x72, 0x44, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x06, 0x78,
|
||||
0x61, 0x74, 0x74, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x62, 0x65,
|
||||
0x70, 0x2e, 0x58, 0x61, 0x74, 0x74, 0x72, 0x52, 0x06, 0x78, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22,
|
||||
0x31, 0x0a, 0x05, 0x58, 0x61, 0x74, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x22, 0xcd, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||
0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66,
|
||||
0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73,
|
||||
0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72,
|
||||
0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x6f, 0x18, 0x09, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x4a, 0x04, 0x08, 0x08,
|
||||
0x10, 0x09, 0x22, 0x52, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61,
|
||||
0x74, 0x61, 0x12, 0x22, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x0e, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65,
|
||||
0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x65, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
|
||||
0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f,
|
||||
0x6c, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64,
|
||||
0x65, 0x72, 0x12, 0x39, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20,
|
||||
0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f,
|
||||
0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0xe5, 0x01,
|
||||
0x0a, 0x1a, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72,
|
||||
0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x44, 0x0a, 0x0b,
|
||||
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0e, 0x32, 0x23, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e,
|
||||
0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79,
|
||||
0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x62, 0x65, 0x70, 0x2e, 0x56, 0x65,
|
||||
0x63, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a,
|
||||
0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x18, 0x04,
|
||||
0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x00, 0x52, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49,
|
||||
0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
|
||||
0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63,
|
||||
0x6b, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x1f, 0x0a,
|
||||
0x05, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x2a, 0xed,
|
||||
0x01, 0x0a, 0x0b, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f,
|
||||
0x0a, 0x1b, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43,
|
||||
0x4c, 0x55, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12,
|
||||
0x16, 0x0a, 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x49, 0x4e, 0x44, 0x45, 0x58, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x4d, 0x45, 0x53, 0x53, 0x41,
|
||||
0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x55, 0x50,
|
||||
0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47,
|
||||
0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x03,
|
||||
0x12, 0x19, 0x0a, 0x15, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,
|
||||
0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x04, 0x12, 0x22, 0x0a, 0x1e, 0x4d,
|
||||
0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e,
|
||||
0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x05, 0x12,
|
||||
0x15, 0x0a, 0x11, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x50, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47,
|
||||
0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x07, 0x2a, 0x4f,
|
||||
0x0a, 0x12, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f,
|
||||
0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45,
|
||||
0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x43, 0x4f,
|
||||
0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4c, 0x5a, 0x34, 0x10, 0x01, 0x2a,
|
||||
0x56, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18,
|
||||
0x0a, 0x14, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x45,
|
||||
0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4d, 0x50,
|
||||
0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x45, 0x56, 0x45, 0x52, 0x10, 0x01, 0x12,
|
||||
0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x41,
|
||||
0x4c, 0x57, 0x41, 0x59, 0x53, 0x10, 0x02, 0x2a, 0x86, 0x01, 0x0a, 0x0a, 0x46, 0x6f, 0x6c, 0x64,
|
||||
0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52,
|
||||
0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x43, 0x45, 0x49,
|
||||
0x56, 0x45, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x54,
|
||||
0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x4e, 0x44, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x12,
|
||||
0x1c, 0x0a, 0x18, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52,
|
||||
0x45, 0x43, 0x45, 0x49, 0x56, 0x45, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x12, 0x21, 0x0a,
|
||||
0x1d, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x43,
|
||||
0x45, 0x49, 0x56, 0x45, 0x5f, 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03,
|
||||
0x2a, 0x51, 0x0a, 0x10, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65,
|
||||
0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x1a, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x53,
|
||||
0x54, 0x4f, 0x50, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x55, 0x4e, 0x4e, 0x49,
|
||||
0x4e, 0x47, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x53,
|
||||
0x54, 0x4f, 0x50, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x41, 0x55, 0x53, 0x45,
|
||||
0x44, 0x10, 0x01, 0x2a, 0xb0, 0x01, 0x0a, 0x0c, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f,
|
||||
0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46,
|
||||
0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x1c, 0x0a,
|
||||
0x18, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
|
||||
0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1b, 0x46,
|
||||
0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59,
|
||||
0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x10, 0x04, 0x2a, 0x76, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72,
|
||||
0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f,
|
||||
0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a,
|
||||
0x12, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x47, 0x45, 0x4e, 0x45,
|
||||
0x52, 0x49, 0x43, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43,
|
||||
0x4f, 0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x53, 0x55, 0x43, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x45,
|
||||
0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45,
|
||||
0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a,
|
||||
0x7e, 0x0a, 0x1e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50,
|
||||
0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70,
|
||||
0x65, 0x12, 0x2d, 0x0a, 0x29, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f,
|
||||
0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41,
|
||||
0x54, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x00,
|
||||
0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x02, 0x1a, 0x02, 0x08, 0x01,
|
||||
0x12, 0x28, 0x0a, 0x20, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59,
|
||||
0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43,
|
||||
0x54, 0x4f, 0x52, 0x59, 0x10, 0x03, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x49,
|
||||
0x4c, 0x45, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d,
|
||||
0x4c, 0x49, 0x4e, 0x4b, 0x10, 0x04, 0x2a, 0x76, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43,
|
||||
0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44,
|
||||
0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12,
|
||||
0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52,
|
||||
0x49, 0x43, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f,
|
||||
0x44, 0x45, 0x5f, 0x4e, 0x4f, 0x5f, 0x53, 0x55, 0x43, 0x48, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10,
|
||||
0x02, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f,
|
||||
0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x2a, 0x7e,
|
||||
0x0a, 0x1e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72,
|
||||
0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||
0x12, 0x2d, 0x0a, 0x29, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41,
|
||||
0x44, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54,
|
||||
0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x47, 0x45, 0x54, 0x10, 0x01, 0x42,
|
||||
0x70, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x2e, 0x62, 0x65, 0x70, 0x42, 0x08, 0x42, 0x65, 0x70, 0x50,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x79, 0x6e,
|
||||
0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
||||
0x67, 0x65, 0x6e, 0x2f, 0x62, 0x65, 0x70, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, 0x02, 0x03,
|
||||
0x42, 0x65, 0x70, 0xca, 0x02, 0x03, 0x42, 0x65, 0x70, 0xe2, 0x02, 0x0f, 0x42, 0x65, 0x70, 0x5c,
|
||||
0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x03, 0x42, 0x65,
|
||||
0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x00, 0x12,
|
||||
0x2d, 0x0a, 0x29, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44,
|
||||
0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45,
|
||||
0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x47, 0x45, 0x54, 0x10, 0x01, 0x42, 0x70,
|
||||
0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x2e, 0x62, 0x65, 0x70, 0x42, 0x08, 0x42, 0x65, 0x70, 0x50, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x79, 0x6e, 0x63,
|
||||
0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67,
|
||||
0x65, 0x6e, 0x2f, 0x62, 0x65, 0x70, 0xa2, 0x02, 0x03, 0x42, 0x58, 0x58, 0xaa, 0x02, 0x03, 0x42,
|
||||
0x65, 0x70, 0xca, 0x02, 0x03, 0x42, 0x65, 0x70, 0xe2, 0x02, 0x0f, 0x42, 0x65, 0x70, 0x5c, 0x47,
|
||||
0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x03, 0x42, 0x65, 0x70,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -51,9 +51,6 @@ type FileInfoTruncated struct {
|
||||
// The version_hash is an implementation detail and not part of the wire
|
||||
// format.
|
||||
VersionHash []byte `protobuf:"bytes,1001,opt,name=version_hash,json=versionHash,proto3" json:"version_hash,omitempty"`
|
||||
// The time when the inode was last changed (i.e., permissions, xattrs
|
||||
// etc changed). This is host-local, not sent over the wire.
|
||||
InodeChangeNs int64 `protobuf:"varint,1002,opt,name=inode_change_ns,json=inodeChangeNs,proto3" json:"inode_change_ns,omitempty"`
|
||||
// The size of the data appended to the encrypted file on disk. This is
|
||||
// host-local, not sent over the wire.
|
||||
EncryptionTrailerSize int32 `protobuf:"varint,1003,opt,name=encryption_trailer_size,json=encryptionTrailerSize,proto3" json:"encryption_trailer_size,omitempty"`
|
||||
@@ -211,13 +208,6 @@ func (x *FileInfoTruncated) GetVersionHash() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FileInfoTruncated) GetInodeChangeNs() int64 {
|
||||
if x != nil {
|
||||
return x.InodeChangeNs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FileInfoTruncated) GetEncryptionTrailerSize() int32 {
|
||||
if x != nil {
|
||||
return x.EncryptionTrailerSize
|
||||
@@ -755,7 +745,7 @@ var file_dbproto_structs_proto_rawDesc = []byte{
|
||||
0x1a, 0x0d, 0x62, 0x65, 0x70, 0x2f, 0x62, 0x65, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
|
||||
0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x22, 0x97, 0x06, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x72, 0x75,
|
||||
0x22, 0xf6, 0x05, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x54, 0x72, 0x75,
|
||||
0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
|
||||
0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1d,
|
||||
@@ -792,89 +782,87 @@ var file_dbproto_structs_proto_rawDesc = []byte{
|
||||
0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6c,
|
||||
0x61, 0x67, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68,
|
||||
0x61, 0x73, 0x68, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x6f, 0x64, 0x65,
|
||||
0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x6e, 0x73, 0x18, 0xea, 0x07, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x0d, 0x69, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x73,
|
||||
0x12, 0x37, 0x0a, 0x17, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74,
|
||||
0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0xeb, 0x07, 0x20, 0x01,
|
||||
0x28, 0x05, 0x52, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72,
|
||||
0x61, 0x69, 0x6c, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c,
|
||||
0x65, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65,
|
||||
0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x25, 0x0a,
|
||||
0x0e, 0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18,
|
||||
0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x6f, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x04, 0x08, 0x10, 0x10, 0x11, 0x22, 0x91, 0x01, 0x0a, 0x0b, 0x46,
|
||||
0x69, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x07, 0x76, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x62, 0x65,
|
||||
0x70, 0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64,
|
||||
0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x65,
|
||||
0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64,
|
||||
0x5f, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e,
|
||||
0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x3f,
|
||||
0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x30, 0x0a,
|
||||
0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x14, 0x2e, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x56, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22,
|
||||
0x33, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06,
|
||||
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62,
|
||||
0x65, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, 0x6c,
|
||||
0x6f, 0x63, 0x6b, 0x73, 0x22, 0x5c, 0x0a, 0x15, 0x49, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x12, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x22,
|
||||
0x0a, 0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0xe9,
|
||||
0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x61,
|
||||
0x73, 0x68, 0x22, 0xe6, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69,
|
||||
0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b,
|
||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b,
|
||||
0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x05, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62,
|
||||
0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65,
|
||||
0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x0a,
|
||||
0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x4e, 0x0a, 0x09, 0x43,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x62, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0xae, 0x01, 0x0a, 0x0e,
|
||||
0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x2e,
|
||||
0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
|
||||
0x61, 0x62, 0x65, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f,
|
||||
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
||||
0x64, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72,
|
||||
0x79, 0x70, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x6d,
|
||||
0x6f, 0x74, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x22, 0x6e, 0x0a, 0x0e,
|
||||
0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e,
|
||||
0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x8c, 0x01, 0x0a,
|
||||
0x0b, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x0c, 0x53, 0x74,
|
||||
0x72, 0x75, 0x63, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x33, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69,
|
||||
0x6e, 0x67, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x44, 0x62, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0xca, 0x02, 0x07, 0x44, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xe2, 0x02, 0x13, 0x44, 0x62,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||
0x61, 0xea, 0x02, 0x07, 0x44, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x12, 0x37, 0x0a, 0x17, 0x65, 0x6e, 0x63, 0x72, 0x79,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x69,
|
||||
0x7a, 0x65, 0x18, 0xeb, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79,
|
||||
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e,
|
||||
0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x76,
|
||||
0x61, 0x6c, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69,
|
||||
0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6e, 0x6f,
|
||||
0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x04, 0x08, 0x10, 0x10,
|
||||
0x11, 0x4a, 0x06, 0x08, 0xea, 0x07, 0x10, 0xeb, 0x07, 0x22, 0x91, 0x01, 0x0a, 0x0b, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x07, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x62, 0x65, 0x70,
|
||||
0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65,
|
||||
0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x65, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f,
|
||||
0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x69,
|
||||
0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x3f, 0x0a,
|
||||
0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x08,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
|
||||
0x2e, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x56, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x33,
|
||||
0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x62,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x65,
|
||||
0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x62, 0x6c, 0x6f,
|
||||
0x63, 0x6b, 0x73, 0x22, 0x5c, 0x0a, 0x15, 0x49, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b,
|
||||
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x12, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a,
|
||||
0x0c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0xe9, 0x07,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x73,
|
||||
0x68, 0x22, 0xe6, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x66, 0x69, 0x6c,
|
||||
0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65,
|
||||
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
|
||||
0x72, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x73,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x73,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x05, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79,
|
||||
0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73,
|
||||
0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09,
|
||||
0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x6c, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a,
|
||||
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x4e, 0x0a, 0x09, 0x43, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x74, 0x12, 0x27, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0xae, 0x01, 0x0a, 0x0e, 0x4f,
|
||||
0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x46, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x2e, 0x0a,
|
||||
0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61,
|
||||
0x62, 0x65, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x65,
|
||||
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10,
|
||||
0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
|
||||
0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79,
|
||||
0x70, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x6d, 0x6f,
|
||||
0x74, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x22, 0x6e, 0x0a, 0x0e, 0x4f,
|
||||
0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a,
|
||||
0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x8c, 0x01, 0x0a, 0x0b,
|
||||
0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x0c, 0x53, 0x74, 0x72,
|
||||
0x75, 0x63, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x33, 0x67, 0x69, 0x74,
|
||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e,
|
||||
0x67, 0x2f, 0x73, 0x79, 0x6e, 0x63, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x64, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0xa2, 0x02, 0x03, 0x44, 0x58, 0x58, 0xaa, 0x02, 0x07, 0x44, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0xca, 0x02, 0x07, 0x44, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xe2, 0x02, 0x13, 0x44, 0x62, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||
0xea, 0x02, 0x07, 0x44, 0x62, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -1786,7 +1786,6 @@ func fileIntfJSONMap(f protocol.FileInfo) map[string]interface{} {
|
||||
"version": jsonVersionVector(f.FileVersion()),
|
||||
"localFlags": f.FileLocalFlags(),
|
||||
"platform": f.PlatformData(),
|
||||
"inodeChange": f.InodeChangeTime(),
|
||||
"blocksHash": f.FileBlocksHash(),
|
||||
"previousBlocksHash": f.PreviousBlocksHash,
|
||||
}
|
||||
|
||||
+2
-24
@@ -25,7 +25,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxSessionLifetime = 7 * 24 * time.Hour
|
||||
maxActiveSessions = 25
|
||||
randomTokenLength = 64
|
||||
maxLoginRequestSize = 1 << 10 // one kibibyte for username+password
|
||||
@@ -271,7 +270,7 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
||||
|
||||
defer connection.Close()
|
||||
|
||||
bindDN := formatOptionalPercentS(cfg.BindDN, escapeForLDAPDN(username))
|
||||
bindDN := formatOptionalPercentS(cfg.BindDN, ldap.EscapeDN(username))
|
||||
err = connection.Bind(bindDN, password)
|
||||
if err != nil {
|
||||
slog.Error("Failed to bind with LDAP server", slogutil.Error(err))
|
||||
@@ -292,7 +291,7 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
||||
// the user. If this matches precisely one user then we are good to go.
|
||||
// The search filter uses the same %s interpolation as the bind DN.
|
||||
|
||||
searchString := formatOptionalPercentS(cfg.SearchFilter, escapeForLDAPFilter(username))
|
||||
searchString := formatOptionalPercentS(cfg.SearchFilter, ldap.EscapeFilter(username))
|
||||
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
|
||||
const timeLimit = 60 // Search for up to a minute...
|
||||
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
|
||||
@@ -310,27 +309,6 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
|
||||
return true
|
||||
}
|
||||
|
||||
// escapeForLDAPFilter escapes a value that will be used in a filter clause
|
||||
func escapeForLDAPFilter(value string) string {
|
||||
// https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Special_Characters
|
||||
// Backslash must always be first in the list so we don't double escape them.
|
||||
return escapeRunes(value, []rune{'\\', '*', '(', ')', 0})
|
||||
}
|
||||
|
||||
// escapeForLDAPDN escapes a value that will be used in a bind DN
|
||||
func escapeForLDAPDN(value string) string {
|
||||
// https://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
|
||||
// Backslash must always be first in the list so we don't double escape them.
|
||||
return escapeRunes(value, []rune{'\\', ',', '#', '+', '<', '>', ';', '"', '=', ' ', 0})
|
||||
}
|
||||
|
||||
func escapeRunes(value string, runes []rune) string {
|
||||
for _, e := range runes {
|
||||
value = strings.ReplaceAll(value, string(e), fmt.Sprintf("\\%X", e))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func formatOptionalPercentS(template string, username string) string {
|
||||
var replacements []any
|
||||
nReps := strings.Count(template, "%s") - strings.Count(template, "%%s")
|
||||
|
||||
+41
-42
@@ -7,6 +7,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -72,48 +73,6 @@ func TestFormatOptionalPercentS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeForLDAPFilter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"username", `username`},
|
||||
{"user(name", `user\28name`},
|
||||
{"user)name", `user\29name`},
|
||||
{"user\\name", `user\5Cname`},
|
||||
{"user*name", `user\2Aname`},
|
||||
{"*,CN=asdf", `\2A,CN=asdf`},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
res := escapeForLDAPFilter(c.in)
|
||||
if c.out != res {
|
||||
t.Fatalf("result should be %s != %s", c.out, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeForLDAPDN(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"username", `username`},
|
||||
{"* ,CN=asdf", `*\20\2CCN\3Dasdf`},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
res := escapeForLDAPDN(c.in)
|
||||
if c.out != res {
|
||||
t.Fatalf("result should be %s != %s", c.out, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockClock struct {
|
||||
now time.Time
|
||||
}
|
||||
@@ -192,3 +151,43 @@ func TestTokenManager(t *testing.T) {
|
||||
t.Errorf("token %q should be invalid", t3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenManagerNoExpiry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mdb, err := sqlite.Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
mdb.Close()
|
||||
})
|
||||
kdb := db.NewMiscDB(mdb)
|
||||
clock := &mockClock{now: time.Now()}
|
||||
|
||||
tm := newTokenManager("testTokensNoExpiry", kdb, -1, 3)
|
||||
tm.timeNow = clock.Now
|
||||
|
||||
token := tm.New()
|
||||
if expiry, ok := tm.tokens.Tokens[token]; !ok || expiry != 0 {
|
||||
t.Fatalf("token should have no expiry, got %d", expiry)
|
||||
}
|
||||
|
||||
clock.wind(365 * 24 * time.Hour)
|
||||
if !tm.Check(token) {
|
||||
t.Fatal("token should still be valid after long time when no-expiry is enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionCookieMaxAgeNoExpiry(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := tokenCookieManager{
|
||||
guiCfg: config.GUIConfiguration{
|
||||
SessionCookieDurationS: -1,
|
||||
},
|
||||
}
|
||||
if got := m.sessionCookieMaxAge(); got != math.MaxInt32 {
|
||||
t.Fatalf("unexpected max age %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-18
@@ -571,7 +571,7 @@ func httpPost(url string, body map[string]string, cookies []*http.Cookie, t *tes
|
||||
}
|
||||
|
||||
func TestHTTPLogin(t *testing.T) {
|
||||
t.Parallel()
|
||||
// This test intentionally does not use t.Parallel()
|
||||
|
||||
httpGetBasicAuth := func(url string, username string, password string) *http.Response {
|
||||
t.Helper()
|
||||
@@ -602,7 +602,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
|
||||
t.Run(fmt.Sprintf("%d path", expectedOkStatus), func(t *testing.T) {
|
||||
t.Run("no auth is rejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetBasicAuth(url, "", "")
|
||||
if resp.StatusCode != expectedFailStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for unauthed request", expectedFailStatus, resp.StatusCode)
|
||||
@@ -613,7 +612,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("incorrect password is rejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetBasicAuth(url, "üser", "rksmrgs")
|
||||
if resp.StatusCode != expectedFailStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for incorrect password", expectedFailStatus, resp.StatusCode)
|
||||
@@ -624,7 +622,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("incorrect username is rejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetBasicAuth(url, "user", "räksmörgås") // string literals in Go source code are in UTF-8
|
||||
if resp.StatusCode != expectedFailStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for incorrect username", expectedFailStatus, resp.StatusCode)
|
||||
@@ -635,7 +632,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("UTF-8 auth works", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetBasicAuth(url, "üser", "räksmörgås") // string literals in Go source code are in UTF-8
|
||||
if resp.StatusCode != expectedOkStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for authed request (UTF-8)", expectedOkStatus, resp.StatusCode)
|
||||
@@ -646,7 +642,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Logout removes the session cookie", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetBasicAuth(url, "üser", "räksmörgås") // string literals in Go source code are in UTF-8
|
||||
if resp.StatusCode != expectedOkStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for authed request (UTF-8)", expectedOkStatus, resp.StatusCode)
|
||||
@@ -661,7 +656,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Session cookie is invalid after logout", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
loginResp := httpGetBasicAuth(url, "üser", "räksmörgås") // string literals in Go source code are in UTF-8
|
||||
if loginResp.StatusCode != expectedOkStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for authed request (UTF-8)", expectedOkStatus, loginResp.StatusCode)
|
||||
@@ -683,7 +677,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("ISO-8859-1 auth works", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetBasicAuth(url, "\xfcser", "r\xe4ksm\xf6rg\xe5s") // escaped ISO-8859-1
|
||||
if resp.StatusCode != expectedOkStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for authed request (ISO-8859-1)", expectedOkStatus, resp.StatusCode)
|
||||
@@ -694,7 +687,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("bad X-API-Key is rejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetXapikey(url, testAPIKey+"X")
|
||||
if resp.StatusCode != expectedFailStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for bad API key", expectedFailStatus, resp.StatusCode)
|
||||
@@ -705,7 +697,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("good X-API-Key is accepted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetXapikey(url, testAPIKey)
|
||||
if resp.StatusCode != expectedOkStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for API key", expectedOkStatus, resp.StatusCode)
|
||||
@@ -716,7 +707,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("bad Bearer is rejected", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetAuthorizationBearer(url, testAPIKey+"X")
|
||||
if resp.StatusCode != expectedFailStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for bad Authorization: Bearer", expectedFailStatus, resp.StatusCode)
|
||||
@@ -727,7 +717,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("good Bearer is accepted", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
resp := httpGetAuthorizationBearer(url, testAPIKey)
|
||||
if resp.StatusCode != expectedOkStatus {
|
||||
t.Errorf("Unexpected non-%d return code %d for Authorization: Bearer", expectedOkStatus, resp.StatusCode)
|
||||
@@ -748,8 +737,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
testWith(false, http.StatusNotFound, http.StatusForbidden, "/any-path/that/does/nooooooot/match-any/noauth-pattern")
|
||||
|
||||
t.Run("Password change invalidates old and enables new password", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// This test needs a longer-than-default shutdown timeout to finish saving
|
||||
// config changes when running on GitHub Actions
|
||||
shutdownTimeout := time.Second
|
||||
@@ -786,8 +773,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
newPassword := "123!asdF"
|
||||
|
||||
t.Run("when done via /rest/config", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := initConfig(initialPassword, t)
|
||||
{
|
||||
baseURL := startHTTPWithShutdownTimeout(t, w, shutdownTimeout)
|
||||
@@ -820,8 +805,6 @@ func TestHTTPLogin(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("when done via /rest/config/gui", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := initConfig(initialPassword, t)
|
||||
{
|
||||
baseURL := startHTTPWithShutdownTimeout(t, w, shutdownTimeout)
|
||||
|
||||
+49
-16
@@ -7,6 +7,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -35,6 +36,8 @@ type tokenManager struct {
|
||||
saveTimer *time.Timer
|
||||
}
|
||||
|
||||
const defaultSessionCookieDurationS = 7 * 24 * 60 * 60
|
||||
|
||||
func newTokenManager(key string, miscDB *db.Typed, lifetime time.Duration, maxItems int) *tokenManager {
|
||||
var tokens apiproto.TokenSet
|
||||
if bs, ok, _ := miscDB.Bytes(key); ok {
|
||||
@@ -60,18 +63,19 @@ func (m *tokenManager) Check(token string) bool {
|
||||
defer m.mut.Unlock()
|
||||
|
||||
expires, ok := m.tokens.Tokens[token]
|
||||
if ok {
|
||||
if expires < m.timeNow().UnixNano() {
|
||||
// The token is expired.
|
||||
m.saveLocked() // removes expired tokens
|
||||
return false
|
||||
}
|
||||
|
||||
// Give the token further life.
|
||||
m.tokens.Tokens[token] = m.timeNow().Add(m.lifetime).UnixNano()
|
||||
m.saveLocked()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return ok
|
||||
if expires != 0 && expires < m.timeNow().UnixNano() {
|
||||
// The token is expired.
|
||||
m.saveLocked() // removes expired tokens
|
||||
return false
|
||||
}
|
||||
|
||||
// Give the token further life.
|
||||
m.tokens.Tokens[token] = m.newExpiryNanos()
|
||||
m.saveLocked()
|
||||
return true
|
||||
}
|
||||
|
||||
// New creates a new token and returns it.
|
||||
@@ -81,12 +85,19 @@ func (m *tokenManager) New() string {
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
m.tokens.Tokens[token] = m.timeNow().Add(m.lifetime).UnixNano()
|
||||
m.tokens.Tokens[token] = m.newExpiryNanos()
|
||||
m.saveLocked()
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
func (m *tokenManager) newExpiryNanos() int64 {
|
||||
if m.lifetime <= 0 {
|
||||
return 0
|
||||
}
|
||||
return m.timeNow().Add(m.lifetime).UnixNano()
|
||||
}
|
||||
|
||||
// Delete removes a token.
|
||||
func (m *tokenManager) Delete(token string) {
|
||||
m.mut.Lock()
|
||||
@@ -100,7 +111,7 @@ func (m *tokenManager) saveLocked() {
|
||||
// Remove expired tokens.
|
||||
now := m.timeNow().UnixNano()
|
||||
for token, expiry := range m.tokens.Tokens {
|
||||
if expiry < now {
|
||||
if expiry != 0 && expiry < now {
|
||||
delete(m.tokens.Tokens, token)
|
||||
}
|
||||
}
|
||||
@@ -152,12 +163,16 @@ type tokenCookieManager struct {
|
||||
}
|
||||
|
||||
func newTokenCookieManager(shortID string, guiCfg config.GUIConfiguration, evLogger events.Logger, miscDB *db.Typed) *tokenCookieManager {
|
||||
sessionLifetimeS := guiCfg.SessionCookieDurationS
|
||||
if sessionLifetimeS == 0 {
|
||||
sessionLifetimeS = defaultSessionCookieDurationS
|
||||
}
|
||||
return &tokenCookieManager{
|
||||
cookieName: "sessionid-" + shortID,
|
||||
shortID: shortID,
|
||||
guiCfg: guiCfg,
|
||||
evLogger: evLogger,
|
||||
tokens: newTokenManager("sessions", miscDB, maxSessionLifetime, maxActiveSessions),
|
||||
tokens: newTokenManager("sessions", miscDB, time.Duration(sessionLifetimeS)*time.Second, maxActiveSessions),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +191,11 @@ func (m *tokenCookieManager) createSession(username string, persistent bool, w h
|
||||
|
||||
maxAge := 0
|
||||
if persistent {
|
||||
maxAge = int(maxSessionLifetime.Seconds())
|
||||
maxAge = m.sessionCookieMaxAge()
|
||||
}
|
||||
path := m.guiCfg.SessionCookiePath
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: m.cookieName,
|
||||
@@ -185,12 +204,26 @@ func (m *tokenCookieManager) createSession(username string, persistent bool, w h
|
||||
// but in http.Cookie MaxAge = 0 means unspecified (session) and MaxAge < 0 means delete immediately
|
||||
MaxAge: maxAge,
|
||||
Secure: useSecureCookie,
|
||||
Path: "/",
|
||||
Path: path,
|
||||
})
|
||||
|
||||
emitLoginAttempt(true, username, r, m.evLogger)
|
||||
}
|
||||
|
||||
func (m *tokenCookieManager) sessionCookieMaxAge() int {
|
||||
switch {
|
||||
case m.guiCfg.SessionCookieDurationS < 0:
|
||||
// A negative value means "never expire the cookie". Use a very
|
||||
// large Max-Age to make the browser keep the cookie for a long
|
||||
// time.
|
||||
return math.MaxInt32
|
||||
case m.guiCfg.SessionCookieDurationS == 0:
|
||||
return defaultSessionCookieDurationS
|
||||
default:
|
||||
return m.guiCfg.SessionCookieDurationS
|
||||
}
|
||||
}
|
||||
|
||||
func (m *tokenCookieManager) hasValidSession(r *http.Request) bool {
|
||||
for _, cookie := range r.Cookies() {
|
||||
// We iterate here since there may, historically, be multiple
|
||||
|
||||
+24
-1
@@ -16,8 +16,18 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var buildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "syncthing",
|
||||
Subsystem: "build",
|
||||
Name: "info",
|
||||
Help: "A metric with a constant '1' value labeled by version information from when the binary was built.",
|
||||
}, []string{"program", "version", "codename", "goversion", "builduser", "builddate", "tags"})
|
||||
|
||||
const Codename = "Hafnium Hornet"
|
||||
|
||||
var (
|
||||
@@ -98,9 +108,22 @@ func LongVersionFor(program string) string {
|
||||
date := Date.UTC().Format("2006-01-02 15:04:05 MST")
|
||||
v := fmt.Sprintf(`%s %s "%s" (%s %s-%s) %s@%s %s`, program, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
|
||||
|
||||
if tags := TagsList(); len(tags) > 0 {
|
||||
tags := TagsList()
|
||||
if len(tags) > 0 {
|
||||
v = fmt.Sprintf("%s [%s]", v, strings.Join(tags, ", "))
|
||||
}
|
||||
|
||||
buildInfo.Reset()
|
||||
buildInfo.With(prometheus.Labels{
|
||||
"program": program,
|
||||
"version": Version,
|
||||
"codename": Codename,
|
||||
"goversion": runtime.Version(),
|
||||
"builduser": fmt.Sprintf("%s@%s", User, Host),
|
||||
"builddate": Date.UTC().Format("2006-01-02 15:04:05 MST"),
|
||||
"tags": strings.Join(tags, ","),
|
||||
}).Set(1)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ func TestDefaultValues(t *testing.T) {
|
||||
MaxSingleEntrySize: 1024,
|
||||
MaxTotalSize: 4096,
|
||||
},
|
||||
BlockIndexing: true,
|
||||
},
|
||||
Device: DeviceConfiguration{
|
||||
Addresses: []string{"dynamic"},
|
||||
@@ -204,6 +205,7 @@ func TestDeviceConfig(t *testing.T) {
|
||||
MaxTotalSize: 4096,
|
||||
Entries: []XattrFilterEntry{},
|
||||
},
|
||||
BlockIndexing: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -786,6 +788,32 @@ func TestGUIConfigURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGUISessionCookiePathPrepare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{name: "empty stays empty", in: "", out: ""},
|
||||
{name: "already rooted", in: " /gui ", out: "/gui"},
|
||||
{name: "needs slash", in: "gui", out: "/gui"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := GUIConfiguration{SessionCookiePath: tc.in}
|
||||
c.prepare()
|
||||
if c.SessionCookiePath != tc.out {
|
||||
t.Fatalf("unexpected path %q != %q", c.SessionCookiePath, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGUIPasswordHash(t *testing.T) {
|
||||
var c GUIConfiguration
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ type DeviceConfiguration struct {
|
||||
Untrusted bool `json:"untrusted" xml:"untrusted"`
|
||||
RemoteGUIPort int `json:"remoteGUIPort" xml:"remoteGUIPort"`
|
||||
RawNumConnections int `json:"numConnections" xml:"numConnections"`
|
||||
Group string `json:"group" xml:"group,attr,omitempty"`
|
||||
}
|
||||
|
||||
func (cfg DeviceConfiguration) Copy() DeviceConfiguration {
|
||||
|
||||
@@ -54,6 +54,7 @@ type FolderConfiguration struct {
|
||||
Path string `json:"path" xml:"path,attr"`
|
||||
Type FolderType `json:"type" xml:"type,attr"`
|
||||
Devices []FolderDeviceConfiguration `json:"devices" xml:"device"`
|
||||
Group string `json:"group" xml:"group,attr,omitempty" restart:"false"`
|
||||
RescanIntervalS int `json:"rescanIntervalS" xml:"rescanIntervalS,attr" default:"3600"`
|
||||
FSWatcherEnabled bool `json:"fsWatcherEnabled" xml:"fsWatcherEnabled,attr" default:"true"`
|
||||
FSWatcherDelayS float64 `json:"fsWatcherDelayS" xml:"fsWatcherDelayS,attr" default:"10"`
|
||||
@@ -86,6 +87,7 @@ type FolderConfiguration struct {
|
||||
SendOwnership bool `json:"sendOwnership" xml:"sendOwnership"`
|
||||
SyncXattrs bool `json:"syncXattrs" xml:"syncXattrs"`
|
||||
SendXattrs bool `json:"sendXattrs" xml:"sendXattrs"`
|
||||
BlockIndexing bool `json:"blockIndexing" xml:"blockIndexing" default:"true"`
|
||||
XattrFilter XattrFilter `json:"xattrFilter" xml:"xattrFilter"`
|
||||
// Legacy deprecated
|
||||
DeprecatedReadOnly bool `json:"-" xml:"ro,attr,omitempty"` // Deprecated: Do not use.
|
||||
|
||||
@@ -33,6 +33,8 @@ type GUIConfiguration struct {
|
||||
InsecureSkipHostCheck bool `json:"insecureSkipHostcheck" xml:"insecureSkipHostcheck,omitempty"`
|
||||
InsecureAllowFrameLoading bool `json:"insecureAllowFrameLoading" xml:"insecureAllowFrameLoading,omitempty"`
|
||||
SendBasicAuthPrompt bool `json:"sendBasicAuthPrompt" xml:"sendBasicAuthPrompt,attr"`
|
||||
SessionCookieDurationS int `json:"sessionCookieDurationS" xml:"sessionCookieDurationS,omitempty" default:"604800"`
|
||||
SessionCookiePath string `json:"sessionCookiePath" xml:"sessionCookiePath,omitempty" default:"/"`
|
||||
}
|
||||
|
||||
func (c GUIConfiguration) IsAuthEnabled() bool {
|
||||
@@ -176,6 +178,12 @@ func (c *GUIConfiguration) prepare() {
|
||||
if c.APIKey == "" {
|
||||
c.APIKey = rand.String(32)
|
||||
}
|
||||
if path := strings.TrimSpace(c.SessionCookiePath); path != "" {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
c.SessionCookiePath = path
|
||||
}
|
||||
}
|
||||
|
||||
func (c GUIConfiguration) Copy() GUIConfiguration {
|
||||
|
||||
@@ -7,11 +7,17 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
@@ -21,6 +27,8 @@ var noFallback = os.Getenv("ALL_PROXY_NO_FALLBACK") != ""
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialerType("socks", socksDialerFunction)
|
||||
proxy.RegisterDialerType("http", httpDialerFunction)
|
||||
proxy.RegisterDialerType("https", httpDialerFunction)
|
||||
|
||||
if proxyDialer := proxy.FromEnvironment(); proxyDialer != proxy.Direct {
|
||||
http.DefaultTransport = &http.Transport{
|
||||
@@ -59,6 +67,114 @@ func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error)
|
||||
return proxy.SOCKS5("tcp", u.Host, auth, forward)
|
||||
}
|
||||
|
||||
type httpProxyDialer struct {
|
||||
proxyURL *url.URL
|
||||
forwardDialer proxy.Dialer
|
||||
}
|
||||
|
||||
func httpDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
|
||||
return &httpProxyDialer{
|
||||
proxyURL: u,
|
||||
forwardDialer: forward,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *httpProxyDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
return h.DialContext(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
// bufferedConn wraps a bufio.Reader (needed by http.ReadResponse) while
|
||||
// providing the other net.Conn methods via embedding.
|
||||
type bufferedConn struct {
|
||||
net.Conn
|
||||
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
func (c *bufferedConn) Read(b []byte) (int, error) {
|
||||
return c.reader.Read(b)
|
||||
}
|
||||
|
||||
var warnCleartextProxyAuthOnce sync.Once
|
||||
|
||||
func (h *httpProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||
return nil, fmt.Errorf("unsupported network for http proxy: %s", network)
|
||||
}
|
||||
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if cd, ok := h.forwardDialer.(proxy.ContextDialer); ok {
|
||||
conn, err = cd.DialContext(ctx, "tcp", h.proxyURL.Host)
|
||||
} else {
|
||||
conn, err = h.forwardDialer.Dial("tcp", h.proxyURL.Host)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial proxy %s: %w", h.proxyURL.Host, err)
|
||||
}
|
||||
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
if err := conn.SetDeadline(deadline); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("set proxy connection deadline: %w", err)
|
||||
}
|
||||
defer func() { _ = conn.SetDeadline(time.Time{}) }()
|
||||
}
|
||||
|
||||
if h.proxyURL.Scheme == "https" {
|
||||
tlsConn := tls.Client(conn, &tls.Config{
|
||||
ServerName: h.proxyURL.Hostname(),
|
||||
})
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("https proxy handshake: %w", err)
|
||||
}
|
||||
conn = tlsConn
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{Host: addr},
|
||||
Host: addr,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
if u := h.proxyURL.User; u != nil {
|
||||
if h.proxyURL.Scheme == "http" {
|
||||
warnCleartextProxyAuthOnce.Do(func() {
|
||||
slog.WarnContext(ctx,
|
||||
"Using basic auth over cleartext HTTP proxy",
|
||||
"proxy", h.proxyURL.Redacted(),
|
||||
)
|
||||
})
|
||||
}
|
||||
password, _ := u.Password()
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(u.Username() + ":" + password))
|
||||
req.Header.Set("Proxy-Authorization", "Basic "+auth)
|
||||
}
|
||||
|
||||
if err := req.Write(conn); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("write proxy CONNECT request: %w", err)
|
||||
}
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
resp, err := http.ReadResponse(br, req)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("read proxy CONNECT response: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("http proxy CONNECT failed: %s", resp.Status)
|
||||
}
|
||||
|
||||
return &bufferedConn{Conn: conn, reader: br}, nil
|
||||
}
|
||||
|
||||
// dialerConn is needed because proxy dialed connections have RemoteAddr() pointing at the proxy,
|
||||
// which then screws up various things such as IsLAN checks, and "let's populate the relay invitation address from
|
||||
// existing connection" shenanigans.
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright (C) 2022 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:build darwin || freebsd || netbsd
|
||||
// +build darwin freebsd netbsd
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (fi basicFileInfo) InodeChangeTime() time.Time {
|
||||
if sys, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||
return time.Unix(0, sys.Ctimespec.Nano())
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (C) 2022 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:build aix || dragonfly || linux || openbsd || solaris || illumos
|
||||
// +build aix dragonfly linux openbsd solaris illumos
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
)
|
||||
|
||||
func (fi basicFileInfo) InodeChangeTime() time.Time {
|
||||
// On Android, mtime and inode-change-time fluctuate, which can cause
|
||||
// conflicts even when nothing has been modified on the device itself.
|
||||
// Ref: https://forum.syncthing.net/t/keep-getting-conflicts-generated-on-android-device-for-files-modified-only-on-a-desktop-pc/19060
|
||||
if build.IsAndroid {
|
||||
return time.Time{}
|
||||
}
|
||||
if sys, ok := fi.FileInfo.Sys().(*syscall.Stat_t); ok {
|
||||
return time.Unix(0, sys.Ctim.Nano())
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var execExts map[string]bool
|
||||
@@ -58,10 +57,6 @@ func (e basicFileInfo) Group() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (basicFileInfo) InodeChangeTime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// osFileInfo converts e to os.FileInfo that is suitable
|
||||
// to be passed to os.SameFile.
|
||||
func (e *basicFileInfo) osFileInfo() os.FileInfo {
|
||||
|
||||
@@ -16,10 +16,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (*BasicFilesystem) SymlinksSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *BasicFilesystem) CreateSymlink(target, name string) error {
|
||||
name, err := f.rooted(name)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,10 +23,6 @@ import (
|
||||
|
||||
var errNotSupported = errors.New("symlinks not supported")
|
||||
|
||||
func (BasicFilesystem) SymlinksSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (BasicFilesystem) ReadSymlink(path string) (string, error) {
|
||||
return "", errNotSupported
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ func (fs *errorFilesystem) Remove(_ string) error { retur
|
||||
func (fs *errorFilesystem) RemoveAll(_ string) error { return fs.err }
|
||||
func (fs *errorFilesystem) Rename(_, _ string) error { return fs.err }
|
||||
func (fs *errorFilesystem) Stat(_ string) (FileInfo, error) { return nil, fs.err }
|
||||
func (*errorFilesystem) SymlinksSupported() bool { return false }
|
||||
func (fs *errorFilesystem) Walk(_ string, _ WalkFunc) error { return fs.err }
|
||||
func (fs *errorFilesystem) Unhide(_ string) error { return fs.err }
|
||||
func (fs *errorFilesystem) Hide(_ string) error { return fs.err }
|
||||
|
||||
@@ -629,10 +629,6 @@ func (fs *fakeFS) Stat(name string) (FileInfo, error) {
|
||||
return fs.Lstat(name)
|
||||
}
|
||||
|
||||
func (*fakeFS) SymlinksSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (*fakeFS) Walk(_ string, _ WalkFunc) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
@@ -1024,7 +1020,3 @@ func (f *fakeFileInfo) Group() int {
|
||||
func (*fakeFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*fakeFileInfo) InodeChangeTime() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ type Filesystem interface {
|
||||
RemoveAll(name string) error
|
||||
Rename(oldname, newname string) error
|
||||
Stat(name string) (FileInfo, error)
|
||||
SymlinksSupported() bool
|
||||
Walk(name string, walkFn WalkFunc) error
|
||||
// If setup fails, returns non-nil error, and if afterwards a fatal (!)
|
||||
// error occurs, sends that error on the channel. Afterwards this watch
|
||||
@@ -101,7 +100,6 @@ type FileInfo interface {
|
||||
IsSymlink() bool
|
||||
Owner() int
|
||||
Group() int
|
||||
InodeChangeTime() time.Time // may be zero if not supported
|
||||
}
|
||||
|
||||
// FileMode is similar to os.FileMode
|
||||
|
||||
@@ -127,12 +127,6 @@ func (fs *logFilesystem) Stat(name string) (FileInfo, error) {
|
||||
return info, err
|
||||
}
|
||||
|
||||
func (fs *logFilesystem) SymlinksSupported() bool {
|
||||
supported := fs.Filesystem.SymlinksSupported()
|
||||
l.Debugln(fs.getCaller(), fs.Type(), fs.URI(), "SymlinksSupported", supported)
|
||||
return supported
|
||||
}
|
||||
|
||||
func (fs *logFilesystem) Walk(root string, walkFn WalkFunc) error {
|
||||
err := fs.Filesystem.Walk(root, walkFn)
|
||||
l.Debugln(fs.getCaller(), fs.Type(), fs.URI(), "Walk", root, walkFn, err)
|
||||
|
||||
+30
-36
@@ -38,37 +38,36 @@ var (
|
||||
|
||||
const (
|
||||
// fs operations
|
||||
metricOpChmod = "chmod"
|
||||
metricOpLchmod = "lchmod"
|
||||
metricOpChtimes = "chtimes"
|
||||
metricOpCreate = "create"
|
||||
metricOpCreateSymlink = "createsymlink"
|
||||
metricOpDirNames = "dirnames"
|
||||
metricOpLstat = "lstat"
|
||||
metricOpMkdir = "mdkir"
|
||||
metricOpMkdirAll = "mkdirall"
|
||||
metricOpOpen = "open"
|
||||
metricOpOpenFile = "openfile"
|
||||
metricOpReadSymlink = "readsymlink"
|
||||
metricOpRemove = "remove"
|
||||
metricOpRemoveAll = "removeall"
|
||||
metricOpRename = "rename"
|
||||
metricOpStat = "stat"
|
||||
metricOpSymlinksSupported = "symlinkssupported"
|
||||
metricOpWalk = "walk"
|
||||
metricOpWatch = "watch"
|
||||
metricOpHide = "hide"
|
||||
metricOpUnhide = "unhide"
|
||||
metricOpGlob = "glob"
|
||||
metricOpRoots = "roots"
|
||||
metricOpUsage = "usage"
|
||||
metricOpType = "type"
|
||||
metricOpURI = "uri"
|
||||
metricOpOptions = "options"
|
||||
metricOpSameFile = "samefile"
|
||||
metricOpPlatformData = "platformdata"
|
||||
metricOpGetXattr = "getxattr"
|
||||
metricOpSetXattr = "setxattr"
|
||||
metricOpChmod = "chmod"
|
||||
metricOpLchmod = "lchmod"
|
||||
metricOpChtimes = "chtimes"
|
||||
metricOpCreate = "create"
|
||||
metricOpCreateSymlink = "createsymlink"
|
||||
metricOpDirNames = "dirnames"
|
||||
metricOpLstat = "lstat"
|
||||
metricOpMkdir = "mdkir"
|
||||
metricOpMkdirAll = "mkdirall"
|
||||
metricOpOpen = "open"
|
||||
metricOpOpenFile = "openfile"
|
||||
metricOpReadSymlink = "readsymlink"
|
||||
metricOpRemove = "remove"
|
||||
metricOpRemoveAll = "removeall"
|
||||
metricOpRename = "rename"
|
||||
metricOpStat = "stat"
|
||||
metricOpWalk = "walk"
|
||||
metricOpWatch = "watch"
|
||||
metricOpHide = "hide"
|
||||
metricOpUnhide = "unhide"
|
||||
metricOpGlob = "glob"
|
||||
metricOpRoots = "roots"
|
||||
metricOpUsage = "usage"
|
||||
metricOpType = "type"
|
||||
metricOpURI = "uri"
|
||||
metricOpOptions = "options"
|
||||
metricOpSameFile = "samefile"
|
||||
metricOpPlatformData = "platformdata"
|
||||
metricOpGetXattr = "getxattr"
|
||||
metricOpSetXattr = "setxattr"
|
||||
|
||||
// file operations
|
||||
metricOpRead = "read"
|
||||
@@ -194,11 +193,6 @@ func (m *metricsFS) Stat(name string) (FileInfo, error) {
|
||||
return m.next.Stat(name)
|
||||
}
|
||||
|
||||
func (m *metricsFS) SymlinksSupported() bool {
|
||||
defer m.account(metricOpSymlinksSupported)(-1)
|
||||
return m.next.SymlinksSupported()
|
||||
}
|
||||
|
||||
func (m *metricsFS) Walk(name string, walkFn WalkFunc) error {
|
||||
defer m.account(metricOpWalk)(-1)
|
||||
return m.next.Walk(name, walkFn)
|
||||
|
||||
+39
-8
@@ -153,12 +153,19 @@ func (f *folder) Serve(ctx context.Context) error {
|
||||
f.sl.DebugContext(ctx, "Folder starting")
|
||||
defer f.sl.DebugContext(ctx, "Folder exiting")
|
||||
|
||||
f.setState(FolderStarting)
|
||||
|
||||
defer func() {
|
||||
f.scanTimer.Stop()
|
||||
f.versionCleanupTimer.Stop()
|
||||
f.setState(FolderIdle)
|
||||
}()
|
||||
|
||||
if err := f.reconcileBlockIndex(ctx); err != nil {
|
||||
f.setError(ctx, err)
|
||||
return err // will get restarted by suture
|
||||
}
|
||||
|
||||
if f.FSWatcherEnabled && f.getHealthErrorAndLoadIgnores() == nil {
|
||||
f.startWatch(ctx)
|
||||
}
|
||||
@@ -175,6 +182,8 @@ func (f *folder) Serve(ctx context.Context) error {
|
||||
pullTimer := time.NewTimer(0)
|
||||
pullTimer.Stop()
|
||||
|
||||
f.setState(FolderIdle)
|
||||
|
||||
for {
|
||||
var err error
|
||||
|
||||
@@ -256,6 +265,15 @@ func (f *folder) Serve(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *folder) reconcileBlockIndex(ctx context.Context) error {
|
||||
if !f.BlockIndexing {
|
||||
f.sl.DebugContext(ctx, "Dropping block index (block indexing disabled)")
|
||||
return f.db.DropBlockIndex(f.folderID)
|
||||
}
|
||||
f.sl.DebugContext(ctx, "Populating block index if empty")
|
||||
return f.db.PopulateBlockIndex(f.folderID)
|
||||
}
|
||||
|
||||
func (*folder) BringToFront(string) {}
|
||||
|
||||
func (*folder) Override() {}
|
||||
@@ -556,12 +574,14 @@ type scanBatch struct {
|
||||
f *folder
|
||||
updateBatch *FileInfoBatch
|
||||
toRemove []string
|
||||
deleted map[string]struct{}
|
||||
}
|
||||
|
||||
func (f *folder) newScanBatch() *scanBatch {
|
||||
b := &scanBatch{
|
||||
f: f,
|
||||
toRemove: make([]string, 0, maxToRemove),
|
||||
deleted: make(map[string]struct{}),
|
||||
}
|
||||
b.updateBatch = NewFileInfoBatch(func(fs []protocol.FileInfo) error {
|
||||
if err := b.f.getHealthErrorWithoutIgnores(); err != nil {
|
||||
@@ -569,6 +589,7 @@ func (f *folder) newScanBatch() *scanBatch {
|
||||
return err
|
||||
}
|
||||
b.f.updateLocalsFromScanning(fs)
|
||||
clear(b.deleted)
|
||||
return nil
|
||||
})
|
||||
return b
|
||||
@@ -604,6 +625,15 @@ func (b *scanBatch) FlushIfFull() error {
|
||||
return b.updateBatch.FlushIfFull()
|
||||
}
|
||||
|
||||
func (b *scanBatch) markDeleted(name string) {
|
||||
b.deleted[name] = struct{}{}
|
||||
}
|
||||
|
||||
func (b *scanBatch) hasDeleted(name string) bool {
|
||||
_, ok := b.deleted[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Update adds the fileinfo to the batch for updating, and does a few checks.
|
||||
// It returns false if the checks result in the file not going to be updated or removed.
|
||||
func (b *scanBatch) Update(fi protocol.FileInfo) (bool, error) {
|
||||
@@ -680,7 +710,6 @@ func (f *folder) scanSubdirsChangedAndNew(ctx context.Context, subDirs []string,
|
||||
fchan = scanner.Walk(scanCtx, scanConfig)
|
||||
}
|
||||
|
||||
alreadyUsedOrExisting := make(map[string]struct{})
|
||||
for res := range fchan {
|
||||
if res.Err != nil {
|
||||
f.newScanError(res.Path, res.Err)
|
||||
@@ -705,11 +734,12 @@ func (f *folder) scanSubdirsChangedAndNew(ctx context.Context, subDirs []string,
|
||||
switch f.Type {
|
||||
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
|
||||
default:
|
||||
if nf, ok := f.findRename(ctx, res.File, alreadyUsedOrExisting); ok {
|
||||
if nf, ok := f.findRename(ctx, res.File, batch); ok {
|
||||
if ok, err := batch.Update(nf); err != nil {
|
||||
return 0, err
|
||||
} else if ok {
|
||||
changes++
|
||||
batch.markDeleted(nf.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -878,7 +908,7 @@ outer:
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (f *folder) findRename(ctx context.Context, file protocol.FileInfo, alreadyUsedOrExisting map[string]struct{}) (protocol.FileInfo, bool) {
|
||||
func (f *folder) findRename(ctx context.Context, file protocol.FileInfo, batch *scanBatch) (protocol.FileInfo, bool) {
|
||||
if len(file.Blocks) == 0 || file.Size == 0 {
|
||||
return protocol.FileInfo{}, false
|
||||
}
|
||||
@@ -899,11 +929,10 @@ loop:
|
||||
}
|
||||
|
||||
if fi.Name == file.Name {
|
||||
alreadyUsedOrExisting[fi.Name] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := alreadyUsedOrExisting[fi.Name]; ok {
|
||||
if batch.hasDeleted(fi.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -922,8 +951,6 @@ loop:
|
||||
continue
|
||||
}
|
||||
|
||||
alreadyUsedOrExisting[fi.Name] = struct{}{}
|
||||
|
||||
if !osutil.IsDeleted(f.mtimefs, fi.Name) {
|
||||
continue
|
||||
}
|
||||
@@ -1264,7 +1291,11 @@ func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) error {
|
||||
}
|
||||
|
||||
func (f *folder) updateLocals(fs []protocol.FileInfo) error {
|
||||
if err := f.db.Update(f.folderID, protocol.LocalDeviceID, fs); err != nil {
|
||||
var opts []db.UpdateOption
|
||||
if !f.BlockIndexing {
|
||||
opts = append(opts, db.WithSkipBlockIndex())
|
||||
}
|
||||
if err := f.db.Update(f.folderID, protocol.LocalDeviceID, fs, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1166,6 +1166,7 @@ func (f *sendReceiveFolder) handleFile(ctx context.Context, file protocol.FileIn
|
||||
blocks: blocks,
|
||||
have: len(have),
|
||||
}
|
||||
|
||||
copyChan <- cs
|
||||
return nil
|
||||
}
|
||||
@@ -1322,7 +1323,7 @@ func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo, dbUpdateChan ch
|
||||
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 {
|
||||
if folder == f.ID || !cfg.BlockIndexing {
|
||||
continue
|
||||
}
|
||||
otherFolderFilesystems[folder] = cfg.Filesystem()
|
||||
@@ -1390,13 +1391,26 @@ func (f *sendReceiveFolder) copyBlock(ctx context.Context, block protocol.BlockI
|
||||
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(ctx, f.ID, block, state, f.mtimefs, buf) {
|
||||
return true
|
||||
// Check for the block in the current version of the file
|
||||
if idx, ok := state.curFileBlocks[string(block.Hash)]; ok {
|
||||
if f.copyBlockFromFile(ctx, state.curFile.Name, state.curFile.Blocks[idx].Offset, state, f.mtimefs, block, buf) {
|
||||
state.copiedFromOrigin(block.Size)
|
||||
return true
|
||||
}
|
||||
if state.failed() != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if state.failed() != nil {
|
||||
return false
|
||||
|
||||
if f.folder.BlockIndexing {
|
||||
// Hope that it's usually in the same folder, so start with that
|
||||
// one. Also possibly more efficient copy (same filesystem).
|
||||
if f.copyBlockFromFolder(ctx, f.ID, block, state, f.mtimefs, buf) {
|
||||
return true
|
||||
}
|
||||
if state.failed() != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for folderID, ffs := range otherFolderFilesystems {
|
||||
@@ -1544,10 +1558,21 @@ func (f *sendReceiveFolder) pullBlock(ctx context.Context, state pullBlockState,
|
||||
return
|
||||
}
|
||||
|
||||
if !f.DisableSparseFiles && state.reused == 0 && state.block.IsEmpty() {
|
||||
if state.block.IsEmpty() {
|
||||
// There is no need to request a block of all zeroes. Pretend we
|
||||
// requested it and handled it correctly.
|
||||
state.pullDone(state.block)
|
||||
if state.reused != 0 || f.DisableSparseFiles {
|
||||
// We are reusing a file (contents apparently weren't all-zeroes
|
||||
// previously), or sparse files are disabled, so we need to
|
||||
// actually write the block.
|
||||
zeroes := make([]byte, state.block.Size)
|
||||
err = f.limitedWriteAt(ctx, fd, zeroes, state.block.Offset)
|
||||
}
|
||||
if err != nil {
|
||||
state.fail(fmt.Errorf("save: %w", err))
|
||||
} else {
|
||||
state.pullDone(state.block)
|
||||
}
|
||||
out <- state.sharedPullerState
|
||||
return
|
||||
}
|
||||
@@ -1593,11 +1618,10 @@ loop:
|
||||
}
|
||||
|
||||
// Verify that the received block matches the desired hash, if not
|
||||
// try pulling it from another device.
|
||||
// For receive-only folders, the hash is not SHA256 as it's an
|
||||
// encrypted hash token. In that case we can't verify the block
|
||||
// integrity so we'll take it on trust. (The other side can and
|
||||
// will verify.)
|
||||
// try pulling it from another device. For receive-encrypted
|
||||
// folders, the hash is not SHA256 as it's an encrypted hash token.
|
||||
// In that case we can't verify the block integrity so we'll take it
|
||||
// on trust. (The other side can and will verify.)
|
||||
if f.Type != config.FolderTypeReceiveEncrypted {
|
||||
lastError = f.verifyBuffer(buf, state.block)
|
||||
}
|
||||
@@ -1789,19 +1813,6 @@ loop:
|
||||
lastFile = job.file
|
||||
}
|
||||
|
||||
if !job.file.IsDeleted() && !job.file.IsInvalid() {
|
||||
// Now that the file is finalized, grab possibly updated
|
||||
// inode change time from disk into the local FileInfo. We
|
||||
// use this change time to check for changes to xattrs etc
|
||||
// on next scan.
|
||||
if err := f.updateFileInfoChangeTime(&job.file); err != nil {
|
||||
// This means on next scan the likely incorrect change time
|
||||
// (resp. whatever caused the error) will cause this file to
|
||||
// change. Log at info level to leave a trace if a user
|
||||
// notices, but no need to warn
|
||||
f.sl.Warn("Failed to update metadata at database commit", slogutil.FilePath(job.file.Name), slogutil.Error(err))
|
||||
}
|
||||
}
|
||||
job.file.Sequence = 0
|
||||
|
||||
batch.Append(job.file)
|
||||
@@ -2187,22 +2198,6 @@ func (f *sendReceiveFolder) withLimiter(ctx context.Context, fn func() error) er
|
||||
return fn()
|
||||
}
|
||||
|
||||
// updateFileInfoChangeTime updates the inode change time in the FileInfo,
|
||||
// because that depends on the current, new, state of the file on disk.
|
||||
func (f *sendReceiveFolder) updateFileInfoChangeTime(file *protocol.FileInfo) error {
|
||||
info, err := f.mtimefs.Lstat(file.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ct := info.InodeChangeTime(); !ct.IsZero() {
|
||||
file.InodeChangeNs = ct.UnixNano()
|
||||
} else {
|
||||
file.InodeChangeNs = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A []FileError is sent as part of an event and will be JSON serialized.
|
||||
type FileError struct {
|
||||
Path string `json:"path"`
|
||||
|
||||
@@ -647,9 +647,6 @@ func TestDeleteIgnorePerms(t *testing.T) {
|
||||
fi, err := scanner.CreateFileInfo(stat, name, ffs, false, false, config.XattrFilter{})
|
||||
must(t, err)
|
||||
ffs.Chmod(name, 0o600)
|
||||
if info, err := ffs.Stat(name); err == nil {
|
||||
fi.InodeChangeNs = info.InodeChangeTime().UnixNano()
|
||||
}
|
||||
scanChan := make(chan string, 1)
|
||||
err = f.checkToBeDeleted(fi, fi, true, scanChan)
|
||||
must(t, err)
|
||||
@@ -920,6 +917,88 @@ func TestPullCtxCancel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPullEmptyBlock exercises pullBlock for blocks of all zeroes.
|
||||
// pullBlock distinguishes three cases:
|
||||
//
|
||||
// - Sparse files enabled, reused == 0: nothing to write, the offset
|
||||
// reads as zero anyway because the temp file is sparse-allocated.
|
||||
// - reused != 0: the temp file is being reused and may already hold
|
||||
// non-zero data at this offset, so zeroes must be written explicitly.
|
||||
// - DisableSparseFiles: cannot rely on a sparse hole reading as zero,
|
||||
// so zeroes must be written explicitly.
|
||||
func TestPullEmptyBlock(t *testing.T) {
|
||||
const blockSize = protocol.MinBlockSize
|
||||
emptyBlock := protocol.BlockInfo{
|
||||
Offset: 0,
|
||||
Size: blockSize,
|
||||
Hash: blocks[0].Hash, // sha256 of a 128 KiB all-zeroes block
|
||||
}
|
||||
if !emptyBlock.IsEmpty() {
|
||||
t.Fatal("test setup: block not recognised as empty")
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
reused []int
|
||||
disableSparse bool
|
||||
preFillTempFile bool
|
||||
}{
|
||||
{name: "sparse-clean"},
|
||||
{name: "reused-temp-file", reused: []int{1}, preFillTempFile: true},
|
||||
{name: "disable-sparse-files", disableSparse: true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, f := setupSendReceiveFolder(t)
|
||||
f.folder.FolderConfiguration.DisableSparseFiles = tc.disableSparse
|
||||
|
||||
file := protocol.FileInfo{
|
||||
Name: "file",
|
||||
Size: int64(blockSize),
|
||||
Permissions: 0o644,
|
||||
Blocks: []protocol.BlockInfo{emptyBlock},
|
||||
}
|
||||
tempName := fs.TempName(file.Name)
|
||||
|
||||
// Pre-fill the temp file with non-zero data so that a missing
|
||||
// overwrite would be detectable.
|
||||
if tc.preFillTempFile {
|
||||
writeFile(t, f.Filesystem(), tempName, bytes.Repeat([]byte{0xff}, blockSize))
|
||||
}
|
||||
|
||||
state := pullBlockState{
|
||||
sharedPullerState: newSharedPullerState(file, f.Filesystem(), f.folderID, tempName, file.Blocks, tc.reused, false, false, protocol.FileInfo{}, !tc.disableSparse, false),
|
||||
block: emptyBlock,
|
||||
}
|
||||
|
||||
out := make(chan *sharedPullerState, 1)
|
||||
f.pullBlock(t.Context(), state, out)
|
||||
|
||||
s := <-out
|
||||
if err := s.failed(); err != nil {
|
||||
t.Fatalf("pullBlock failed: %v", err)
|
||||
}
|
||||
cleanupSharedPullerState(s)
|
||||
|
||||
fd, err := f.Filesystem().Open(tempName)
|
||||
must(t, err)
|
||||
defer fd.Close()
|
||||
data, err := io.ReadAll(fd)
|
||||
must(t, err)
|
||||
|
||||
if len(data) != blockSize {
|
||||
t.Fatalf("temp file too short: got %d bytes, want at least %d", len(data), emptyBlock.Offset+int64(blockSize))
|
||||
}
|
||||
for i, b := range data {
|
||||
if b != 0 {
|
||||
t.Fatalf("byte %d: got %#x, want 0", i, b)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullDeleteUnscannedDir(t *testing.T) {
|
||||
_, f := setupSendReceiveFolder(t)
|
||||
ffs := f.Filesystem()
|
||||
|
||||
@@ -27,12 +27,15 @@ const (
|
||||
FolderCleaning
|
||||
FolderCleanWaiting
|
||||
FolderError
|
||||
FolderStarting
|
||||
)
|
||||
|
||||
func (s folderState) String() string {
|
||||
switch s {
|
||||
case FolderIdle:
|
||||
return "idle"
|
||||
case FolderStarting:
|
||||
return "starting"
|
||||
case FolderScanning:
|
||||
return "scanning"
|
||||
case FolderScanWaiting:
|
||||
|
||||
+96
-9
@@ -1667,8 +1667,6 @@ func waitForState(t *testing.T, sub events.Subscription, folder, expected string
|
||||
}
|
||||
if err == expected {
|
||||
return
|
||||
} else {
|
||||
t.Error(ev)
|
||||
}
|
||||
}
|
||||
case <-timeout:
|
||||
@@ -3344,6 +3342,87 @@ func TestRenameSameFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenameBatchFlush verifies that rename detection works correctly when
|
||||
// a batch flush happens mid-scan. With enough files to exceed
|
||||
// MaxBatchSizeFiles the scan batch flushes at least once, clearing the
|
||||
// in-memory deleted-tracking map. After the flush the database itself
|
||||
// guards against reusing an already-consumed rename source. The fake
|
||||
// filesystem iterates in non-deterministic order, so the two rename pairs
|
||||
// may or may not straddle a flush boundary on any given run; with
|
||||
// 2*MaxBatchSizeFiles filler files the cross-flush case is hit roughly half
|
||||
// the time.
|
||||
func TestRenameBatchFlush(t *testing.T) {
|
||||
wcfg, fcfg := newDefaultCfgWrapper(t)
|
||||
m := setupModel(t, wcfg)
|
||||
defer cleanupModel(m)
|
||||
|
||||
ffs := fcfg.Filesystem()
|
||||
|
||||
// Two source files with identical content so they share a blocks hash.
|
||||
content := []byte("shared-content-for-rename-detection")
|
||||
writeFile(t, ffs, "src-a", content)
|
||||
writeFile(t, ffs, "src-b", content)
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
// Delete the sources and create two new destinations with the same
|
||||
// content plus enough filler files to force at least one batch flush.
|
||||
must(t, ffs.Remove("src-a"))
|
||||
must(t, ffs.Remove("src-b"))
|
||||
writeFile(t, ffs, "dst-a", content)
|
||||
writeFile(t, ffs, "dst-b", content)
|
||||
for i := range MaxBatchSizeFiles * 2 {
|
||||
writeFile(t, ffs, fmt.Sprintf("filler-%04d", i), []byte(fmt.Sprintf("filler-%04d", i)))
|
||||
}
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
// Collect all files keyed by name.
|
||||
files := make(map[string]protocol.FileInfo)
|
||||
it, errFn := m.LocalFilesSequenced("default", protocol.LocalDeviceID, 0)
|
||||
for fi := range it {
|
||||
files[fi.FileName()] = fi
|
||||
}
|
||||
if err := errFn(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, name := range []string{"src-a", "src-b"} {
|
||||
fi, ok := files[name]
|
||||
if !ok {
|
||||
t.Fatalf("%q not found in DB", name)
|
||||
}
|
||||
if !fi.IsDeleted() {
|
||||
t.Fatalf("%q should be deleted", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range []string{"dst-a", "dst-b"} {
|
||||
fi, ok := files[name]
|
||||
if !ok {
|
||||
t.Fatalf("%q not found in DB", name)
|
||||
}
|
||||
if fi.IsDeleted() {
|
||||
t.Fatalf("%q should not be deleted", name)
|
||||
}
|
||||
}
|
||||
|
||||
// When rename detection works the deleted source is appended to the
|
||||
// batch right after its destination, so their sequences are adjacent
|
||||
// (src.seq == dst.seq + 1). If detection failed the sources would
|
||||
// only be deleted in a later scan phase with much higher sequences.
|
||||
dstSeqs := map[int64]bool{
|
||||
files["dst-a"].SequenceNo(): true,
|
||||
files["dst-b"].SequenceNo(): true,
|
||||
}
|
||||
for _, name := range []string{"src-a", "src-b"} {
|
||||
srcSeq := files[name].SequenceNo()
|
||||
if !dstSeqs[srcSeq-1] {
|
||||
t.Errorf("deleted %q (seq %d) not adjacent to a destination file; rename was not detected", name, srcSeq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockListMap(t *testing.T) {
|
||||
wcfg, fcfg := newDefaultCfgWrapper(t)
|
||||
m := setupModel(t, wcfg)
|
||||
@@ -3599,15 +3678,17 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
|
||||
m.promoteConnections()
|
||||
|
||||
// Initial CCs
|
||||
initTimeout := time.NewTimer(time.Second)
|
||||
defer initTimeout.Stop()
|
||||
select {
|
||||
case <-cc1:
|
||||
default:
|
||||
t.Fatal("missing initial CC from device1")
|
||||
case <-initTimeout.C:
|
||||
t.Fatal("timed out waiting for initial CC from device1")
|
||||
}
|
||||
select {
|
||||
case <-cc2:
|
||||
default:
|
||||
t.Fatal("missing initial CC from device2")
|
||||
case <-initTimeout.C:
|
||||
t.Fatal("timed out waiting for initial CC from device2")
|
||||
}
|
||||
|
||||
t.Log("Applying config change")
|
||||
@@ -3700,13 +3781,19 @@ func TestIssue6961(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCompletionEmptyGlobal(t *testing.T) {
|
||||
m, conn, fcfg := setupModelWithConnection(t)
|
||||
m, _, fcfg := setupModelWithConnection(t)
|
||||
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
|
||||
|
||||
// Insert a local file
|
||||
files := []protocol.FileInfo{{Name: "foo", Version: protocol.Vector{}.Update(myID.Short()), Sequence: 1}}
|
||||
m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files)
|
||||
must(t, m.sdb.Update(fcfg.ID, protocol.LocalDeviceID, files))
|
||||
|
||||
// A remote announces it deleted
|
||||
files[0].Deleted = true
|
||||
files[0].Version = files[0].Version.Update(device1.Short())
|
||||
must(t, m.IndexUpdate(conn, &protocol.IndexUpdate{Folder: fcfg.ID, Files: files}))
|
||||
must(t, m.sdb.Update(fcfg.ID, device1, files))
|
||||
|
||||
// Our completion should be 95%
|
||||
comp := m.testCompletion(protocol.LocalDeviceID, fcfg.ID)
|
||||
if comp.CompletionPct != 95 {
|
||||
t.Error("Expected completion of 95%, got", comp.CompletionPct)
|
||||
|
||||
@@ -25,18 +25,19 @@ import (
|
||||
// updated along the way.
|
||||
type sharedPullerState struct {
|
||||
// Immutable, does not require locking
|
||||
file protocol.FileInfo // The new file (desired end state)
|
||||
fs fs.Filesystem
|
||||
folder string
|
||||
tempName string
|
||||
realName string
|
||||
reused int // Number of blocks reused from temporary file
|
||||
ignorePerms bool
|
||||
hasCurFile bool // Whether curFile is set
|
||||
curFile protocol.FileInfo // The file as it exists now in our database
|
||||
sparse bool
|
||||
created time.Time
|
||||
fsync bool
|
||||
file protocol.FileInfo // The new file (desired end state)
|
||||
fs fs.Filesystem
|
||||
folder string
|
||||
tempName string
|
||||
realName string
|
||||
reused int // Number of blocks reused from temporary file
|
||||
ignorePerms bool
|
||||
hasCurFile bool // Whether curFile is set
|
||||
curFile protocol.FileInfo // The file as it exists now in our database
|
||||
curFileBlocks map[string]int // block hash to index in curFile
|
||||
sparse bool
|
||||
created time.Time
|
||||
fsync bool
|
||||
|
||||
// Mutable, must be locked for access
|
||||
err error // The first error we hit
|
||||
@@ -54,6 +55,12 @@ type sharedPullerState struct {
|
||||
}
|
||||
|
||||
func newSharedPullerState(file protocol.FileInfo, fs fs.Filesystem, folderID, tempName string, blocks []protocol.BlockInfo, reused []int, ignorePerms, hasCurFile bool, curFile protocol.FileInfo, sparse bool, fsync bool) *sharedPullerState {
|
||||
// Map the existing blocks by hash to block index in the current file
|
||||
blocksMap := make(map[string]int, len(curFile.Blocks))
|
||||
for idx, block := range curFile.Blocks {
|
||||
blocksMap[string(block.Hash)] = idx
|
||||
}
|
||||
|
||||
return &sharedPullerState{
|
||||
file: file,
|
||||
fs: fs,
|
||||
@@ -69,6 +76,7 @@ func newSharedPullerState(file protocol.FileInfo, fs fs.Filesystem, folderID, te
|
||||
ignorePerms: ignorePerms,
|
||||
hasCurFile: hasCurFile,
|
||||
curFile: curFile,
|
||||
curFileBlocks: blocksMap,
|
||||
sparse: sparse,
|
||||
fsync: fsync,
|
||||
created: time.Now(),
|
||||
|
||||
@@ -110,6 +110,7 @@ func newFolderConfig() config.FolderConfiguration {
|
||||
cfg.FSWatcherEnabled = false
|
||||
cfg.PullerDelayS = 0
|
||||
cfg.Devices = append(cfg.Devices, config.FolderDeviceConfiguration{DeviceID: device1})
|
||||
cfg.BlockIndexing = true
|
||||
return cfg
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -8,4 +8,4 @@ package pmp
|
||||
|
||||
import "github.com/syncthing/syncthing/internal/slogutil"
|
||||
|
||||
var l = slogutil.NewAdapter("NAT-PMP discovery and port mapping")
|
||||
func init() { slogutil.RegisterPackage("NAT-PMP discovery and port mapping") }
|
||||
|
||||
+6
-10
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
natpmp "github.com/jackpal/go-nat-pmp"
|
||||
|
||||
"github.com/syncthing/syncthing/internal/slogutil"
|
||||
"github.com/syncthing/syncthing/lib/nat"
|
||||
"github.com/syncthing/syncthing/lib/netutil"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
@@ -34,14 +35,14 @@ func Discover(ctx context.Context, renewal, timeout time.Duration) []nat.Device
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
l.Debugln("Failed to discover gateway", err)
|
||||
slog.DebugContext(ctx, "Failed to discover gateway", slogutil.Error(err))
|
||||
return nil
|
||||
}
|
||||
if ip == nil || ip.IsUnspecified() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Debugln("Discovered gateway at", ip)
|
||||
slog.DebugContext(ctx, "Discovered gateway", "ip", ip)
|
||||
|
||||
c := natpmp.NewClientWithTimeout(ip, timeout)
|
||||
// Try contacting the gateway, if it does not respond, assume it does not
|
||||
@@ -51,13 +52,8 @@ func Discover(ctx context.Context, renewal, timeout time.Duration) []nat.Device
|
||||
return ierr
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(err.Error(), "Timed out") {
|
||||
slog.Debug("Timeout trying to get external address, assume no NAT-PMP available")
|
||||
return nil
|
||||
}
|
||||
slog.DebugContext(ctx, "Failed to get external address", slogutil.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
var localIP net.IP
|
||||
@@ -69,7 +65,7 @@ func Discover(ctx context.Context, renewal, timeout time.Duration) []nat.Device
|
||||
conn.Close()
|
||||
localIP, err = osutil.IPFromAddr(conn.LocalAddr())
|
||||
if localIP == nil {
|
||||
l.Debugln("Failed to lookup local IP", err)
|
||||
slog.DebugContext(ctx, "Failed to lookup local IP", slogutil.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,10 +139,6 @@ type FileInfo struct {
|
||||
// the protocol.
|
||||
LocalFlags FlagLocal
|
||||
|
||||
// The time when the inode was last changed (i.e., permissions, xattrs
|
||||
// etc changed). This is host-local, not sent over the wire.
|
||||
InodeChangeNs int64
|
||||
|
||||
// The size of the data appended to the encrypted file on disk. This is
|
||||
// host-local, not sent over the wire.
|
||||
EncryptionTrailerSize int
|
||||
@@ -184,7 +180,6 @@ func (f *FileInfo) ToWire(withInternalFields bool) *bep.FileInfo {
|
||||
}
|
||||
if withInternalFields {
|
||||
w.LocalFlags = uint32(f.LocalFlags)
|
||||
w.InodeChangeNs = f.InodeChangeNs
|
||||
w.EncryptionTrailerSize = int32(f.EncryptionTrailerSize)
|
||||
}
|
||||
return w
|
||||
@@ -284,7 +279,6 @@ type FileInfoWithoutBlocks interface {
|
||||
GetBlockSize() int32
|
||||
GetPlatform() *bep.PlatformData
|
||||
GetLocalFlags() uint32
|
||||
GetInodeChangeNs() int64
|
||||
GetEncryptionTrailerSize() int32
|
||||
GetDeleted() bool
|
||||
GetInvalid() bool
|
||||
@@ -322,7 +316,6 @@ func fileInfoFromWireWithBlocks(w FileInfoWithoutBlocks, blocks []BlockInfo) Fil
|
||||
func FileInfoFromDB(w *bep.FileInfo) FileInfo {
|
||||
f := FileInfoFromWire(w)
|
||||
f.LocalFlags = FlagLocal(w.LocalFlags)
|
||||
f.InodeChangeNs = w.InodeChangeNs
|
||||
f.EncryptionTrailerSize = int(w.EncryptionTrailerSize)
|
||||
return f
|
||||
}
|
||||
@@ -330,7 +323,6 @@ func FileInfoFromDB(w *bep.FileInfo) FileInfo {
|
||||
func FileInfoFromDBTruncated(w FileInfoWithoutBlocks) FileInfo {
|
||||
f := fileInfoFromWireWithBlocks(w, nil)
|
||||
f.LocalFlags = FlagLocal(w.GetLocalFlags())
|
||||
f.InodeChangeNs = w.GetInodeChangeNs()
|
||||
f.EncryptionTrailerSize = int(w.GetEncryptionTrailerSize())
|
||||
f.truncated = true
|
||||
return f
|
||||
@@ -339,14 +331,14 @@ func FileInfoFromDBTruncated(w FileInfoWithoutBlocks) FileInfo {
|
||||
func (f FileInfo) String() string {
|
||||
switch f.Type {
|
||||
case FileInfoTypeDirectory:
|
||||
return fmt.Sprintf("Directory{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, Platform:%v, InodeChangeTime:%v}",
|
||||
f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.Platform, f.InodeChangeTime())
|
||||
return fmt.Sprintf("Directory{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, Platform:%v}",
|
||||
f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.Platform)
|
||||
case FileInfoTypeFile:
|
||||
return fmt.Sprintf("File{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, Length:%d, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, BlockSize:%d, NumBlocks:%d, BlocksHash:%x, Platform:%v, InodeChangeTime:%v}",
|
||||
f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.Size, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.RawBlockSize, len(f.Blocks), f.BlocksHash, f.Platform, f.InodeChangeTime())
|
||||
return fmt.Sprintf("File{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, Length:%d, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, BlockSize:%d, NumBlocks:%d, BlocksHash:%x, Platform:%v}",
|
||||
f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.Size, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.RawBlockSize, len(f.Blocks), f.BlocksHash, f.Platform)
|
||||
case FileInfoTypeSymlink, FileInfoTypeSymlinkDirectory, FileInfoTypeSymlinkFile:
|
||||
return fmt.Sprintf("Symlink{Name:%q, Type:%v, Sequence:%d, Version:%v, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, SymlinkTarget:%q, Platform:%v, InodeChangeTime:%v}",
|
||||
f.Name, f.Type, f.Sequence, f.Version, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.SymlinkTarget, f.Platform, f.InodeChangeTime())
|
||||
return fmt.Sprintf("Symlink{Name:%q, Type:%v, Sequence:%d, Version:%v, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, SymlinkTarget:%q, Platform:%v}",
|
||||
f.Name, f.Type, f.Sequence, f.Version, f.Deleted, f.IsInvalid(), f.LocalFlags, f.NoPermissions, f.SymlinkTarget, f.Platform)
|
||||
default:
|
||||
panic("mystery file type detected")
|
||||
}
|
||||
@@ -462,10 +454,6 @@ func (f FileInfo) PlatformData() PlatformData {
|
||||
return f.Platform
|
||||
}
|
||||
|
||||
func (f FileInfo) InodeChangeTime() time.Time {
|
||||
return time.Unix(0, f.InodeChangeNs)
|
||||
}
|
||||
|
||||
func (f FileInfo) FileBlocksHash() []byte {
|
||||
return f.BlocksHash
|
||||
}
|
||||
@@ -514,12 +502,6 @@ func (f FileInfo) isEquivalent(other FileInfo, comp FileInfoComparison) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we care about either ownership or xattrs, are recording inode change
|
||||
// times and it changed, they are not equal.
|
||||
if !(comp.IgnoreOwnership && comp.IgnoreXattrs) && f.InodeChangeNs != 0 && other.InodeChangeNs != 0 && f.InodeChangeNs != other.InodeChangeNs {
|
||||
return false
|
||||
}
|
||||
|
||||
// Mask out the ignored local flags before checking IsInvalid() below
|
||||
f.LocalFlags &^= comp.IgnoreFlags
|
||||
other.LocalFlags &^= comp.IgnoreFlags
|
||||
|
||||
@@ -49,6 +49,10 @@ const (
|
||||
// MaxBlockSize is the maximum block size allowed
|
||||
MaxBlockSize = 16 << MiB
|
||||
|
||||
// MaxRequestSize is the largest amount of data that can be read in a
|
||||
// single request
|
||||
MaxRequestSize = 2 * MaxBlockSize
|
||||
|
||||
// DesiredPerFileBlocks is the number of blocks we aim for per file
|
||||
DesiredPerFileBlocks = 2000
|
||||
|
||||
@@ -457,14 +461,6 @@ func (c *rawConnection) dispatcherLoop() (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *bep.Request:
|
||||
err = checkFilename(msg.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return newProtocolError(err, msgContext)
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *bep.ClusterConfig:
|
||||
err = c.model.ClusterConfig(clusterConfigFromWire(msg))
|
||||
@@ -484,6 +480,15 @@ func (c *rawConnection) dispatcherLoop() (err error) {
|
||||
err = c.handleIndexUpdate(idxUp)
|
||||
|
||||
case *bep.Request:
|
||||
if err := checkFilename(msg.Name); err != nil {
|
||||
return newProtocolError(err, msgContext)
|
||||
}
|
||||
if msg.Size <= 0 {
|
||||
return newProtocolError(fmt.Errorf("request size %d too small", msg.Size), msgContext)
|
||||
}
|
||||
if msg.Size > MaxRequestSize {
|
||||
return newProtocolError(fmt.Errorf("request size %d exceeds maximum allowed", msg.Size), msgContext)
|
||||
}
|
||||
go c.handleRequest(requestFromWire(msg))
|
||||
|
||||
case *bep.Response:
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -541,6 +543,84 @@ func TestDispatcherToCloseDeadlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMaxSize(t *testing.T) {
|
||||
invalidSize := []int{-65536, 0, MaxRequestSize + 1}
|
||||
for _, s := range invalidSize {
|
||||
t.Run(fmt.Sprintf("invalid/%d", s), func(t *testing.T) {
|
||||
m := newTestModel()
|
||||
rw := testutil.NewBlockingRW()
|
||||
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||
c.Start()
|
||||
defer closeAndWait(c, rw)
|
||||
|
||||
c.inbox <- &bep.ClusterConfig{}
|
||||
|
||||
// A request at exactly MaxRequestSize should be accepted.
|
||||
c.inbox <- &bep.Request{
|
||||
Id: 1,
|
||||
Name: "valid",
|
||||
Size: MaxRequestSize,
|
||||
}
|
||||
|
||||
res := <-c.outbox
|
||||
if msg, ok := res.msg.(*bep.Response); !ok || msg.Id != 1 {
|
||||
t.Errorf("bad response %#v", msg)
|
||||
}
|
||||
|
||||
// A request with an invalid size should cause the dispatcher to
|
||||
// return with a protocol error.
|
||||
c.inbox <- &bep.Request{
|
||||
Id: 2,
|
||||
Name: "invalid",
|
||||
Size: int32(s),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.dispatcherLoopStopped:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out before dispatcher loop terminated")
|
||||
}
|
||||
|
||||
err := m.closedError()
|
||||
if err == nil {
|
||||
t.Fatal("expected connection to be closed with an error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "protocol error") {
|
||||
t.Errorf("expected a protocol error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestInvalidFilename(t *testing.T) {
|
||||
m := newTestModel()
|
||||
rw := testutil.NewBlockingRW()
|
||||
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
|
||||
c.Start()
|
||||
defer closeAndWait(c, rw)
|
||||
|
||||
c.inbox <- &bep.ClusterConfig{}
|
||||
c.inbox <- &bep.Request{
|
||||
Id: 1,
|
||||
Name: "../escape",
|
||||
Size: 1024,
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.dispatcherLoopStopped:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timed out before dispatcher loop terminated")
|
||||
}
|
||||
|
||||
err := m.closedError()
|
||||
if err == nil {
|
||||
t.Fatal("expected connection to be closed with an error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "protocol error") {
|
||||
t.Errorf("expected a protocol error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexIDString(t *testing.T) {
|
||||
// Index ID is a 64 bit, zero padded hex integer.
|
||||
var i IndexID = 42
|
||||
|
||||
@@ -133,13 +133,17 @@ func TestRelay(ctx context.Context, uri *url.URL, certs []tls.Certificate, sleep
|
||||
}()
|
||||
defer cancel()
|
||||
|
||||
// Give the join a moment to happen, increasing the chance the
|
||||
// invitation check works on the first try
|
||||
time.Sleep(sleep / 10)
|
||||
|
||||
for range times {
|
||||
_, err = GetInvitationFromRelay(ctx, uri, id, certs, timeout)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
incorrectResponseCodeErr := &incorrectResponseCodeErr{}
|
||||
if errors.As(err, &incorrectResponseCodeErr) {
|
||||
if errors.As(err, &incorrectResponseCodeErr) && incorrectResponseCodeErr.code != protocol.ResponseNotFound.Code {
|
||||
return fmt.Errorf("getting invitation: %w", err)
|
||||
}
|
||||
time.Sleep(sleep)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user